Data validation is a very important part of any enterprise application. ASP.NET has a validation framework, but it is very limited in scope and starts falling apart as soon as you need to perform more complex validations. Problems with ASP.NET validation framework are well documented
by Peter Blum on his web site, so we are not going to repeat them here. Peter has also built a nice replacement for the standard ASP.NET validation framework, which is worth looking into if you prefer standard ASP.NET validation mechanism to the one offered by Spring.NET for some reason. Both frameworks will allow you to perform very complex validations, but we designed Spring.NET validation framework differently for the reasons described below.
On the Windows Forms side situation is even worse, and data validation features are completely inadequate, as pointed out by Ian Griffiths in this article
.
One of the major problems we saw in most validation frameworks available today, both open source and commercial, is that they are tied to a specific presentation technology. ASP.NET validation framework uses ASP.NET controls to define validation rules, so these rules end up in the HTML markup of your pages. Peter Blum's framework uses the same approach. In our opinion validation is not applicable only to the presentation layer, so there is no reason to tie it to any particular technology. We designed Spring.NET Validation Framework in such a way that it enables data validation in different application layers using the same validation rules.
Goals that we wanted to achieve were the following:
- Allow validation of any object, whether it is UI control or a domain object
- Allow the same validation framework to be used in both Windows Forms and ASP.NET applications, as well as in the service layer (to validate parameters passed to the service, for example)
- Allow composition of the validation rules, so arbitrarly complex validation rule sets can be constructed
- Allow validators to be conditional, so they only execute if a specific condition is met
Following sections will describe in more detail how these goals were achieved and show you how to use Spring.NET Validation Framework in your applications.
Validation Framework in Action
Decoupling validation from presentation was the major goal that significantly influenced design of the validation framework. Basically, we wanted to be able to define a set of validation rules that are completely independent from the presentation, so we can reuse them (or at least have the ability to reuse them) in different application layers. This meant that the approach taken by Microsoft ASP.NET team would not work, and custom validation controls were not an option.
The approach we took was to configure validation rules just like any other object managed by Spring, within application context. However, due to possible complexity of the validation rules, we decided not to use standard Spring.NET configuration schema for validator definitions, but to define a more specific and much easier to use custom configuration schema for validation. Following example shows validation rules for the Trip object in SpringAir sample application:
<objects xmlns="http://www.springframework.net" xmlns:v="http://www.springframework.net/validation">
<object type="TripForm.aspx" parent="standardPage">
<property name="TripValidator" ref="tripValidator" />
</object>
<v:group id="tripValidator">
<v:required id="departureAirportValidator" test="StartingFrom.AirportCode">
<v:message id="error.departureAirport.required" providers="departureAirportErrors, validationSummary"/>
</v:required>
<v:group id="destinationAirportValidator">
<v:required test="ReturningFrom.AirportCode">
<v:message id="error.destinationAirport.required" providers="destinationAirportErrors, validationSummary"/>
</v:required>
<v:condition test="ReturningFrom.AirportCode != StartingFrom.AirportCode" when="ReturningFrom.AirportCode != ''">
<v:message id="error.destinationAirport.sameAsDeparture" providers="destinationAirportErrors, validationSummary"/>
</v:condition>
</v:group>
<v:group id="departureDateValidator">
<v:required test="StartingFrom.Date">
<v:message id="error.departureDate.required" providers="departureDateErrors, validationSummary"/>
</v:required>
<v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue">
<v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/>
</v:condition>
</v:group>
<v:group id="returnDateValidator" when="Mode == 'RoundTrip'">
<v:required test="ReturningFrom.Date">
<v:message id="error.returnDate.required" providers="returnDateErrors, validationSummary"/>
</v:required>
<v:condition test="ReturningFrom.Date >= StartingFrom.Date" when="ReturningFrom.Date != DateTime.MinValue">
<v:message id="error.returnDate.beforeDeparture" providers="returnDateErrors, validationSummary"/>
</v:condition>
</v:group>
</v:group>
</objects>
There are a few things to note in the example above:
- You need to reference validation schema by adding xmlns:v="http://www.springframework.net/validation" namespace declaration to the root element.
- You can mix standard object definitions and validator definitions in the same configuration file, as long as both schemas are referenced.
- Validator is an object like any other, so you can reference it in a standard way, just like we are doing when injecting tripValidator into TripForm.aspx page definition.
- Validation framework uses Spring's powerful expression evaluation engine to evaluate both validation rule and applicability conditions for the validator, so any valid Spring expression can be specified within test and when attributes of any validator.
The example above shows many of the features of the framework, so let's discuss them one by one
Validator Groups
Validators can be grouped together for many reasons, but the most typical usage scenario is group multiple validation rules that apply to the same value. In the example above, there is a validator group for almost every property of the Trip instance, and then there is a top-level group for the Trip object itself that groups all other validators.
There are three types of validator groups, each with a different behavior:
| Type |
XML Tag |
Behavior |
| AND |
group |
Returns true only if all contained validators return true. This is the most commonly used validator group. |
| OR |
any |
Returns true if one or more of the contained validators return true. |
| XOR |
exclusive |
Returns true if only one of the contained validators return true. |
While the first type (AND) is definitely the most useful, the other two allow you to implement some specific validation scenarios in a very simple way, so you should keep them in mind when designing your validation rules.
One thing to remember is that validator group is a validator like any other and can be used anywhere validator is expected. You can nest groups within other groups and reference them using validator reference syntax (described later), so they really allow you to structure your validation rules in the most reusable way.
Validators
Ultimately, you will have one or more validator definitions for each piece of data that you want to validate. Spring.NET has several built-in validators that are sufficient for most validations, even fairly complex ones, but framework is also extensible and allows you to write custom validators and use them in the same way as the built-in ones.
Condition Validator
Syntax:
<v:condition id="id" test="testCondition" when="applicabilityCondition" parent="parentValidator">
actions
</v:condition>
Example:
<v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue">
<v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/>
</v:condition>
Condition validator could be considered "the mother of all validators". You can use it to achieve almost anything that can be achieved by using other validator types, but in some cases test expression might be very complex, which is why you should use more specific validator type if possible. However, condition validator is still your best bet if you need to check whether particular value belongs to a particular range, or perform a similar test, as those conditions are fairly easy to write.
 |
Keep in mind that Spring.NET Validation Framework typically works with domain objects, after data binding from the controls has been performed, which means that the object being validated is strongly typed. This means that you can easily compare numbers and dates without having to worry if theor string representation is comparable. |
Required Validator
Syntax:
<v:required id="id" test="requiredValue" when="applicabilityCondition" parent="parentValidator">
actions
</v:required>
Example:
<v:required test="ReturningFrom.AirportCode">
<v:message id="error.destinationAirport.required" providers="destinationAirportErrors, validationSummary"/>
</v:required>
Required validator is also one of the most commonly used ones, and it is much more powerful than the ASP.NET required validator, because it works with many other data types other than strings. For example, it will allow you to validate DateTime instances (both MinValue and MaxValue return false), integer and decimal numbers, as well as any reference type, in which case it returns true for a non-null value and false for {{null}}s.
Test attribute for the required validator will typically specify an expression that resolves to a property of a domain object, but it could be any valid expression that returns a value, including a method call.
Regular Expression Validator
Syntax:
<v:regex id="id" test="valueToEvaluate" when="applicabilityCondition" parent="parentValidator">
<v:property name="Expression" value="regularExpressionToMatch"/>
<v:property name="Options" value="regexOptions"/>
actions
</v:regex>
Example:
<v:regex test="ReturningFrom.AirportCode">
<v:property name="Expression" value="[A-Z][A-Z][A-Z]"/>
<v:message id="error.destinationAirport.threeCharacters" providers="destinationAirportErrors, validationSummary"/>
</v:regex>
Regular expression validator is very useful when validating values that need to conform to some predefined format, such as telephone numbers, email addresses, URLs, etc.
One major difference of the regular expression validator compared to other built-in validator types is that you need to set a required Expression property to a regular expression to match against.
Generic Validator
Syntax:
<v:validator id="id" test="requiredValue" when="applicabilityCondition" type="validatorType" parent="parentValidator">
actions
</v:validator>
Example:
<v:validator test="ReturningFrom.AirportCode" type="MyNamespace.MyAirportCodeValidator, MyAssembly">
<v:message id="error.destinationAirport.invalid" providers="destinationAirportErrors, validationSummary"/>
</v:required>
Generic validator allows you to plug in your custom validator by specifying its type name. Custom validators are very simple to implement, because all you need to do is extend BaseValidator class and implement abstract bool Validate(object objectToValidate) method. Your implementation simply needs to return true if it determines that object is valid, or false otherwise.
Conditional Validator Execution
As you can see from the examples above, each validator (and validator group) allows you to define its applicability condition by specifying a logical expression as the value of when attribute. This feature is very useful and is one of the major deficiences in the standard ASP.NET validation framework, because in many cases specific validators need to be turned on or off based on the values of the object being validated.
For example, when validating Trip object we need to validate return date only if the Trip.Mode property is set to TripMode.RoundTrip enum value. In order to achieve that we created following validator definition:
<v:group id="returnDateValidator" when="Mode == 'RoundTrip'">
// nested validators
</v:group>
Validators within this group will only be evaluated for round trips.
 |
You should also note that you can compare enums using string value of the enumerated option. You can also use fully qualified enum name, such as:
Mode == TripMode.RoundTrip
However, in this case you need to make sure that alias for the TripMode enum type is registered using Spring's standard type aliasing mechanism. |
Validation Actions
Validation actions are executed every time containing validator is executed. They allow you to do anything you want based on the result of the validation. By far the most common use of validation action is to add validation error message to the errors collection, but theoretically you could do anything you want. Because adding validation error message to the errors collection is such a common scenario, Spring.NET validation schema defines a separate XML tag for this type of validation action.
Error Messages
Syntax:
<v:message id="messageId" providers="errorProviderList" when="messageApplicabilityCondition">
<v:param value="paramExpression"/>
</v:message>
Example:
<v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary">
<v:param value="StartingFrom.Date.ToString('D')"/>
<v:param value="DateTime.Today.ToString('D')"/>
</v:message>
There are several things that you have to be aware of when dealing with error messages:
- Message id is used to lookup error message in the appropriate Spring.NET message source.
- providers attribute specifies comma separated list of "error buckets" particular error message should be added to. These "buckets" will later be used by the particular presentation technology in order to display error messages as necessary.
- Message can have zero or more parameters. Each parameter is an expression that will be resolved using current validation context and the resolved values will be passed as parameters to IMessageSource.GetMessage method, which will return fully resolved message.
Generic Actions
Syntax:
<v:action type="actionType" when="actionApplicabilityCondition">
properties
</v:action>
Example:
<v:action type="Spring.Validation.Actions.ExpressionAction, Spring.Core" when="#page != null">
<v:property name="Valid" value="#page.myPanel.Visible = true"/>
<v:property name="Invalid" value="#page.myPanel.Visible = false"/>
</v:action>
Generic actions can be used to perform all kinds of validation actions. In simple cases, such as in the example above where we turn control's visibility on or off depending on the validation result, you can use the built-in ExpressionAction class and simply specify expressions to be evaluated based on the validator result.
In other situations you may want to create your own action implementation, which is fairly simple thing to do – all you need to do is implement IValidationAction interface:
public interface IValidationAction
{
void Execute(bool isValid, object validationContext, IDictionary contextParams, ValidationErrors errors);
}
Validator References
Sometimes it is not possible (or desireable) to nest all validation rules within a single top-level validator group. For example, if you have object graph where both ObjectA and ObjectB have a reference to ObjectC, you might want to set up validation rules for ObjectC only once and reference them from the validation rules for both ObjectA and ObjectB, instead of duplicating them within both definitions.
This is very easy to achieve with Spring.NET using validator reference configuration tag.
Validator Reference Tag
Syntax:
<v:ref name="referencedValidatorId" context="validationContextForTheReferencedValidator"/>
Example:
<v:group id="objectA.validator">
<v:ref name="objectC.validator" context="MyObjectC"/>
// other validators for ObjectA
</v:group>
<v:group id="objectB.validator">
<v:ref name="objectC.validator" context="ObjectCProperty"/>
// other validators for ObjectB
</v:group>
<v:group id="objectC.Validator">
// validators for ObjectC
</v:group>
It is as simple as that — you define validation rules for ObjectC separately and reference them from within other validation groups. Important thing to realize that in most cases you will also want to "narrow" the context for the referenced validator, typicvally by specifying the name of the property that holds referenced object. In the example above, ObjectA.MyObjectC and ObjectB.ObjectCProperty are both of type ObjectC, which objectC.validator expects to recieve as the validation context.
Using Validation Framework from ASP.NET
Now that you know how to configure validation rules, let's see what it takes to evaluate those rules within your typical ASP.NET application and to display error messages.
The first thing you need to do is inject validators you want to use into your ASP.NET page, as shown in the example below:
<objects xmlns="http://www.springframework.net" xmlns:v="http://www.springframework.net/validation">
<object type="TripForm.aspx" parent="standardPage">
<property name="TripValidator" ref="tripValidator" />
</object>
<v:group id="tripValidator">
// our validation rules
</v:group>
</objects>
Once that's done, you need to perform validation in one or more of the page event handlers, which typically looks similar to this:
public void SearchForFlights(object sender, EventArgs e)
{
if (Validate(Controller.Trip, tripValidator))
{
Process.SetView(Controller.SearchForFlights());
}
}
 |
Keep in mind that your ASP.NET page needs to extend Spring.Web.UI.Page in order for the code above to work. |
Finally, you need to define where validation errors should be displayed by adding one or more <spring:validationError/> and <spring:validationSummary/> controls to the ASP.NET form:
<%@ Page Language="c#" MasterPageFile="~/Web/StandardTemplate.master" Inherits="TripForm" CodeFile="TripForm.aspx.cs" %>
<%@ Register TagPrefix="spring" Namespace="Spring.Web.Anthem.UI.Controls" Assembly="Spring.Web.Anthem" %>
<%@ Register TagPrefix="anthem" Namespace="Anthem" Assembly="Anthem" %>
<asp:Content ID="head" ContentPlaceHolderID="head" runat="server">
<script language="javascript" type="text/javascript">
<!--
function showReturnCalendar(isVisible)
{
document.getElementById('<%= returningOnDate.ClientID %>').style.visibility = isVisible? '': 'hidden';
document.getElementById('returningOnCalendar').style.visibility = isVisible? '': 'hidden';
}
-->
</script>
</asp:Content>
<asp:Content ID="body" ContentPlaceHolderID="body" runat="server">
<div style="text-align: center">
<h4><asp:Label ID="caption" runat="server"></asp:Label></h4>
<spring:ValidationSummary ID="validationSummary" runat="server" />
<table>
<tr class="formLabel">
<td> </td>
<td colspan="3">
<spring:RadioButtonGroup ID="tripMode" runat="server">
<asp:RadioButton ID="OneWay" onclick="showReturnCalendar(false);" runat="server" />
<asp:RadioButton ID="RoundTrip" onclick="showReturnCalendar(true);" runat="server" />
</spring:RadioButtonGroup>
</td>
</tr>
<tr>
<td class="formLabel" align="right">
<asp:Label ID="leavingFrom" runat="server" /></td>
<td nowrap="nowrap">
<anthem:DropDownList ID="leavingFromAirportCode" AutoCallBack="true" runat="server" />
<spring:ValidationError id="departureAirportErrors" runat="server" />
</td>
<td class="formLabel" align="right">
<asp:Label ID="goingTo" runat="server" /></td>
<td nowrap="nowrap">
<anthem:DropDownList ID="goingToAirportCode" AutoCallBack="true" runat="server" />
<spring:ValidationError id="destinationAirportErrors" runat="server" />
</td>
</tr>
<tr>
<td class="formLabel" align="right">
<asp:Label ID="leavingOn" runat="server" /></td>
<td nowrap="nowrap">
<spring:Calendar ID="leavingFromDate" runat="server" Width="75px" AllowEditing="true" Skin="system" />
<spring:ValidationError id="departureDateErrors" runat="server" />
</td>
<td class="formLabel" align="right">
<asp:Label ID="returningOn" runat="server" /></td>
<td nowrap="nowrap">
<div id="returningOnCalendar">
<spring:Calendar ID="returningOnDate" runat="server" Width="75px" AllowEditing="true" Skin="system" />
<spring:ValidationError id="returnDateErrors" runat="server" />
</div>
</td>
</tr>
<tr>
<td class="buttonBar" colspan="4">
<br/>
<anthem:Button ID="findFlights" runat="server"/></td>
</tr>
</table>
</div>
<script language="javascript" type="text/javascript">
if (document.getElementById('<%= tripMode.ClientID %>').value == 'OneWay')
showReturnCalendar(false);
else
showReturnCalendar(true);
</script>
</asp:Content>
Rendering Validation Errors
Spring.NET allows you to render validation errors within the page in seevral different ways, and if none of them suits your needs you can implement your own validation errors renderer. Implementations of the Spring.Web.Validation.IValidationErrorsRenderer that ship with the framework are:
| Name |
Class |
Description |
| Block |
Spring.Web.Validation.DivValidationErrorsRenderer |
Renders validation errors as list items within a <div> tag. Default renderer for <spring:validationSummary> control. |
| Inline |
Spring.Web.Validation.SpanValidationErrorsRenderer |
Renders validation errors within a <span> tag. Default renderer for <spring:validationError> control. |
| Icon |
Spring.Web.Validation.IconValidationErrorsRenderer |
Renders validation errors as error icon, with error messages displayed in a tooltip. Best option when saving screen real estate is important. |
These three error renderers should be sufficient for most applications, but in case you want to display errors in some other way you can write your own renderer by implementing Spring.Web.Validation.IValidationErrorsRenderer interface:
namespace Spring.Web.Validation
{
public interface IValidationErrorsRenderer
{
void RenderErrors(Page page, HtmlTextWriter writer, IList errors);
}
}
Configuring Validation Errors Renderer to Use
The best part of the errors renderer mechanism is that you can easily change it accross the application by modifying configuration templates for <spring:validationSummary> and <spring:validationError> controls:
<object id="Spring.Web.Anthem.UI.Controls.ValidationError" abstract="true">
<property name="Renderer">
<object type="Spring.Web.Validation.IconValidationErrorsRenderer, Spring.Web">
<property name="IconSrc" value="validation-error.gif"/>
</object>
</property>
</object>
<object id="Spring.Web.Anthem.UI.Controls.ValidationSummary" abstract="true">
<property name="Renderer">
<object type="Spring.Web.Validation.DivValidationErrorsRenderer, Spring.Web">
<property name="CssClass" value="validationError"/>
</object>
</property>
</object>
It's as simple as that!
Conclusion
By now you should have a good understanding of all the features of the Spring.NET Validation Framework. We believe we have achieved all of the initial goals and then added even more value by adding things such as generic validation actions and validation error renderers. As always, feedback and suggestions for improvement are very much appreciated.
-Aleks