Geeks With Blogs
// ThomasWeller C#/.NET software development, software integrity, life as a freelancer, and all the rest

This series of posts discusses the use of an Aspect-oriented programming approach for implementing the validation part of a business domain, using the ValidationAspects aspect library, that sits on top of the PostSharp aspect weaver and allows for placing Design by Contract - style attributes on properties and method arguments, as well as for easy runtime validation of an object's state.

This is the third post of a three-part series that discusses the use of these validation aspects throughout the different layers of an application. Each part covers a separate layer as follows:

  • The first part showed in some detail how validation aspects can be applied to the domain layer's business objects both for property value/argument interception and object state validation. We developed a small sample domain there, that's used in the following parts.
  • The second part then looked at how these validations can be integrated into a persistence layer that's using NHibernate.
  • The third part now (this one) will use the validation aspects to perform (mostly client-sided) validation in an ASP.NET MVC GUI. The xVal framework will be used to bring the server-side validations to the web pages.

Where are we so far...?

In the first post, I introduced a Person class (our "domain"), which allows for validation of property values, method arguments, and internal object state. Property and  argument values are directly intercepted during invocation of the property setter or method, whereas state validation is done by calling Validate() on the object and then checking the return value. The below tests show the typical usage:

[Test]

public void PersonDoesNotValidate()

{

    var person = CreateInvalidPerson();

 

    ValidationResult result = person.Validate();

 

    Assert.IsFalse(result.IsValid,

                   "Object validation succeeded, but was expected to fail.");

}

 

[Test]

public void CannotSetATooHighAge()

{

    Person person = new Person();

    Assert.Throws<ValidationException>(() => person.Age = 1234);

}

The second post then showed how one can integrate this validation mechanism with NHibernate persistence to prevent invalid entities from being saved or updated, making the following test pass:

[Test]

public void CannotSaveInvalidPerson()

{

    using (ISession session = this.CreateSession())

    using (ITransaction tx = session.BeginTransaction())

    {

        var person = CreateValidPerson();

        person.Email = "abc-def@ghi.jkl"; // makes the person invalid...

 

        var exc =  Assert.Throws<HibernateException>(() => session.Save(person));

        Assert.IsInstanceOfType<ValidationException>(exc.InnerException);

    }

}

...and where do we want to go from there?

The goal of this third post now is to reuse the so-far-developed validation mechanism in an ASP.NET MVC GUI. To begin, I let Visual Studio create a PersonController and a Person-typed view (which can be seen below).

Wherever possible, we want to use client-side validation, avoiding server roundtrips. In the end, a web form with invalid input should result in this (client-side validation of user input):

ClientSideValidation

... or this (server-side validation, explicitly calling the Validate() method on the Person class):

ServerSideValidation

While making the business class' validation aspects play together with NHibernate required only a few lines of code, using them with ASP.NET MVC needs a bit more (but not too much) effort...

Translating validation aspects to client-side validations

To turn the (server-side) validation aspects of the Person class into client-side validations, I'm using the xVal validation framework, which is aimed to "link up your choice of server-side validation mechanism with your choice of client-side validation library, neatly fitting both into ASP.NET MVC architecture and conventions". In short, it lets you map any kind of server-side validation to any kind of client-side validation, as long as there is a suitable mapping between your server-side validations and the xVal-provided set of rules (which can be extended with custom, self-written ones, if necessary).

The component that does the mapping must implement the IRulesProvider interface. The RulesProvider for the built-in ValidationAspects looks like this:

public class ValidationAspectsRulesProvider : IRulesProvider

{

    public ValidationAspectsRulesProvider()

    {

        EmitBuiltInValidators();

    }

 

    private static void EmitBuiltInValidators()

    {

        Emit<Rule>.For<InRange>(x => new RangeRule((decimal)x.Minimum, (decimal)x.Maximum));

        Emit<Rule>.For<IsEmail>(x => new RegularExpressionRule(x.IsStrict ? IsEmail.StrictRegex.ToString()

                                                                   : IsEmail.LenientRegex.ToString()));

        Emit<Rule>.For<LengthInRange>(x => new StringLengthRule(x.Minimum, x.Maximum));

        Emit<Rule>.For<MatchesRegex>(x => new RegularExpressionRule(x.Regex));

        Emit<Rule>.For<Minimum>(x => new RangeRule((decimal)x.Value, null));

        Emit<Rule>.For<MinimumLength>(x => new StringLengthRule(x.Length, null));

        Emit<Rule>.For<Maximum>(x => new RangeRule(null, (decimal)x.Value));

        Emit<Rule>.For<MaximumLength>(x => new StringLengthRule(null, x.Length));

        Emit<Rule>.For<NotNull>(x => new RequiredRule());

        Emit<Rule>.For<NotNullOrEmpty>(x => new RequiredRule());

    }

 

    public RuleSet GetRulesFromType(Type type)

    {

        var rules = from propertyInfo in type.GetProperties()

                    where propertyInfo.CanValidate()

                    from rule in propertyInfo.EmitValidation<Rule>()

                    select new { propertyInfo.Name, rule };

 

        return new RuleSet(rules.ToLookup(x => x.Name, x => x.rule));

    }

 

} // class ValidationAspectsRulesProvider

(This is a slightly modified version of what I found in this post.) The ValidationAspectsRulesProvider is then added to the application in Global.asax:

protected void Application_Start()

{

    ...

 

    xVal.ActiveRuleProviders.Providers.Clear();

    xVal.ActiveRuleProviders.Providers.Add(new ValidationAspectsRulesProvider());

}

To finally set up xVal with jQuery Validation, you also have to perform these additional steps:

  • Put jquery.validate.js (downloadable here) and xVal.jquery.validate.js (comes with the  xVal download) into your .../Scripts folder.
  • Add the necessary references to your master page:

<head>

    <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery-1.3.2.js")%>"></script>

    <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery.validate.js")%>"></script>

    <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/xVal.jquery.validate.js")%>"></script>

</head>

  • Also, to import xVal’s HTML helpers, add the following line to your Web.config’s <namespaces> node:

<namespaces>

 

  <!-- leave rest unchanged... -->

  <add namespace="xVal.Html"/>

</namespaces>

  • Finally, add the following line to your view's .aspx:

<%= Html.ClientSideValidation<Person>() %>

That’s it. Now, jQuery Validation will enforce the validations that were originally applied to the properties of our Person class.

Of course, if the client has JavaScript turned off, he is still subject to server-side validation - or will run into an error in this (slightly naive) example.

Incorporating server-side (object state) validation

Supposed, that all the property validation is already done on the client side (which means that the form's input values can be bound to their relating class properties without error), we can have a method like this on the PersonController class:

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Edit(Person person)

{

    ...

Of course, this can fail somewhere in the depths of the MVC framework, if not all of the possibly false input values are intercepted before posting. So, if you cannot guarantee for that in a particular scenario, there must be a custom model binder or some form of presentation model to check for the validity of the property values on the server side...

But given that a Person instance can actually be created from the form's input values, our POST-method can be as simple as this:

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Edit(Person person)

{

    this.GetModelStateFromValidation(person);

 

    ViewData["Message"] = ModelState.IsValid

        ? "Validation was successful."

        : "Validation was unsuccessful. Please correct the errors and try again.";

 

    return View(person); // re-send (for demo...)

}

GetModelStateFromValidation() is an extension method on the Controller class. It validates a given object instance and then "translates" the validation results to ModelState errors, which are expected and automatically handled by the MVC infrastructure. The error key will be either the property name (if the error relates to a property) or the name of the validation method that caused the error. Here's the (slightly simplified) code:

public static class ControllerValidationExtension

{

    public static void GetModelStateFromValidation(this Controller controller, object objToValidate)

    {

        if (objToValidate == null)

        {

            throw new ArgumentNullException("objToValidate");

        }

 

        ValidationResult validationResult = objToValidate.Validate();

 

        if (!validationResult.IsValid)

        {

            AddModelErrors(controller.ModelState,

                           (ValidationContextException)validationResult.ValidationException);

        }

    }

 

    private static void AddModelErrors(ModelStateDictionary modelState, ValidationContextException contextException)

    {

        foreach (var exception in contextException.Flatten())

        {

            string errorKey = exception.Context.Property != null ? exception.Context.Property.Name

                                                                 : exception.Context.Method.Name;

            string errorMessage = exception.Messages.Aggregate((s1, s2) => s1 + " -- " + s2);

 

            modelState.AddModelError(errorKey, errorMessage);

        }

    }

 

} // class ControllerValidationExtension

The sample solution

To see all this in more detail (and running...), I provided a sample solution (VS 2008), which you can download here. It's another evolution of the sample code from the first and second part, to which I added an MVC application (ValidationDemo.UI) and a relating test project (ValidationDemo.UI.Test).

 

kickit
shoutit
delicious facebook digg reddit linkedin stumbleupon technorati mrwong yahoo google-48x48 twitter email favorites
Posted on Monday, November 23, 2009 4:47 AM Architecture and Design , ASP.NET (MVC) , Aspect-oriented programming | Back to top


Comments on this post: Validating business objects with AOP (3: UI with ASP.NET MVC + xVal)

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Thomas Weller | Powered by: GeeksWithBlogs.net | Join free