Enterprise Library Validation Application Block with MVC Binders

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.

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

22: /// Converts an Enterprise Library ValidationResult into the correct "key" to be used by MVC Views.

23: ///

24: ///

25: ///

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

This article is part of the GWB Archives. Original Author: Steve Michelotti

New on Geeks with Blogs

  • We Won The One Award I Actually Care About

    Full Scale made the Inc. 5000 for the fifth year straight, the 12th listing across my three companies. Here is why the one award you cannot buy is worth stopping for.

  • Your Customers Build the Features Now

    I let a tool I liked sit dead for a year rather than build the features I wanted. An MCP server meant I never had to, and your customers can do the same to your product.

  • Get the Size of a Directory in Linux the Easy Way

    du -sh for the quick answer, ncdu for the cleanup, df for the disk itself: every command for checking directory size in Linux, plus why du and df never agree.

  • Vim Search and Replace: The Ultimate Guide

    One :%s command replaces every match in a file before a find dialog would even open. The Vim substitute patterns worth the muscle memory: flags, ranges, capture groups, and multi-file edits.