Chapter 12. Validation Framework

12.1. Introduction

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 the out of the box 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 the 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 the Spring.NET validation framework differently for the reasons described below.

On the Windows Forms side the situation is even worse. Out of the box 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. The 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. As such, the Spring.NET Validation Framework is designed in a way that enables data validation in different application layers using the same validation rules.

The goals of the validation framework are the following:

  1. Allow for the validation of any object, whether it is a UI control or a domain object.

  2. 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).

  3. Allow composition of the validation rules so arbitrarily complex validation rule sets can be constructed.

  4. Allow validators to be conditional so they only execute if a specific condition is met.

The following sections will describe in more detail how these goals were achieved and show you how to use the Spring.NET Validation Framework in your applications.

12.2. Example Usage

Decoupling validation from presentation was the major goal that significantly influenced design of the validation framework. 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 taken was to configure validation rules just like any other object managed by Spring - within the application context. However, due to possible complexity of the validation rules we decided not to use the standard Spring.NET configuration schema for validator definitions but to instead provide a more specific and easier to use custom configuration schema for validation. Note that the validation framework is not tied to the use of XML, you can use its API Programatically. The following example shows validation rules defined for the Trip object in the 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 the validation schema by adding a 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.

  • The Validator defined in the configuration file is identified by and id attribute and can be referenced in the standard Spring way, i.e. the injection of tripValidator into TripForm.aspx page definition in the first <object> tag above.

  • The validation framework uses Spring's powerful expression evaluation engine to evaluate both validation rules and applicability conditions for the validator. As such, any valid Spring expression can be specified within the 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 in the following sections.

12.3. Validator Groups

Validators can be grouped together. This is important for many reasons but the most typical usage scenario is to 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. There is also 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:

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.

Table 12.1. Validator Groups
TypeXML TagBehavior
ANDgroupReturns true only if all contained validators return true. This is the most commonly used validator group.
ORanyReturns true if one or more of the contained validators return true.
XORexclusiveReturns true if only one of the contained validators return true.

One thing to remember is that a 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.

12.4. 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. The framework is extensible so you can write your own custom validators and use them in the same way as the built-in ones.

12.4.1. Condition Validator

The condition validator evaluates any logical expression that is supported by Spring's evaluation engine. The syntax is

<v:condition id="id" test="testCondition" when="applicabilityCondition" parent="parentValidator">
  actions
</v:condition>

An example is shown below

<v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue">
   <v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/>
</v:condition>

In this example the StartingFrom property of the Trip object is compared to see if it is later than the current date, i.e. DateTime but only when the date has been set (the initial value of StartingFrom.Date was set to DateTime.MinValue).

The 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 the 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.

[Note]Note

Keep in mind that Spring.NET Validation Framework typically works with domain objects. This is after data binding from the controls has been performed so that the object being validated is strongly typed. This means that you can easily compare numbers and dates without having to worry if the string representation is comparable.

12.4.2. Required Validator

This validator ensures that the specified test value is not empty. The syntax is

<v:required id="id" test="requiredValue" when="applicabilityCondition" parent="parentValidator">
  actions
</v:required>

An example is shown below

<v:required test="ReturningFrom.AirportCode">
   <v:message id="error.destinationAirport.required" providers="destinationAirportErrors, validationSummary"/>
</v:required>

The specific tests done to determine if the required value is set is listed below

Table 12.2. Rules to determine if required value is valid
System.TypeTest
System.TypeType exists
System.Stringnot null or an empty string
system.DateTime
Not System.DateTime.MinValue and not system.DateTime.MaxValue
One of the number types.
not zero
System.Char
Not System.Char.MinValue or whitespace.
Any reference type other than System.Stringnot null

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.

The 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.

12.4.3. Regular Expression Validator

The syntax is

<v:regex id="id" test="valueToEvaluate" expression="regularExpressionToMatch" when="applicabilityCondition" parent="parentValidator">
  <v:property name="Options" value="regexOptions"/>
  actions
</v:regex>

An example is shown below

<v:regex test="ReturningFrom.AirportCode" expression="[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.

[Note]Note

Note that current behavior limits the Regular Expression Validator to expressions to being full matches, i.e., ^(expression)$, thus limiting functionality. To not change this behavior in a point release, a property AllowPartialMatching has been added in 1.3.1 to support the correct behavior.

This property will be removed for next major/minor version and implementation will be fixed to get the intented behavior.

12.4.4. Generic Validator

The syntax is

<v:validator id="id" test="requiredValue" when="applicabilityCondition" type="validatorType" parent="parentValidator">
  actions
</v:validator>

An example is shown below

<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

12.4.5. 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 the when attribute. This feature is very useful and is one of the major deficiencies 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 a Trip object we need to validate return date only if the Trip.Mode property is set to the 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.

[Note]Note

You should also note that you can compare enums using the string value of the enumeration. 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.

12.5. Validator Actions

Validation actions are executed every time the 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 the validation action is to add validation error message to the errors collection, but theoretically you could do anything you want. Because adding validation error messages to the errors collection is such a common scenario, Spring.NET validation schema defines a separate XML tag for this type of validation action.

12.5.1. Error Message Action

The syntax is

<v:message id="messageId" providers="errorProviderList" when="messageApplicabilityCondition">
  <v:param value="paramExpression"/>
</v:message>

An example is shown below

<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:

  • id is used to look up the error message in the appropriate Spring.NET message source.

  • providers specifies a 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.

  • a 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 the fully resolved message.

12.5.2. Exception Action

If you would like an exception to be thrown when validation fails use the exception action.

<v:exception/>

This will throw an exception of the type ValidationException and you can access error information via its ValidationErrors property. To throw your own custom exception, provide a SpEL fragment that instantiates the custom exception.

<v:exception throw='new System.InvalidOperationException("invalid")'/>

12.5.3. Generic Actions

The syntax is

<v:action type="actionType" when="actionApplicabilityCondition">
  properties
</v:action>

An example is shown below

<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
{
    /// <summary>
    /// Executes the action.
    /// </summary>
    /// <param name="isValid">Whether associated validator is valid or not.</param>
    /// <param name="validationContext">Validation context.</param>
    /// <param name="contextParams">Additional context parameters.</param>
    /// <param name="errors">Validation errors container.</param>
    void Execute(bool isValid, object validationContext, IDictionary contextParams, ValidationErrors errors);
}

12.6. Validator References

Sometimes it is not possible (or desirable) to nest all the validation rules within a single top-level validator group. For example, if you have an 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.

The syntax is shown below

<v:ref name="referencedValidatorId" context="validationContextForTheReferencedValidator"/>

An example is shown below

<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, typically 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 receive as the validation context.

12.7. Progammatic usage

You can also create Validators programmatically using the API. An example is shown below

UserInfo userInfo = new UserInfo();  // has Name and Password props

ValidatorGroup userInfoValidator = new ValidatorGroup();

userInfoValidator.Validators
                 .Add(new RequiredValidator("Name", null));

userInfoValidator.Validators
                 .Add(new RequiredValidator("Password", null));

ValidationErrors errors = new ValidationErrors();
bool userInfoIsValid = userInfoValidator.Validate(userInfo, errors);

No matter if you create your validators programmatically or declaratively, you can invoke them in service side code via the 'Validate' method shown above and then handle error conditions. Spring provides AOP parameter validation advice as part of ithe aspect library which may also be useful for performing server-side validation.

12.8. Usage tips within 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">
     <v:required id="departureAirportValidator" test="StartingFrom.AirportCode">
        <!-- write error message to 2 providers -->       
        <v:message id="error.departureAirport.required" providers="departureAirportErrors, errorSummary"/>
     </v:required>

     <v:group id="destinationAirportValidator">
        <v:required test="ReturningFrom.AirportCode">
           <!-- write error message to 2 providers -->       
           <v:message id="error.destinationAirport.required" providers="destinationAirportErrors, errorSummary"/>
        </v:required>
     </v:group>
  </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());
    }
}
[Note]Note

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:

<!-- code snippet taken from the SpringAir sample application -->
<%@ Page Language="c#" MasterPageFile="~/Web/StandardTemplate.master" Inherits="TripForm" CodeFile="TripForm.aspx.cs" %>
        
        <!-- render all error messages sent to 'errorSummary' provider -->
        <spring:ValidationSummary ID="summary" Provider="errorSummary" runat="server" />
        <table>
            <tr>
                <td>
                    <asp:Label ID="leavingFrom" runat="server" /></td>
                <td>
                    <asp:DropDownList ID="leavingFromAirportCode" AutoCallBack="true" runat="server" />
                    <!-- render error messages sent to 'departureAirportErrors' provider -->
                    <spring:ValidationError ID="leavingFromError" Provider="departureAirportErrors" runat="server" />
                </td>
                <td>
                    <asp:Label ID="goingTo" runat="server" /></td>
                <td>
                    <asp:DropDownList ID="goingToAirportCode" AutoCallBack="true" runat="server" />
                    <!-- render error messages sent to 'destinationAirportErrors' provider -->
                    <spring:ValidationError ID="goingToError" Provider="destinationAirportErrors" runat="server" />
                </td>
            </tr>
        </table>

12.8.1. Rendering Validation Errors

Spring.NET allows you to render validation errors within the page in several 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:

Table 12.3. Validation Renderers
NameClassDescription
BlockSpring.Web.Validation.DivValidationErrorsRenderer Renders validation errors as list items within a <div> tag. Default renderer for <spring:validationSummary> control.
InlineSpring.Web.Validation.SpanValidationErrorsRenderer Renders validation errors within a <span> tag. Default renderer for <spring:validationError> control.
IconSpring.Web.Validation.IconValidationErrorsRendererRenders 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
{
    /// <summary>
    /// This interface should be implemented by all validation errors renderers.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Validation errors renderers are used to decouple rendering behavior from the
    /// validation errors controls such as <see cref="ValidationError"/> and
    /// <see cref="ValidationSummary"/>.
    /// </para>
    /// <para>
    /// This allows users to change how validation errors are rendered by simply plugging in
    /// appropriate renderer implementation into the validation errors controls using
    /// Spring.NET dependency injection.
    /// </para>
    /// </remarks>
    public interface IValidationErrorsRenderer
    {
        /// <summary>
        /// Renders validation errors using specified <see cref="HtmlTextWriter"/>.
        /// </summary>
        /// <param name="page">Web form instance.</param>
        /// <param name="writer">An HTML writer to use.</param>
        /// <param name="errors">The list of validation errors.</param>
        void RenderErrors(Page page, HtmlTextWriter writer, IList errors);
    }
}

12.8.1.1. Configuring which Error Renderer to use.

The best part of the errors renderer mechanism is that you can easily change it across the application by modifying configuration templates for <spring:validationSummary> and <spring:validationError> controls:

<!-- Validation errors renderer configuration -->
<object id="Spring.Web.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.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!

12.8.2. How Validate() and Validation Controls play together

Validation Controls (ValidationSummary, ValidationError) need to somehow get the list of errors collected during validation. Both, Spring.Web.UI.Page and Spring.Web.UI.UserControl come with a ValidationErrors property and implement IValidationContainer. ValidationControls will automatically pick the IValidationContainer control they are placed on:

// ASPX / ASCX Template Code
<%@ Control Language="c#"%>
        
        <!-- render all error messages sent to 'errorSummary' provider -->
        <spring:ValidationSummary ID="summary" Provider="errorSummary" runat="server" />

        <asp:DropDownList ID="leavingFromAirportCode" AutoCallBack="true" runat="server" />
        <!-- render error messages sent to 'departureAirportErrors' provider -->
        <spring:ValidationError ID="leavingFromError" Provider="departureAirportErrors" runat="server" />


<script language="C#" runat="server">
public void SearchForFlights(object sender, EventArgs e)
{
    if (Validate(Controller.Trip, tripValidator))
    {
        Process.SetView(Controller.SearchForFlights());
    }
}
</script>

If you need to render errors from a UserControl not in the hierarchy of your Validation control, you can specify the name of the target validation container control:

// ASPX / ASCX Template Code
<%@ Page Language="c#" %>
<%@ Register TagPrefix="user" TagName="EmployeeInfoEditor" Src="EmployeeInfoEditor.ascx" %>
        
    <spring:ValidationSummary ID="summary" Provider="summary" ValidationContainerName="editor" runat="server" />
    <user:EmployeeInfoEditor ID="editor" runat="server" />