I'm looking at some oldish code that sits inside of a web control library. This is my main web control library were I keep all of my custom controls that I typically use. It includes all the AJAX controls, a custom databinder, some generic status display controls, a number of stock control extensions, plus a host of web control related helper routines used in control development in general. It's a fairly focused library assembly that's fully self contained without outside dependencies.
The databinder control of the library can run fully self contained, but I also have an optional dependency on my business class library. Basically the automatic databinding validation that takes place can take validation errors from my business objects and automatically shove those validation errors into the databinder so that the databinder's validation code can display these errors - in addition to unbinding errors - in a consistent fashion. It works great, but doing this introduces a dependency into my control project that I always have to remember when I distribute the control assembly when it's not used in relation to the business library (which is common).
So the question is how to deal with this effectively. I have been futzing around with this a bit today and while I have a working solution I'm not really happy with it so I thought I throw this out for some discussion here in case I'm missing something obvious.
So the original code inside of the Web Control library does basically this:
#if USE_WWBUSINESS
/// <summary>
/// Takes a collection of ValidationErrors and assigns it to the
/// matching controls. These controls must match in signature as follows:
/// Must have the same name as the field and a 3 letter prefix. For example,
///
/// txtCompany - matches company field
/// cmbCountry - matches the Country field
/// </summary>
/// <returns></returns>
public void AddValidationErrorsToBindingErrors(Westwind.BusinessObjects.ValidationErrorCollection errors)
{
foreach (Westwind.BusinessObjects.ValidationError error in errors)
{
Control ctl = wwWebUtils.FindControlRecursive(this.Page.Form, error.ControlID);
this.AddBindingError(error.Message, ctl);
}
}
#endif
and I then have a project level #define constant that I have to set or unset for compilation. The Westwind.BusinessObjects.ValidationErrorCollection is located in a separate generic business library assembly and so in order to compile the code I need to ensure that the assembly is available.
This works, but it's a hassle for two reasons:
- It's easy to forget the switch and get compile errors
- The assembly is used in several projects some of which require the switch others that don't
- There's no way to tell looking at the assembly whether it has the switch set or not
Regardless though - once compiled - I can deploy the assembly even if the Westwind.BusinessObjects assembly is not present and the assembly works fine as long as the above method is not called.
So what if I want to abstract away the dependency?
The first and probably easiest alternative to do this is to do away with the concrete type altogether and then rely on Reflection to get the values. Something like the following works to remove the dependency:
/// <summary>
/// Takes a collection of ValidationErrors and assigns it to the
/// matching controls. These controls must match in signature as follows:
/// Must have the same name as the field and a 3 letter prefix. For example,
///
/// txtCompany - matches company field
/// cmbCountry - matches the Country field
///
/// The input parameter is a generic IList value, but the type should be
/// specifically Westwind.BusinessObjects.ValidationErrorCollection. The
/// generic parameter is used here to avoid an assembly dependence.
/// </summary>
/// <returns></returns>
/// </summary>
/// <param name="errors">Westwind.BusinessObjects.ValidationErrorCollection list</param>
public void AddValidationErrorsToBindingErrors(IList errors)
{
if (errors == null)
return;
foreach (object error in errors)
{
string controlID = wwUtils.GetProperty(error, "ControlID") as string;
string message = wwUtils.GetProperty(error, "Message") as string;
Control ctl = wwWebUtils.FindControlRecursive(this.Page.Form, controlID);
this.AddBindingError(message, ctl);
}
}
wwUtils.GetProperty() is a wrapper Reflection method that easily retrieves a property using Reflection and there are a bunch of routines on wwUtils that provide similar functionality to use Reflection to call methods, set values and provided 'pathed' Reflection access using . syntax (ie. wwUtils.GetProperty(loObject,"Address.Street")).
Again, this is functional and better since it removes the external dependency but it still doesn't make me happy because the method interface is not very clean to the end user - they have no idea what's expected as an input value at this point other than that it's an IList interface. Yet it won't really work unless the specific ValidationErrorCollection is passed. Hence the rather lengthy comment and explicit type description in the parameter comment which hopefully shows up with the XML documentation file if shipped.
Unfortunately I can't really think of a cleaner way to do this and preserve the type interface as anything that provides the type - Westwind.BusinessObject.ValidationErrorCollection - would introduce the assembly dependence. Using an interface to describe the errors is no better because there too the type would need to be present in both assemblies somehow. I can't see a way to share the type or interface without some sort of external reference.
The only other thing I could possibly see would be to subclass the control and pull it back into the business project or more appropriately into a Web project. The control could then get a new name and provide an override with the explicit type. But that too would suck because it'd have to be done for every web project. Or pull the control subclass into the business project which would work, but be pretty unclean and then require a System.Web in the business library which also is not very clean.
So what do you think? Is there another way to approach this without polluting one or the other assemblies with assembly references that shouldn't be there?
Other Posts you might also like