Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

About the author

Maarten Balliauw is currently employed as a Technical Evangelist at JetBrains. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Pro NuGet Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
Maarten Balliauw - MVP - Most Valuable Professional
Maarten Balliauw - ASPInsider

Search

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright Maarten Balliauw 2013


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.

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
}

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:

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

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

[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);
    }
}

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:

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
}

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


Categories: ASP.NET | C# | General | MVC

Comments (9) -

PaulBlamire United Kingdom |

Friday, January 30, 2009 3:01 PM

PaulBlamire

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.

maartenba Belgium |

Friday, January 30, 2009 3:17 PM

maartenba

Have updated that. Thanks for noticing!

Wookie United Kingdom |

Friday, January 30, 2009 6:54 PM

Wookie

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/

maartenba |

Saturday, January 31, 2009 9:54 AM

maartenba

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

trendbender Russia |

Monday, February 02, 2009 7:41 PM

trendbender

thx, good post Smile

Alberto Ferreira Portugal |

Friday, February 20, 2009 12:55 PM

Alberto Ferreira

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´t 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.

James United Kingdom |

Friday, February 20, 2009 8:41 PM

James

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...

maartenba Belgium |

Monday, February 23, 2009 9:07 AM

maartenba

James, here's an answer in code format Smile

[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);
    }
}

Jon United Kingdom |

Saturday, April 04, 2009 11:06 AM

Jon

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

Pingbacks and trackbacks (4)+

Comments are closed