Posts
208
Comments
1144
Trackbacks
51
Enterprise Library Validation Application Block with MVC Binders

A while back, I blogged about using the Enterprise Library Validation Application Block (VAB) with ASP.NET MVC. As MVC has matured as a framework, this scenario has becoming simpler.  In early releases of MVC, I implemented the execution of the VAB validation in the controller methods.  However, I now prefer to put that logic in the binders themselves.  In earlier versions of the framework, the model binders that came out of the box dealt well with simple objects but if you had more complex View Models (as described in this post) then you had to roll your own binder.  With the latest releases of MVC, the DefaultModelBinder that comes OOTB with MVC is now quite robust and is even capable of dealing with those more complex binding scenarios.  Hence, my preferred method for performing the valiation is best described in this post by David Hayden here.

However, the one issue with that method is that, although the binder deals well with the complex object, you’ll still run into the same issue with the VAB when it comes to the Key property of the validation messages.  That is, the key is the name of the business object property itself which does not always match the property of the view model.  For example, let’s revisit the example from my previous post and update it to use this new method.  We have our Model passed to our view defined as:

   1:  public class ContactViewData
   2:  {
   3:      public Contact Contact { get; set; }
   4:      public IEnumerable<State> StateList { get; set; }
   5:  }

We bind to our textbox like this:

   1:  <%=Html.TextBox("Contact.FirstName")%>

Or, if you prefer the MVC Futures approach, like this:

   1:  <%=Html.TextBoxFor(m => m.Contact.FirstName) %>

So our mismatch is that when the FirstName is invalid, the key for the validation result will be “FirstName” but we were binding to “Contact.FirstName”.  You have two basic options to tackle this type of situation:

Option 1 – Prepend appropriate prefix with your binder

This is basically a re-write of my original method by utilizing a custom model binder that derives from the DefaultModelBinder.  This is also a hybrid of Hayden’s approach:

   1:  public class ContactModelBinder : DefaultModelBinder
   2:  {
   3:      protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
   4:      {
   5:          var validator = ValidationFactory.CreateValidator(bindingContext.ModelType);
   6:          var validationResults = validator.Validate(bindingContext.Model);
   7:   
   8:          foreach (var result in validationResults)
   9:          {
  10:              string mvcKey = GetMvcKey(result);
  11:              bindingContext.ModelState.AddModelError(mvcKey, result.Message);
  12:          }
  13:      }
  14:   
  15:      static Dictionary<Type, string> propertyPrefixMap = new Dictionary<Type, string>()
  16:      {
  17:          { typeof(Contact), "Contact" },
  18:          { typeof(Address), "Contact.Address" }
  19:      };
  20:   
  21:      /// <summary>
  22:      /// Converts an Enterprise Library ValidationResult into the correct "key" to be used by MVC Views.
  23:      /// </summary>
  24:      /// <param name="validationResult"></param>
  25:      /// <returns></returns>
  26:      static string GetMvcKey(ValidationResult validationResult)
  27:      {
  28:          string result;
  29:          propertyPrefixMap.TryGetValue(validationResult.Target.GetType(), out result);
  30:   
  31:          if (string.IsNullOrEmpty(result))
  32:          {
  33:              return validationResult.Key;
  34:          }
  35:          else
  36:          {
  37:              return result + "." + validationResult.Key;
  38:          }
  39:      }
  40:  }

This upside is that you enable you model and validations to match precisely.  The downside is that this model binder does not have a lot of re-use if you’re not using this model in multiple views.

 

Option 2 – Set your Html.ValidationMessage() differently

In this typical scenario, you’d set up your view like this:

   1:  <%=Html.TextBox("Contact.FirstName")%>
   2:  <%=Html.ValidationMessage("Contact.FirstName") %>

Of course, this leads to the issue described above. As an alternative, you could use different name parameters like this:

   1:  <%=Html.TextBox("Contact.FirstName")%>
   2:  <%=Html.ValidationMessage("FirstName") %>

The upside is that you could leverage a more re-usable binder as described in Hayden’s post.  The downside is that it seems a little counter-intuitive to be using different parameters on the TextBox() and ValiationMessage() extension methods to represent the same business object property.  Perhaps this mis-match “feels” a little better with the MvcFutures syntax:

   1:  <%=Html.TextBoxFor(m => m.Contact.FirstName) %>
   2:  <%=Html.ValidationMessage("FirstName") %>

 

Whichever way you end up choosing, you certainly have a couple of decent options.  The fact that MVC was designed in such a flexible way to be able to give you these options in the first place speaks volumes.

posted on Monday, March 16, 2009 11:17 PM Print
Comments
Gravatar
# re: Enterprise Library Validation Application Block with MVC Binders
Craig
5/14/2009 6:19 PM
It seems that when using the VAB in this fashion, it competes with the default behaviour of MVC validation. Say, if you attempt to bind a null to a non-nullable integer type that is decorated with VAB attributes in the model. Your ModelState will be invalid (by default binding), your error collection will be populated with the VAB validation error message (if any), but the ModelStateDictionary for that element will have the standard validation error message from the binding attempt. At the very least, this could lead to confusion when using the Html helpers to display errors & summaries. What is the best way to use binding with nullable types like int, decimal, enum, etc., while keeping the validation results consistent when using the VAB?
Gravatar
# re: Enterprise Library Validation Application Block with MVC Binders
Steve
5/18/2009 9:20 AM
@Craig - I think what you're asking is, if you have a nullable property, how can you incorporate this in MVC with the VAB so that the user either 1) must leave it blank which is valid, or 2) IF they enter a value, that value must be valid. For example, if you have:

[RangeValidator(18, RangeBoundaryType.Inclusive, 30, RangeBoundaryType.Inclusive, MessageTemplate = "Age must be between 18 and 30")]
public int? Age { get; set; }

The user can leave Age blank but if they fill it in, it must be in the 18-30 range. Is that the correct scenario you're asking about? If so, the default behavior will be the trigger the validation even if the user did not fill in a value. In order to get the behavior you're looking for, you have the explicitly "allow" nulls as an "OR" condition like this:

[ValidatorComposition(CompositionType.Or, MessageTemplate="Age must be in range or null")]
[NotNullValidator(Negated=true)]
[RangeValidator(18, RangeBoundaryType.Inclusive, 30, RangeBoundaryType.Inclusive, MessageTemplate = "Age must be between 18 and 30")]
public int? Age { get; set; }

Hope this helps.
Gravatar
# re: Enterprise Library Validation Application Block with MVC Binders
Craig
5/19/2009 3:08 PM
@Steve -- Your validation *is* what I've been trying to do; I apparently overlooked the effective "NotNotNullValidator". Aside from validation, I had to roll my own binder to dovetail with the VAB to my liking.

I still have the issues with custom-shaped ViewModels, but I guess I'll just simplify the objects that are passed around.
Gravatar
# re: Enterprise Library Validation Application Block with MVC Binders
wes
11/13/2009 3:12 AM
Hi all,
I think for anyone using Caching Application block from Microsoft, they should consider the drawbacks of this caching solution. I would suggest to do research before going ahead. A good read can be found here Drawbacks of regular Caching Application Block

Post Comment

Title *
Name *
Email
Comment *  
Verification

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud