Integrating The Spring.NET Validation Framework And ASP .NET MVC

I recently completed a very successful project for a client using ASP .NET MVC, Spring.NET and jQuery. One of the technical requirements of the solution was that we wanted to have an advanced validation mechanism that would allow us to move all our validation rules out of theUI layer, and business logic, and into a dynamically configurable rule engine. Spring.NET’s validation framework is ideally suited for this challenging task.

Spring.NET’s validation framework is based on a hierarchy of validation groups, conditions, and actions. I refer those unfimiliiar with Spring.NET’s validation framework, to their well composed documentation.

What I’ll propose here is a way to integrate Spring.NET’s validation with ASP MVC, and some issues you might encounter alng the way.

Spring.NET Validation Is From Mars, and ASP MVC Is From Venus

Okay, so you’ve covered the validation framework’s documentation. You can create new validation rules, pass through an object (in the MVC space this would be the model), and get back a boolean result to tell you whether the object passed (true) or failed (false). As I’ve mentioned this process is made very clear in the help documentation of the validation framework, so I’m going to jump straight into the juicy complexities.

Spring.NET’s validation rules apply themselves directly to a POCO’s (Plain Old C# Object) schema. For instance, say I’ve got a class Customer that I pass through as the validationContext, and I wanted to validate his CellPhone ContactNumber (Customer.ContactNumbers.CellPhone), the validation rule might look something like this:

<v:regex when="ContactNumbers.CellPhone != null and ContactNumbers.CellPhone != ''"
test="ContactNumbers.CellPhone">

When we’re talking about MVC, our Customer will serve as the model sent to a ViewPage by a Controller action.

ASP’s Controller all have a ViewData.ModelState.AddModelError(…), that takes in a key, and an error message. To get MVC to highlight the input element, the key must match its name. To get MVC to automatically bind the HTML control to a model property, the name of the control in turn should be the same as the path to the Property that provides its value. For example, say I want ASP MVC to automatically display his CellPhone number, then I’d use “Customer.ContactNumbers.CellPhone” as the input control’s name. To flag this control as an error source, I’ll use a key with the exact same name.

<%= Html.TextBox( "Customer.ContactNumbers.HomeDialCode" )%>
-
<%= Html.TextBox( "Customer.ContactNumbers.HomePhone" )%>
<%= Html.ValidationMessage( "Customer.ContactNumbers.HomeDialCode" )%>
<%= Html.ValidationMessage( "Customer.ContactNumbers.HomePhone" )%>

The important point is that the naming convention of the ViewPage’s HTML controls, is based on the model’s schema or graph, and so are the validation rules. The trick is then to relate a Spring.NET validation error (and specifically the error message) to the current node in the schema to which it’s being applied.

Spring.NET’s message or ErrorMessageAction has the concept of “providers“. A provider is just a fancy name for an IDictionary<string,IList> of key-values, where the provider name serves as the key, and the list is a list of ErrorMessages. The idea is that each provider name relates to a Spring validation web server control, that will display it’s error. For ASP MVC we’d rather like to generate the correct ModelError key, so MVC can automatically take care of displaying the error message in its own way.

So at this point we have 3 tasks that we need to complete to successfully integrate ASP MVC and Spring.NET Validation:

1. Tell each Spring.NET validation rule where in the object graph it’s currently executing.
2. Generate the correct error key from the validation rule’s location or context.
3. Convert the validation error to a ModelError and add it to the Controller’s ModelState.

Validation Rules: “I Know What I’m Doing, But Not Where I’m Doing It!”

One of the problems of Spring.NET’s validation rules are that they do not know where in the schema they’re executing. Put a another way, they do not know the path that lead to the current rule being executed – henceforth being referred to as their context. Why is this important? It is important because, as explained previously, the MVC’s input field names and error keys are based on the full path of underlying model-object property.

The most straightforward way to provide this contextual information for a validation rule is to embed it directly inside its definition, probably using a custom IValidationAction:

<v:regex id="homeDialCodeValidator" when="HomeDialCode != null and HomeDialCode != ''" test="HomeDialCode">
<v:property name="Expression" value="&#91;0-9&#93;{3}"/>
  <v:action type="Shinobido.GhostBlade.Validation.ModelValidationAction, Shinobido.GhostBlade.Core">
    <v:property name="CurrentError">
      <object id="homeDialCodeError" type="Shinobido.GhostBlade.Validation.ModelError, Shinobido.GhostBlade">
<property name="Key" value="Customer.ContactNumbers.HomeDialCode" />
<property name="Message" value="3 numbers expected." />
      </object>
    </v:property>
  </v:action>
</v:regex>

The problem with the aforementioned approach is that it reduces the re-usability of validation rules. For instance, lets say we have the following classes:

public class ContactNumbers
{
    public string HomeDialCode { get; set; }
    public string HomePhone { get; set; }
}

public class Customer
{
    public string Name { get; set; }
    public ContactNumbers ContactNumbers { get; set; }
    public Spouse Spouse { get; set; }
    public NextOfKin NextOfKin { get; set; }
}

public class Spouse
{
    public ContactNumbers ContactNumbers { get; set; }
}

public class NextOfKin
{
    public ContactNumbers ContactNumbers { get; set; }
}

Ideally we want to create one contactNumbersValidator rule-group, that can be referenced from other validators, wherever ContactNumbers need to be validated:

<v:ref name="contactNumbersValidator" context="Spouse.ContactNumbers" />

The problem with specifying the context information or error key, in the rule, as that it only caters for a single context. For instance, in the above example we would like to validate ContactNumbers from three different contexts: Customer.ContactNumbers, Customer.Spouse.ContactNumbers, and Customer.NextOfKin.ContactNumbers. All three will use the same validation rule, but from different contexts or schema paths. If we specify a context of Customer.ContactNumbers.HomeDialCode, then we are unable to accommodate any other path, such as those for NextOfKin and Spouse.

What we can infer from this exercise is that we need to specify a rule’s context outside of the rule, in a parent group or rule. A validation rule itself cannot contain the context, because it does not know from where it’s referenced. So how do we set the rule’s current context, from its parent?

One of the ValidatorGroup‘s overloads for the Validate(…) method, has an input argument called contextParams of type IDictionary. The significance of this argument is that we can write and read values to it – programmatically and from the config file. This allows us to make variables available to validation groups, validators or actions.

So how do we set a variable from the rule configuration? For this we use the “#variableName” syntax of Spring.NET’s expression language. When Spring.NET encounters the following command “#SomeVariable=’SomeValue‘” a new entry is made in the contextParams dictionary with a key name of “SomeVariable” and value of “SomeValue”.

What we’re going to do is write a magic little custom class that allow us to navigate up and down the object graph, as validation rules are evaluated:

public class ObjectContext
{
    private IList<string> contextPath = new List<string>();
    private string finalContext;
    private readonly StringBuilder pathBuilder = new StringBuilder();

    public bool StepIn( params string[] path )
    {
        for ( var i = 0; path != null && i < path.Length; ++i )
        {
            contextPath.Add( path&#91;i&#93; );
        }
        return true;
     }

    public bool FinalStepIn( params string&#91;&#93; path )
    {
        for ( var i = 0; path != null && i < path.Length; ++i )
        {
            if ( i == path.Length - 1 )
            {
                finalContext = path&#91;i&#93;;
            }
            else
            {
                contextPath.Add( path&#91;i&#93; );
            }
        }
        return true;
    }

    public bool StepOut( params string&#91;&#93; path )
    {
        // Reverse list so we can start deleting from index 0
        var reversedContextPath = contextPath.Reverse().ToList();
        for ( var i = 0; i < reversedContextPath.Count(); ++i )
        {
            // Just remove the same number of items in "path"
            // from "contextPath"
            reversedContextPath.RemoveRange( 0, path.Length );
        }

        // Revert back to original order and assign back to "contextPath"
        reversedContextPath.Reverse();
        contextPath = reversedContextPath;

        return true;
    }

    public string ContextPath
    {
        get
        {
            if ( pathBuilder.Length > 0 ) pathBuilder.Remove( 0, pathBuilder.Length );

            for ( var i = 0; i < contextPath.Count; ++i )
            {
                pathBuilder.Append( contextPath&#91;i&#93; );

                // If don't have a leaf node, and this is the last
                // intermediate/branch node, then do not add a "."
                if ( string.IsNullOrEmpty( finalContext ) &&
                     i == contextPath.Count - 1 )
                {
                    continue;
                }

                pathBuilder.Append( "." );
            }
            pathBuilder.Append( finalContext );
            return pathBuilder.ToString();
        }
     }
 }

&#91;/sourcecode&#93;

Let's first explain some of the <strong>logic behind ObjectContext</strong>. When it comes to object schemas or graphs we can distinguish between three types of contexts: <strong>the root context</strong>, <strong>several intermediate sub contexts</strong>, and a <strong>final leaf context</strong> (containing the "test" expression). The <strong>root context </strong><em>serves as the starting point for the evaluation path</em>, and is similar to MVC's concept of a binding prefix. <strong>Sub contexts</strong> <em>refer to those that navigate from the root down to a final leaf context</em>. A <strong>leaf context</strong> <em>is a validation rule end-point, that does not have any children, and contains the actual "test" expression</em>.

<strong>ObjectContext </strong>has three methods <strong>StepIn</strong>, <strong>FinalStepIn</strong>, and <strong>StepOut</strong>. <strong>StepIn </strong><em>allows us to step into a child context</em>, like when we move from Customer into Spouse. <strong>FinalStepIn </strong><em>is used when we move from a parent context into a final leaf context</em>, like we'd do if we moved from ContactNumbers to HomePhone. Lastly we have <strong>StepOut</strong>, <em>that takes us from a child context back up to a parent one</em>, such as when we move back from Spouse to Customer.

All we need to do now, is <strong>add a new instance of ObjectContext to contextParams</strong> and call the relevant methods as we move through the object graph's nodes. I wrote a <strong>ValidatorGroup extension</strong> method to automatically add a new ObjectContext instance to contextParams:


public static class ValidatorGroupExtensions
{
    public static bool Validate( this ValidatorGroup validatorGroup, object validationContext, IValidationErrors errors, bool hasContext )
    {
        var contextParams = hasContext ? new Dictionary<string,object> { { "ObjectContext", new ObjectContext() } } : new Dictionary<string,object>();
        return validatorGroup.Validate( validationContext, contextParams, errors );
    }
}

At this point it should be clear how to add a new instance of ObjectContext to contextParams. What we haven’t discussed is how we’re going to retrieve our ObjectContext instance from contextParams, and invoke one of its methods in the validation configuration. The answer to this question lies in the fact that Spring.NET’s IExpression interface allows multiple independent commands to be executed in the same expression. What’s also important is that we realize that the validation configuration schema’s “when” and “test” attributes are actually IExpression properties. Therefore we can use any of the validation framework’s attributes/properties that is an IExpression to use variables from contextParams:

<v:group id="customerValidator" when="#ObjectContext.StepIn( 'Customer' )">

    <v:condition when="#ObjectContext.FinalStepIn( 'Name' )" test="Name != null and Name != ''" />

    <v:group when="( #ObjectContext.StepIn( 'Spouse', 'ContactNumbers' ); Employer != null )">
        <v:ref name="contactNumbersValidator" context="Spouse.ContactNumbers" />
    </v:group>

    <v:group when="( #ObjectContext.StepOut( 'Spouse', 'ContactNumbers' ); #ObjectContext.StepIn( 'ContactNumbers' ) )">
        <v:ref name="contactNumbersValidator" context="ContactNumbers" />
    </v:group>
</v:group>

<!-- contactNumbersValidator -->
<v:group id="contactNumbersValidator">

    <!-- CellPhone validator -->
    <v:regex when="( #ObjectContext.FinalStepIn( 'CellPhone' ); CellPhone != null and CellPhone != '' )" test="CellPhone">
        <v:property name="Expression" value="&#91;0-9&#93;{10}"/>
        <v:action type="Shinobido.GhostBlade.ErrorMessageContextAction, Shinobido.GhostBlade">
            <v:property name="Message" value="Unexpected format for cellphone number." />
        </v:action>
    </v:regex>

</v:group>

The first “when” validation statement on the group element tells Spring.NET to fetch the value of the DictionaryEntry with key “ObjectContext”. To execute multiple commands in a single IExpression we place the entire expression in brackets, and separate the each sub-expression with a semi-colon. When multiple statements are specified like this, Spring.NET always returns the last statement’s value. This is why our navigation methods all return true: So that the groups are executed by default, relieving us from the burden to add a last additional expression that returns true.

The next step is to create a custom IValidationAction that inherits from BaseValidationAction, that will store our validation rule’s error message, and build the current context’s full path info:

public class ErrorMessageContextAction: BaseValidationAction
{
    /// <summary>
    /// Gets or sets the error message.
    /// </summary>
    public string Message { get; set; }

    protected override void OnInvalid( object validationContext, IDictionary contextParams, IValidationErrors errors )
    {
        var objContext = contextParams["ObjectContext"] as ObjectContext;

        if ( objContext == null ) return;

        var error = new ErrorMessage( null, Message );
        errors.AddError( objContext.ContextPath, error );
     }
 }

BaseValidationAction provides two virtual methods OnInvalid and OnValid, that are called when a rule fails or passes. ErrorMessageContextAction has one property called Message, that is set to the actual error message we wish to display. Our override of OnInvalid uses ObjectContext.ContextPath to build the full path of the current context. This full path will be used as our ‘provider’. ASP MVC’s built-in error message handling magic fulfills the role of validation errors renderer using this ‘provider’.

The last piece of the puzzle is a custom IValidationErrors implementation that will add validation errors to a MVC Controller’s ViewData.ModelState:

public class ModelStateErrors: IValidationErrors
{
    ModelStateDictionary modelState;

    public ModelStateErrors( ModelStateDictionary modelState )
    {
        this.modelState = modelState;
    }

    public void AddError( string provider, ErrorMessage message )
    {
        if ( message != null &&
             message.Parameters != null &&
             message.Parameters.Length > 0 &&
             message.Parameters[0] != null &&
             message.Parameters[0] is string )
        {
            modelState.AddModelError( provider, message.Parameters[0].ToString() );
        }
    }

    public void MergeErrors( ValidationErrors errorsToMerge )
    {
        throw new System.NotImplementedException();
    }

    public IList GetErrors( string provider )
    {
        var modelStateDict = modelState[provider];
        return modelStateDict == null ? null : modelStateDict.Errors;
    }

    public IList GetResolvedErrors(string provider, IMessageSource messageSource)
    {
        throw new System.NotImplementedException();
    }

    public bool IsEmpty
    {
        get { return modelState.Count == 0; }
    }

    public IList Providers
    {
        get
        {
            return modelState.Keys.ToList();
        }
    }
}

Class ModelStateErrors internally wraps a ViewData‘s ModelStateDictionary, and automatically adds Spring.NET validation errors to it. The provider name is used as the key for AddModelError, because this is the ‘bucket’ that we’d like to sink the message to. The ErrorMessage‘s first parameter is expected to be the message text.

Usage and Conclusion

An important point to remember when specifying the context in Spring’s validation configuration, is to always provide a complete path from the current node to a final leaf node. It is important to StepIn and StepOut of contexts in-sync with the validation rules. If a context is skipped, our we don’t StepOut of a previous one, we will not get the correct path. When you get the wrong context, check that you’ve SteppedOut as many times as you’ve SteppedIn, to bring you back to the current node or level. Also check that you’ve specified the correct context names when you StepInto a context.

All we need to do is make sure our context navigation correctly follows our validation rules. Apart from this we don’t really need to do anything else to get Spring.NET to add its validation errors and messages to ViewData’s ModelState:

1. Import the namespace where the ValidatorGroupExtensions is defined:

using Shinobido.GhostBlade.Extensions;

2. Get the desired ValidatorGroup from Spring.NET’s objects configuration.
3. Call ValidatorGroup.Validate with ModelStateErrors as the IValidationErrors implementation, and hasContext as true.

Just obtain the desired ValidatorGroup from the objects configuration, ModelStateErrors as the IValidationErrors implementation in a Controller’s Action method:

public partial class CustomerController: Controller
{
    public ActionResult EditPersonalAssessment( int id )
    {
        var customer = ObjectManager.New<ICustomer>();
        customer.ContactNumbers = new ContactNumbers
                                  {
                                      CellPhone = "djsk"
                                  };
        customer.Spouse = new Spouse
                          {
                              ContactNumbers = new ContactNumbers
                                               {
                                                   CellPhone = "kasjd"
                                               }
                          };
        var validator = ObjectManager.New<ValidatorGroup>( "customerValidator" );
        var result = validator.Validate( sampleObj, new ModelStateErrors( ViewData.ModelState ), true );
    }
}

From the final usage we observe that it’s a pretty neat way of integrating Spring.NET’s Validation Framework with ASP .NET MVC’s ModelState and error handling magic. The only part that’s a bit weird is the way we specify the context in Spring.NET’s validation configuration. It abuses the IExpression attributes a little in a way that they weren’t originally designed for. But I believe this is a small price to pay to gain the huge benefit of automatic integration of Spring.NET’s Validation Framework and ASP MVC’s validation and error handling and display.

In the next Spring.NET post I’ll be covering how to use Spring.NET’s Aspect Oriented Programming (AOP) to create an impersonation aspect. Happy Springing until then!

Advertisements

13 Comments on “Integrating The Spring.NET Validation Framework And ASP .NET MVC”

  1. Scott White says:

    Nice work. Spring.Net is an excellent framework, I’ve seen it work in everything from financial systems to gas scheduling & nominations.

  2. openlandscape says:

    @Scott. Thanks. Spring.NET is a truly wonderful framework. There is almost no scenario that it can’t add value to. And what I love about it is that it’s really easy to use. Over the past year I’ve used Spring.NET extensively for several projects at a division of one of South Africa’s biggest banks, for: web services, dependency injection, data access, ORM, AOP, logging, caching, validation, expression evaluation, etc. All with great success.

  3. Set says:

    Thanks, superb article, I am looking for some code that permits me to map a business object to different validator startegies. Could you email me a sample code solution. Thanks in advance.

    • openlandscape says:

      @Set

      > Thanks, superb article
      Thanks.

      > I am looking for some code that permits me to map a business object to different validator startegies.

      I am not sure what you mean by “validator strategies”. If you want me to help you, you’ll have to more specific than this.

  4. Brendan Vogt says:

    Hi,

    At what bank do you work? I also work for one of the big banks in SA.

    I am in need of help and direction regarding the Spring.NET’s validation framework. The documentation said that it was designed to be used in different application layers. Now I can setup the validation rules for the web page, but how do I use these exact rules for my business objects. Surely it is wise to valid the properties of each class?

    Can you mail me please so that we can take this discussion further?

    Brendan

  5. openlandscape says:

    Brendan,

    In your controller or page class, get an instance of your business/model object submitted from the page, and pass it to the Spring.NET validation framework.

    Before the post-back/action/event completes check the result from the validation framework. If the object successfully passed, then continue. If the object failed, then stay on the same page from where the object was submitted & display an error message.

    Remember you need to setup the rules for the business object, not the ASP .NET Page. You can then call the validation configuration from any library that references Spring.NET.

    Feel free to post/e-mail some code: Your Spring.NET configuration & your ASP Page or Controller & View.

    > it was designed to be used in different application layers

    This is indeed true. It can be used from anywhere in your code.

    > Surely it is wise to valid the properties of each class?

    It is wise to validate the properties of a class that you’d like to validate.

    > Can you mail me please so that we can take this discussion further?

    I have e-mailed you, but let’s dicuss the problem & solution here, so the other readers can also benefit from it.

  6. Brendan says:

    Thanks. I don’t want the business object properties to be validated on the client side. I want them validated in the business object self. The reason being what if I use the business object in a web service? Then I have to recode all the validation. Am I thinking correctly here? .NET Tiers validates business objects in the business object itself, at the property level. Do Factory validates the business object properties in the constructor of the object.

    Am I thinking correctly here?

  7. openlandscape says:

    Brendan,

    This is not a problem. Say you have a business object called Product with a method called Validate() that validates whether Product is correct. Inside the method get an instance of the ValidatorGroup from the ContextRegistry.GetContext().GetObject(“validationRuleGroupID”). Then call Validate on the ValidatorGroup object, passing “this” (the current Product instance itself) through as the object you’d like to validate.

    You can instantiate ValidatorGroups from anywhere in your code.Whether its from your business logic code, or UI. It really doesn’t matter. You do it in exactly the same way, whether it’s a UI class or some business object behind a web service. The only thing that’s different is the location of your Spring.NET configuration file.

  8. […] to Vote[Del.icio.us] Integrating The Spring.NET Validation Framework And ASP .NET MVC « OpenLandscape (7/1…Monday, July 12, 2010 from […]

  9. Mabdeen says:

    please i want to see demo of how to apply validation framework ob business object, i go with your tutorial but i still don’t have working one can you help me please

    • openlandscape says:

      Mabdeen,

      At this stage I do not have a working copy or demo. But maybe you can explain what is going wrong & I might be able to assist. Let me know.

  10. shyam says:

    Hey,
    Am new to Spring.net.
    I just started with making small application or i can say a small example that just illustrates the use of spring.
    but still i am not getting the exact idea, how to use it.
    can you help me with this??
    how do i start learning Spring.net?

    Thanks in Advance.

    • openlandscape says:

      Hi,

      I recommend you do the following:
      1. Read the Spring.NET documentation that explains the core functionality. I cannot stress this enough. Spring.NET’s documentation is probably one of its biggest advantages. Always start here. Even when you start using the additional features of Spring.NET, like the data access DI.
      2. Try IoC with a lighter framework, such as TinyIoC or Funq. Spring.NET has a ton of additional functionality that ties in with the core DI, which can confuse new users. The aforementioned 2 frameworks only do the basics, and leave the rest up to you.
      3. Install Spring.NET and go through the examples. Maybe take a copy of one of the examples, and mold it into your own Hello World app.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s