ASP.NET MVC Validation using IDataErrorInfo with DefaultModelBinder

One of the cool features of the DefaultModelBinder in the ASP.NET MVC Framework is its support for IDataErrorInfo. Although I think IDataErrorInfo breaks down very quickly in the real world, one certainly can take advantage of it for simple scenarios, especially forms-over-data web applications that do nothing more than save and retrieve information from a database. In fact, if you watch my Introduction to the ASP.NET MVC Framework Screencast, I purposely introduced and used the built-in support for IDataErrorInfo in the DefaultModelBinder to do input validation. You can download the source code which I will describe here.

 

IDataErrorInfo

In the screencast, I walked through a sample database-driven ASP.NET MVC contact manager web application. The application revolved around CRUD-style development against a Contacts Table in a SQL Server Database. In my example I used LINQ To SQL and the visual designer to create the Contact Entity. Since LINQ To SQL defines each entity as a Partial Class we can go ahead and create a Partial Class of Contact that implements IDataErrorInfo:

 

public partial class Contact : IDataErrorInfo

{

    public string Error

    {

        get { return string.Empty; }

    }

 

    public string this[string columnName]

    {

        get

        {

            string results = null;

 

            switch (columnName)

            {

                case "FirstName":

                    {

                        if (string.IsNullOrEmpty(FirstName))

                            results = "FirstName is required.";

                        break;

                    }

                case "LastName":

                    {

                        if (string.IsNullOrEmpty(LastName))

                            results = "LastName is required.";

                        break;

                    }

                case "Email":

                    {

                        if (string.IsNullOrEmpty(Email))

                            results = "Email is required.";

                        else

                        {

                            var regex = new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");

                            if (!regex.IsMatch(Email))

                                results = "Valid Email Required.";

                        }

 

                        break;

                    }

                case "Phone":

                    {

                        if (string.IsNullOrEmpty(Phone))

                            results = "Phone is required.";

                        break;

                    }

                default:

                    results = string.Empty;

                    break;

            }

 

            return results ?? string.Empty;

        }

    }

}

 

There are really two pieces to IDataErrorInfo:

  • Error - Gets an error message indicating what is wrong with this object.
  • Item - Gets the error message for the property with the given name.

 

DefaultModelBinder and IDataErrorInfo

The ContactsController takes advantage of the fact that the DefaultModelBinder will implement IDataErrorInfo validation during model binding by checking ModelState.IsValid to see if binding / validation was successful. If it wasn't successful, ContactsController re-displays the view with all the validation errors provided via IDataErrorInfo.

 

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Create([Bind(Exclude = "Id")]Contact contact)

{

    try

    {

        if (!ModelState.IsValid)

            return View();

 

        _db.Contacts.InsertOnSubmit(contact);

        _db.SubmitChanges();

        return RedirectToAction("Index");

    }

    catch

    {

        return View();

    }

}

 

DefaultModelBinder OnModelUpdated

Take a peek at the DefaultModelBinder's OnModelUpdated and OnPropertyValidated

 

protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {

    IDataErrorInfo errorProvider = bindingContext.Model as IDataErrorInfo;

    if (errorProvider != null) {

        string errorText = errorProvider.Error;

        if (!String.IsNullOrEmpty(errorText)) {

            bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorText);

        }

    }

}

 

protected virtual void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) {

    IDataErrorInfo errorProvider = bindingContext.Model as IDataErrorInfo;

    if (errorProvider != null) {

        string errorText = errorProvider[propertyDescriptor.Name];

        if (!String.IsNullOrEmpty(errorText)) {

            string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);

            bindingContext.ModelState.AddModelError(modelStateKey, errorText);

        }

    }

}

 

to understand more about how IDataErrorInfo is integrated with the DefaultModelBinder. Understanding OnModelUpdated and OnPropertyValidated will also help you to understand how to override the default use of IDataErrorInfo for you custom validation solutions using System.ComponentModel.DataAnnotations, Enterprise Library Validation Application Block, etc.

Hope this helps,


David Hayden

 

ASP.NET MVC Tutorials

 

posted on Monday, May 11, 2009 6:15 PM

Main

News

Green Tea

.NET Development

Enterprise Library

Patterns & Practices