in

ASP.NET Weblogs

A Recipe for New Media

My Social Media Adventure #1 -- DataAnnotations Model Binder

An important part of ASP.NET MVC is the ability to specify a complex object type as the parameter to your controller action method and have MVC "hydrate" your object from the Form data sent to the server by the browser.  For Scott Guthrie's treatment on this feature, go here.  I'm not going to review this functionality.  What I want to talk about is the fact that model validation can be built into the Model Binder so that not only is your object hydrated, it is automatically validated without you ever having to worry about it.  There are several different possible technologies that could be used to do this validation.  I personally looked at:

  • System.ComponentModel.DataAnnotations (Check out Phil Haack's great MIX09 session "Ninja on Fire Blackbelt tips" for an awesome overview of this and other MVC stuff) 
  • NHibernate.Validators

I ultimately selected DataAnnotations for 3 reasons:

  1. The first and biggest reason I chose DataAnnotations, was one of the requirements I had was localization and I was going to be developing custom validators.  I searched the web over looking for a simple way to do this with NHibernate.Validator and there wasn't one (you actually had to implement an interface, override a default class and lose a bunch of the baked in functionality, not good in my books).  With DataAnnotations, I simply needed to extend the base class ValidationAttribute and all the localization goodness was baked in.
  2. Complexity, NHibernate.Validator uses an attribute and a validator class, which are connected via an attribute on the attribute... Way too complicated (DataAnnotations, the attribute handles validation as well).
  3. DataAnnotation Model Binder is going to be baked into ASP.NET MVC 2.0.

Now, having made that decision, I began testing with the default DataAnnotations Model Binder sample posted here.  The first thing I realized was that Localization was broken!!  It, thankfully, was an easy fix. I just removed the call to GetDisplayName from the OnModelUpdated as it was unnecessary, and instead of the call to GetDisplayName in OnPropertyValidating, I put this line: 

            validationContext.MemberName = propertyDescriptor.Name;

 And everything started working as expected.  To save you time, I've attached the changed file.  For more information on how to actually implement the DataAnnotations Model Binder, I recommend watching the video referenced above (Ninja on Fire, at position 61:00 of the video, though I highly recommend the whole video).

(NOTE:  The file really is attached... it is at the very bottom of this post, but if you don't see it... Click Here)

Ok, having fixed the bug (there was one other bug I heard about, but had not run into yet, about nested complex properties, that I've included the fix for as well), I started writing my custom Validators.  The first of which was CreditCardValidatorAttribute, which (obviously) implements a credit card validation.  So let's look at how I did that.  I'm going to exclude the actual code that does the validation (again, I've included the whole source code in the attachment) and look more at the code specific to DataAnnotations:

public class CreditCardAttribute : AbstractLuhnAttribute
{
public CreditCardAttribute()
: base(() => Resources.AbstractLuhn_ErrorMessage)
{ }
}

That's it.  As I said, I wasn't going to look at the code to do the actual credit card validation (Which is in AbstractLuhnAttribute).  What is happening here is that the ValidationAttribute base class contains a constructor that takes a Func<string> parameter which it will use to lookup the error message. In the constructor of our CreditCardValidator, we pass it a value that comes from the Resources of the DLL containing this validator.

If you have watched the video as I suggested you would have seen Phil Haack showing you how to define your own Error messages and how to place them into your own Resources within the ASP.NET MVC Web project, all of that example is totally valid with this Credit Card Validator as well!

The next validator I implemented was a copy of the Future validator from the NHibernate.Validator project, which validated that a date is in the future.  I made a slight modification (due to business rules requirements) and I implemented TodayOrFuture.  It was slightly different from the Credit Card validator in that it's validation message required a more complex token replacement.  So here is the important code (again, the actual validation has been removed, but is included in the attachment to this article. ):

public class TodayOrFutureAttribute : ValidationAttribute
{
public TodayOrFutureAttribute()
: base(() => Resources.TodayOrFuture_ErrorMessage)
{
}

public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, base.ErrorMessageString,
new
object[] { name, DateTime.Now.ToShortDateString() });
}
}

The difference is the FormatErrorMessage method, which allows us to inject a value into the ErrorMessage (which the raw form is

{0} must be greater than or equal to {1}

so when you input an incorrect date, you will get an error message that reads like:

Post Date must be greater than or equal to 09/09/2009

Again, it makes creating a custom validator extremely easy.  And for those who noticed that I did not involve the Culture in generating the Date string, good eye!  I didn't realize it myself until after I had written this article.  I'm going to fix it in the attachment.

 Up next will be a look at how I integrated StructureMap with the ASP.NET MVC ControllerFactory to provide Dependency Injection to the Controllers.

Comments

No Comments