Flexible Conditional Validation with ASP.NET MVC – adding client-side support

I’ve worked with a number of customers that wanted to be able to do cross-field validation of their models along the lines of “if property x is set then property y is required”. Some customers approached this using IValidatableObject, and others created attributes such as RequiredIfXSet. The downsides with IValidatableObject include the lack of client-side validation support and difficulties with re-use of the validation logic. With RequiredIfXSet, the challenge becomes the proliferation of RequiredIfZSet etc attributes. Often this leads people to a more general purpose approach, and I showed how to create a flexible RequiredIf attribute in Part 1.

This post will briefly look at the process and key points for adding client-side support. I’ll skip over a lot of the implementation details so that the post doesn’t end up too long, but the link to the code is at the bottom of the article.

To recap, the previous post gave us the RequiredIf attribute which can be used as shown below:

    public class PendingRequestModel
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }

[StringLength(20)]
public string Title { get; set; }

[DataType(DataType.Date)]
public DateTime RequestedOn { get; set; }

[DataType(DataType.Date)]
public DateTime DueOn { get; set; }

public bool IsComplete { get; set; }

[DataType(DataType.MultilineText)]
[RequiredIf("IsComplete", true, ErrorMessage = "Must add comments if the item is complete")]         public string Comments { get; set; }
}

In this example, the condition is simply “IsComplete”, but it can be more complicated if desired, such as “IsComplete || DueOn < DateTime.Now”.

 

Steps for adding client-side validation

To add client-side validation with ASP.NET MVC (3 onwards)

  • Create the client-side validator and register it with the client-side validation framework
  • Implement IClientValidatable on your attribute and serve up the meta-data about the validation to be passed to the client-side framework

We’ll tackle those two parts in reverse order…

Generating the validation meta-data

Normally the metadata is fairly obvious and simple. For the StringLength validator it would be the maximum string length, for RegEx it is the regular expression. In the case of RequiredIf, we have a C# expression which isn’t likely to be much use in the browser. The approach I took for this post was to convert the C# expression into a JavaScript expression that would form part of the meta-data.

In the original code the DynamicQuery NuGet package is used to parse the expression string. This gives an expression tree which is dynamically compiled each time into a delegate represents the condition (read: perf hit that needs optimising!). For the client-side support we will take the expression tree that gives us rich data about the expression and then convert that into a JavaScript expression.

The basic approach is to write an Expression visitor by deriving from System.Linq.Expressions.ExpressionVisitor, which provides the base functionality to walk the expression tree. We can then override the various methods on the base class such as VisitBinary to provide the implementation

                protected override Expression VisitBinary(BinaryExpression node)
        {
            string operatorString = null;
            switch (node.NodeType)
            {
                case ExpressionType.AndAlso:
                    operatorString = "&&";
                    break;
                case ExpressionType.OrElse:
                    operatorString = "||";
                    break;
                // code omitted
            }
            Visit(node.Left);
            _buf.Append(" ");
            _buf.Append(operatorString);
            _buf.Append(" ");
            Visit(node.Right);
            return node;
        }

With the JavaScriptExpressionVisitor we can convert simple C# expressions to JavaScript, and we can now bundle that JavaScript expression as part of the meta-data that the client-side validator consumes (in the IClientValidatable.GetClientValidationRules implementation on RequiredIfAttribute).

Creating and wiring up the client-side validator

In the downloadable code (see link at the bottom), the client-side code is in requiredIf.js. To use this you need to ensure that you have referenced jQuery, jQuery.validate, jQuery.validate.unobtrusive and requiredIf (you’ll also need jQuery-ui if your expressions contain date values as I reused the date parsing – feel free to replace in your implementation)

There are two key pieces in requiredIf.js: creating the validator and wiring it up to the unobtrusive framework.

Creating the validator involves registering it with jQuery.validate, and this is done in the call to $.validator.addMethod(). This function receives a set of parameters, and these are specified when wiring the validator up to the unbobtrusive framework. This is the code that pulls out the meta-data that we specified in the IClientValidatable.GetClientValidationRules implementation server-side. It lives in the call to $.validator.unobtrusive.adapters.add().

Show me the code

The sample code for this article is hosted on code.msdn.microsoft.com at https://code.msdn.microsoft.com/Flexible-Conditional-37ae638e

NOTE: The code is not fully featured or production-ready. There is only sufficient implementation of the JavaScriptExpressionVisitor for the purposes of the blog post, there are areas that are known to be a perf hit (and probably unknown areas too), and there has been no real testing. :-)