Form validation with ASP.NET MVC release candidate

Last week, the ASP.NET MVC framework release candidate was released (check ScottGu’s post). Apart from some great new tooling support, form validation has never been easier. Here’s a quick introduction.

Employee from Northwind database Imagine we have a LINQ to SQL data model, containing an Employee from the Northwind database. As you may know, LINQ to SQL will generate this Employee class as a partial class, which we can use to extend this domain object’s behaviour. Let’s extend this class with an interface implementation for IDataErrorInfo.

[code:c#]

public partial class Employee : IDataErrorInfo
{
    #region IDataErrorInfo Members

    public string Error
    {
        get { throw new NotImplementedException(); }
    }

    public string this[string columnName]
    {
        get { throw new NotImplementedException(); }
    }

    #endregion
}

[/code]

IDataErrorInfo is an interface definition that is found in System.ComponentModel. It provides the functionality to offer custom error information that a user interface can bind to. Great, let’s do that! Assume we have a view which is used to edit this Employee object. The code for this will be quite easy: some HTML form stuff, Html.ValidationMessage calls, … Here’s a snippet:

[code:c#]

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Edit</h2>

    <%= Html.ValidationSummary() %>

    <% using (Html.BeginForm()) {>
        <%= Html.Hidden("id", Model.EmployeeID) %>

        <fieldset>
            <legend>Fields</legend>
            <p>
                <label for="LastName">LastName:</label>
                <%= Html.TextBox("LastName") %>
                <%= Html.ValidationMessage("LastName", "*") %>
            </p>

            <!-- ... -->

        <fieldset>
    <% } %>

</asp:Content>

[/code]

The controller’s action method for this will look like the following:

[code:c#]

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection)

    Employee employee = repository.RetrieveById(id);

    try
    {
        UpdateModel(employee, collection.ToValueProvider());
        repository.Save(employee);

        return RedirectToAction("Index");
    }
    catch
    {
        return View(employee);
    }
}

[/code]

Nothing fancy here: a call to UpdateModel (to populate the Employee instance with data fom the form) and a try-catch construction. How will this thing know what’s wrong? This is where the IDataErrorInfo interface comes useful. ASP.NET MVC’s UpdateModel method will look for this interface implementation and retrieve information from it. The Error property that is defined on IDataErrorInfo returns a string containing any error that is “global” for the Employee object. The this[string columnName] indexer that is defined on IDataErrorInfo is used to retrieve error messages for a specific property. Now let’s make sure FirstName and LastName are provided:

[code:c#]

public partial class Employee : IDataErrorInfo
{
    #region IDataErrorInfo Members

    public string Error
    {
        get { return ""; }
    }

    public string this[string columnName]
    {
        get
        {
            switch (columnName.ToUpperInvariant())
            {
                case "FIRSTNAME":
                    if (string.IsNullOrEmpty(FirstName))
                        return "Please provide a firstname.";
                    break;
                case "LASTNAME":
                    if (string.IsNullOrEmpty(LastName))
                        return "Please provide a lastname.";
                    break;
            }

            return "";
        }
    }

    #endregion
}

[/code]

Great, let’s try it out. If I omit the firstname or lastname when editing an Employee object, here’s what the view looks like:

ASP.NET MVC form validation

How easy was that! More on the new things in the ASP.NET MVC release candidate can be found in ScottGu’s blog post.

kick it on DotNetKicks.com

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

9 responses

  1. Avatar for PaulBlamire
    PaulBlamire January 30th, 2009

    Hi Maarten,

    Good article. Just a thought though, should you not be declaring your employee object outside of the try block so that if the catch clause is invoked you don't need to hit the db again? Suppose it depends on if L2S implements an identity map, and if your repository hold onto the datacontext.

  2. Avatar for maartenba
    maartenba January 30th, 2009

    Have updated that. Thanks for noticing!

  3. Avatar for Wookie
    Wookie January 31st, 2009

    Nice work by the MVC team but I think that the solution provided by Steve Sanderson's xVal library is much more elegant; not to mention that it covers both client and server side validation.

    http://xval.codeplex.com/

  4. Avatar for maartenba
    maartenba January 31st, 2009

    True, but this is still an elegant solution to server side validation.

  5. Avatar for trendbender
    trendbender February 3rd, 2009

    thx, good post :)

  6. Avatar for Alberto Ferreira
    Alberto Ferreira February 20th, 2009

    Because DefaultBindig check this[string columnName] just after set the property, this approach don't let you validate one property based on another, because it coud be not initialized yet.

    I Think was better to validate all the columnName when Error is called.

    Another think: if DefaultBinding can&#180t cast to destination type (ex.: unitPrice TextBox with "Hello"), IDataErrorInfo will never be called, so this way we can create our custom message for this kind of error.

  7. Avatar for James
    James February 21st, 2009

    How do you add error messages for the situation where someone enters text in a numeric-only or date field?
    i.e. if you have:
    public ActionResult Add(Employee item)
    {
    }
    and someone puts in "blahblah" into the BirthDate field (which presumably is of type DateTime)...

    The allocation of each field to the employee object happens before the Add method is called, and the ModelState already has an error from the start of the method, but with a blank error message. Is there any way to capture this and add a message in? At the moment i'm identifying the blank error message, removing the error from the dictionary object and adding it back in with a generic message ... all from within the controller... which doesn't seem quite right...

  8. Avatar for maartenba
    maartenba February 23rd, 2009

    James, here's an answer in code format :-)

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(FormCollection collection)
    {
    Note note = new Note();

    try
    {
    UpdateModel(
    note,
    new string[] { "Title", "Body" },
    collection.ToValueProvider()
    );

    noteRepository.Save(note, User.Identity.Name);

    return RedirectToAction("List");
    }
    catch (InvalidOperationException ex)
    {
    // ...................................................
    // Model binding went wrong...
    //
    // You can add some code here for validating tha values
    // provided, or pass the formCollection.ToValueProvider()
    // to the Note class.
    //
    // In there, you can set the ModelState errors.
    //
    // Example:
    // note.AddModelStateErrors( formCollection.ToValueProvider(), ModelState );
    // ...................................................

    return View(note);
    }
    }

  9. Avatar for Jon
    Jon April 4th, 2009

    I have the same issue as James but cannot get it to work properly. Any chance you could do a post or show some code exactly how that scenario would work.

    Thanks