Chapter 11. Validation Framework

11.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 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 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. 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 Spring.NET Validation Framework in your applications.

11.2. Example Usage

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 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 standard the 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 programmatically. The following example shows validation rules defined 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.

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

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

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

11.4.1. Condition Validator

The condition validator evaluates any logical expersion 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 (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 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.

11.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 11.2. Rules to determine if required value is valid

System.TypeTest
System.TypeType exists
System.Stringnot null or an empty string

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.

11.4.3. Regular Expression Validator

The syntax is

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

An example is shown below

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

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

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

[Note]Note

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.

11.5. Validator 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.

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

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

11.5.2. 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);
}

11.6. Validator References

Sometimes it is not possible (or desirable) 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.

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.

11.7. 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">
     // 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());
    }
}
[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:

<%@ 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>&nbsp;</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>

11.7.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 11.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 pluggin 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);
    }
}

11.7.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.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!