One of the things that has always bothered me about the ASP.NET validation controls is that it sometimes takes two or three of them to validate a particular field. Say, for example, that you have a password field you want to validate. Normally it's required, has a minimum and maximum length, and needs to be compared against a confirmation field. It takes about three controls to make that happen:
<asp:RequiredFieldValidator runat="server"
ID="reqPassword" ControlToValidate="txtPassword" Text="*"
ErrorMessage="Password is required" />
<asp:RegularExpressionValidator runat="server"
ID="regexPassword" ControlToValidate="txtPassword" Text="*"
ErrorMessage="Password length must be at least 6 characters" />
<asp:CompareValidator runat="server" ID="comparePassword"
ControlToValidate="txtPassword"
ControlToCompare="txtPasswordConfirm" Text="*"
ErrorMessage="Confirm password" />
I’m currently in the process of building my own validation control that allows you to put one control on the page, then add as many “Validation Rules” to the control as you wish. So my validation control looks something more like this:
<
rd:ValidationRuleSet runat="server" id="vrs1"
ControlToValidate="TextBox1" >
<rd:RequiredRule Text="*" Message="Password is Required" />
<rd:RegexRule Text="*" Regex=".{6,}"
Message="Password length must be at least 6 characters" />
<rd:CompareRule Text="*" DataType="String"
Message="Confirm Password" CompareOperator="Equal"
ControlToCompare="txtPasswordConfirm" />
rd:ValidationRuleSet>
This approach also makes it easy to programmatically add rules to the control without having to worry about dynamically adding controls to the page, which is important because the end goal is to make it easy to store validation rules and error messages in a database where they can be easily maintained. But I digress…
I’ve got the basic validation rules down: required, compare, regular expression, data range, etc. But I also wanted to give developers the ability to create custom rules for their projects by extending a base validation rule class (and then write a lot of JavaScript for the Client-Side support). Offering design-time support for a collection of items (in this case rules) is fairly easy. All you have to do is create a class that inherits from System.ComponentModel.Design.CollectionEditor and override the Type[] CreateNewItemTypes() method. From the CreateNewItemTypes() method, you just return an array of the various types you want to allow developers to add to your collection through the collection editor. The issue that I was running into was how to offer design time support for a custom rule? Especially since custom rules would probably only be used on a project-by-project basis.
I decided the best thing to do is allow developers to define their custom rules in the web.config, allowing for rule definitions on a project-by-project basis. Which leads me to the issue of how do you access the web.config at design time? At run time it’s fairly easy to find the web.config because you have a lot of nice methods for locating files using the Server object or the Page.ResolveUrl method. Design-time is a bit less obvious. And I wasn’t (and am still not) very familiar with design-time code. So I set out to find a way to do it.
Google did its job and landed me at Josh Flanagan’s blog entry titled Accessing web.config at Design Time in .NET 2.0. It’s got all the code you need if you want to access the web.config from a System.Web.UI.Design.ControlDesigner (Thanks Josh!). Unfortunately, I was needing a way to access it from a CollectionEditor. But the examples proved very useful, so after digging around the object available to me using Intellisense, I came up with the following solution inside the CreateNewItemTypes method of a class that derives from CollectionEditor:
protected
override Type[] CreateNewItemTypes()
{
ValidationConfiguration validationConfig = null;
if (Context != null && Context.Container != null &&
Context.Container.Components != null)
{
IWebApplication webApp = null;
int componentIndex = 0;
while (webApp == null && componentIndex <
Context.Container.Components.Count)
{
webApp = Context.Container.Components
[componentIndex].Site.GetService
(typeof(IWebApplication)) as IWebApplication;
componentIndex++;
}
if (webApp != null)
{
IProjectItem item = webApp.GetProjectItemFromUrl
("~/web.config");
if (item != null)
{
System.Xml.XmlDocument doc =
new System.Xml.XmlDocument();
doc.Load(item.PhysicalPath);
ValidationConfigurationSectionHandler handler =
new ValidationConfigurationSectionHandler();
validationConfig = handler.Create(null, null,
doc.SelectSingleNode
("configuration/rebelDeveloper/validation"))
as ValidationConfiguration;
}
}
}
if (validationConfig == null)
{
validationConfig = new ValidationConfiguration();
}
validationConfig.AddPrimaryType(typeof(MinLengthRule),
typeof(MaxLengthRule), typeof(RegexRule), typeof(CompareRule),
typeof(RangeRule), typeof(RequiredRule));
return validationConfig.CreateSupportedTypeArray();
}
The general idea is that you are looking for an IWebApplication service, and you get it by calling the Site.GetService method on an IComponent object. In Josh’s examples, there is a property called Component in the ControlDesigner class that offers the method. In the CollectionEditor class you have to dig a bit deeper, and you ultimately get a collection of components. Since I don’t really know much about design time stuff, I figured I would just loop through all of them looking for a IWebApplication service (though it does seem to always come back from the first component in the collection. I figure that at design time, it’s okay to be a bit inefficient and redundant.
So now I’ve got configurable design-time rules.