Print

Integrating Validation Application Block with ASP.NET part 1

This post describes how to take integration of Validation Application Block with ASP.NET Web Forms to the next level by introducing extension methods that centralize the creation of PropertyProxyValidator controls and enable compile time support.

Last year, when reading the Hands-On Labs document for the Enterprise Library Validation Application Block, it struck me that the integration with ASP.NET Web Forms pretty sucked when using Validation Application Block (VAB). While the Hands-On Labs document has a special chapter (Lab 12) devoted on ASP.NET integration and the framework contains an assembly specially for this task, I couldn’t help noticing that it was cumbersome and error prone.

I have to admit, the Patterns & Practices team did a good job in creating nice design time support. When you follow the instructions in the Hands-On Lab document you will be able to drag a PropertyProxyValidator to the form and configure it. A PropertyProxyValidator is a ASP.NET validator control that functions as proxy between the ASP.NET validation mechanism and the VAB. As its name describes, it allows you to validate a single property from your domain.

The problem however is that you will usually have many PropertyProxyValidator controls on the page. Each validator has to be configured with at least the type to validate (SourceTypeName),  the name of the property to validate (PropertyName), the ID of the control to validate (ControlToValidate), and optionally the ruleset to validate (RulesetName). Not only is it lot of work to hook up all validators on the page, but the complete configuration is given in markup, making it almost impossible to quickly refactor the domain or the representation of the validators later on.

Here is an example of how a PropertyProxyValidator looks in markup:

<asp:TextBox runat="server" ID="FirstNameTextBox" />

<vab:PropertyProxyValidator runat="server"
ID="Validator1"
SourceTypeName="Company.Application.Domain.Person"
PropertyName="FirstName"
ControlToValidate="FirstNameTextBox"
RulesetName="Alternative"
/>

A better solution would be to wire up the validators in the code behind, in a way that gives us compile time support. There is no easy way to do this with VAB out of the box, but as always, it’s not difficult to get a pleasurable workaround for this. We could for instance write an extension method that allows appending a validator to the page just behind the control to validate:

protected override void OnPreInit(EventArgs e)
{
// Add a validator for the Person.LastName property.
this.LastNameTextBox.AddValidatorFor<Person>(p => p.LastName);

// Add a validator for the 'Alternative' VAB ruleset and set
// an alternative ASP.NET validation group.
this.FirstNameTextBox
.AddValidatorFor<Person>(p => p.FirstName, "Alternative")
.ValidationGroup = "AlternativeValidationGroup";

base.OnPreInit(e);
}

The first line of code registers a validator on the LastNameTextBox for the Person.LastName property. The validator will be added to the page directly after the control to validate. The second line registers a validator for the ‘Alternative’ rule set of the Person.FirstName. Because the extension method returns the created validator, we can directly set the ValidationGroup of this validator. Of course I wouldn’t advice using literal strings as done in this example, but you get the idea.

Here’s the code for these extension methods:

public static BaseValidator AddValidatorFor<T>(
this Control control, Expression<Func<T, object>> property)
{
return AddValidatorFor<T>(control, property, string.Empty);
}

public static BaseValidator AddValidatorFor<T>(
this Control control, Expression<Func<T, object>> property,
string rulesetName)
{
var validator = CreateValidatorFor(control, property,
rulesetName);

AddValidatorToPageJustAfterControl(validator, control);

return validator;
}

public static BaseValidator CreateValidatorFor<T>(
this Control control, Expression<Func<T, object>> property)
{
return CreateValidatorFor(control, property, string.Empty);
}

public static BaseValidator CreateValidatorFor<T>(
this Control control, Expression<Func<T, object>> property,
string rulesetName)
{
PropertyProxyValidator proxy = CreateNewProxyValidator();

proxy.ControlToValidate = control.ID;

BindProxyValidatorToDomainProperty(proxy, property,
rulesetName);

return proxy;
}

private static PropertyProxyValidator CreateNewProxyValidator()
{
return new PropertyProxyValidator()
{
Display = ValidatorDisplay.Static,
};
}

private static void BindProxyValidatorToDomainProperty<T>(
PropertyProxyValidator proxy,
Expression<Func<T, object>> property, string rulesetName)
{
proxy.PropertyName = GetPropertyName(property);
proxy.SourceTypeName = typeof(T).AssemblyQualifiedName;
proxy.RulesetName = rulesetName;
}

private static string GetPropertyName(LambdaExpression property)
{
var member = property.Body as MemberExpression;

if (member == null)
{
var unaryExpression = (UnaryExpression)property.Body;
member = (MemberExpression)unaryExpression.Operand;
}

return member.Member.Name;
}

private static void AddValidatorToPageJustAfterControl(
BaseValidator validator, Control control)
{
int index = control.Parent.Controls.IndexOf(control);

try
{
control.Parent.Controls.AddAt(index + 1, validator);
}
catch (HttpException ex)
{
throw BuildMoreExpressiveException(control, ex);
}
}

private static Exception BuildMoreExpressiveException(
Control control, HttpException exception)
{
return new InvalidOperationException(string.Format(
"Sorry, you have encountered a rare .NET bug. " +
"Control '{1}', which is the parent control of '{0}'" +
", contains '<% %>' tags. Because of the existance " +
"of those tags, this parent control can not be " +
"modified and it is impossible to dynamically add " +
"validators to it. You can fix this by wrapping " +
"'{0}' in another control. For instance: " +
"<asp:{2} runat='server' ID='{1}'>" +
"<asp:PlaceHolder runat='server'>" +
"<asp:{3} runat='server' id='{0}' />" +
"</asp:PlaceHolder>" +
"</asp:{2}>. {4}", control.ID, control.Parent.ID,
control.Parent.GetType().Name,
control.GetType().Name, exception.Message));
}

I think this code rather speaks for itself. When called, the method will create a new validator by binding to the control to validate and binding it to the property in the domain. After that it adds the validator to the page’s control hierarchy.

This code has several other benefits. First of all, while working with a PropertyProxyValidator internally, it returns the newly created instance by it’s base type: BaseValidator. This way we can prevent our presentation layer from having a direct dependency on the Validation Application Block, which is a good thing from an architectural perspective.

A second benefit is that we’ve now centralized the creation of these validators and can customize the creation more easily. As you might know, the default behavior of the ASP.NET validators and therefore the PropertyProxyValidator is to display the full error message inline. It would be more attractive when the validator would only show an image and allow the error message to be shown when the image is hovered with the mouse. With the current design we only have to change the CreateNewProxyValidator method. Here is the improved implementation:

private static PropertyProxyValidator CreateNewProxyValidator()
{
var proxy = new PropertyProxyValidator()
{
Display = ValidatorDisplay.Static,
};

// Let's make the proxy more fancy :-)
proxy.Text = "<img src='status_failed_small.gif' />";

// Here is a little trick to set the title attribute
// with the error message.
proxy.PreRender += (sender, e) =>
{
var title = proxy.ErrorMessage.Replace("<br/>", ",");

if (title.Length > 0 && title[title.Length - 1] == ',')
{
title = title.Substring(0, title.Length - 1);
}

proxy.Attributes["title"] = title;
};

return proxy;
}

There is a bit dirty trickery going on in this method, because of how the PropertyProxyValidator is implemented, but at least we’re now able to change the look and feel of our application. You don’t want to get this task assigned to you when all PropertyProxyValidator were defined in markup.

Please note that the code presented in this post has one important drawback: it will fail miserably when we're trying to validate anything else than text, such as numbers, dates etc. Please read part 2 for a solution to this problem.

Please note that there is an important quirk in de design of ASP.NET that you might come across when implementing this solution. The exception message of the BuildMoreExpressiveException method already gives it away. The solution proposed in this article works by injecting controls in to the page's control hierarchy. However, when an ASP.NET page contains <% %> code blocks, the collection of controls containing such a code block gets immutable and the line control.Parent.Controls.AddAt(index + 1, proxy); will result in an HttpException. You can work around this problem by wrapping the control that you want to validate with a server tag, such as an <asp:PlaceHolder></asp:PlaceHolder>. By throwing an exception with a very descriptive message, I'm hoping to save you and your fellow developers a lot of time, when you encounter this problem.

Happy validating!

- ASP.NET, C#, Enterprise Library, Validation Application Block - four comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

four comments:

Interesting post Steven, I'm looking into the VAB too. It's nice to have this type of functionality delivered to you off the shelf however compile time checking is a necessity in my opinion.
Hans van Bakel (URL) - 03 10 10 - 12:31

Hi Steve,
This is really a great post and fabulous idea.

I have one question here - you have not used ServerSideValidationExtender so I am not sure how PropertyProxyValidator is going to work.

Please do reply.

Thanks,
Mandeep.
Mandeep (URL) - 27 07 11 - 23:26

Hi Mandeep,

The ServerSideValidationExtender is part of the AjaxControlToolkit, which I'm not very familiar with, but I think it's just a matter of changing the AddValidatorToPageJustAfterControl method to the following, and you're good to go:

private static void AddValidatorToPageJustAfterControl(
    BaseValidator proxy, Control control)
{
    int index = control.Parent.Controls.IndexOf(control);
    control.Parent.Controls.AddAt(index + 1, proxy);
    
    if (proxy is PropertyProxyValidator)
    {
    AddServerSideValidationExtender(proxy, control);
    }
}

private static void AddServerSideValidationExtender(
    BaseValidator proxy, Control control)
{
    var extender = new ServerSideValidationExtender();
    extender.TargetControlID = proxy.ID;
    control.Parent.Controls.Add(extender);
}
Steven (URL) - 29 07 11 - 18:31

HI steve,

Thanks for replying. I knew it is missing so I also added it.

Regards,
Mandeep.
Mandeep (URL) - 30 07 11 - 16:10


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.