Practical .NET

Exploiting the Validation Tools in ASP.NET MVC

Your users will make mistakes ... but it would be a mistake to treat all their errors the same way. You can get more out of ASP.NET MVC's validation infrastructure just by paying attention to how you name your errors.

In your ASP.NET MVC controllers, you probably begin any Action method that accepts data from the user by checking the ModelState class’s IsValid property. This property returns false if ASP.NET MVC ran into any problems while moving data received from the browser into the parameters of your Action method (that is, during model binding). The result of leveraging IsValid is that the start of a typical method looks like this:

[HttpPost]
Public Function UpdateCustomer(functionButton As String, cust As Customer) As ActionResult
  If Not ModelState.IsValid Then
    Return View("UpdateCustomer", cust)
  End If
  '... rest of method...

In addition to setting the IsValid property, the model-binding process also adds error messages to the ModelState that, in your Views, the ValidationMessageFor helper will display. Each of those messages is tied to the property that generated the problem during model binding, so you typically display the message with one of these two lines of code in your View:

@Html.ValidationMessage("DeptCode")
@Html.ValidationMessageFor(Function(m) m.DeptCode)

The second version gives you IntelliSense support when you type the code in, but otherwise the result is the same: The first message generated when moving data from the browser into the DeptCode property of the cust object is displayed.

However, it’s likely that you want to do more validation than is taken care of with model binding. As you add additional validation there are problems you need to address, such as recording any errors you find and then displaying those errors to your users. The ASP.NET MVC validation infrastructure can help with both parts.

Finding and Recoding Errors
Typically, you apply any additional, custom validation after you check the IsValid property. As you turn up additional errors related to specific properties, the easiest way to have those messages displayed is to use the ModelState’s AddModelError method. This adds your custom error messages to ModelState. Also, typically, you’ll tie your message to the property with the data you’re unhappy with by passing the name to AddModelError with your message.

This code, for example, makes sure that the value in the cust object’s DeptCode property is valid and, if it’s not, adds an error to the ModelState’s list, tied to that property:

If Not Department.IsValidCode(cust.DeptCode) Then
  ModelState.AddModelError("DeptCode", "Department code not valid")
End If

ModelState treats your errors exactly the way it treats errors generated by model binding: When you add an error using AddModelError, the ModelState’s IsValid property is automatically set to false. So, after all your additional validation errors, you can just check IsValid to see if you’ve turned up any new errors.

This code skips any further processing by sending the user an error page after any custom validation:

If Not Department.IsValidCode(cust.DeptCode) Then
  ModelState.AddModelError("DeptCode", "Department code not valid")
End If
'... more validation ...
If Not ModelState.IsValid Then
  Return View("UpdateCustomer", cust)
End If

You’re not, however, obligated to use the names of properties when adding messages to the ModelState, either. This code uses an arbitrary name of TimeExpired for an error message that isn’t associated with any property on the Model class:

If (DateTime.Now - CType(Session("transactionStart"), DateTime)).Minutes > 5 Then
  ModelState.AddModelError("TimeExpired", "You’ve run out of time")
End If

In fact, you’re not obliged to assign any name to your message if you just pass String.Empty as the first parameter to AddModelError. The following example cross-checks DeptCode and Region and adds a message when there’s a problem. Because the validation involves two properties, it doesn’t assign a name to the message:

If cust.DeptCode = "D1" AndAlso
  cust.Region = "W"  Then
  ModelState.AddModelError(String.Empty, 
    "Invalid combination of Department Code and Region")
End If

As you’ll see, it may actually be helpful to use String.Empty with errors that either don’t apply to any property or that involve two or more properties (I’ll call these "nameless" messages).

But, now that you’ve recorded your errors, you’ll want to display them to your users in a View.

Displaying Errors
When you use ValidationMessage or ValidationMessageFor you usually pass the name of a property on your View’s Model class, either as a string or as a lambda expression. You’re not, however, restricted to those names if you use ValidationMessage (unfortunately, ValidationMessageFor will only work with properties on your Model class). This code adds to my View the message that, in earlier code, I named TimeExpired:

@Html.ValidationMessage("TimeExpired")

This works equally well with any nameless messages (though, of course, it will only display the first nameless message):

@Html.ValidationMessage(String.Empty)

In addition to ValidationMessage/ValidationMessageFor you can use the ValidationSummary helper to display all your messages in one place in your View. The issue now is that you have two classes of messages: named and nameless.

Named errors, tied to a single property, are all driven by that property’s value. Odds are that when users fix the problem with the first message displayed by ValidationMessage/ValidationMessageFor, they’ll probably have fixed any other problems with that property’s value.

That’s not necessarily true if you have multiple nameless messages, because they probably have nothing to do with one another. Fixing one of those errors may have no impact on any of the others: Displaying just the first error assigned to String.Empty may not be much help in fixing other messages you’ve tied to String.Empty.

Fortunately, ValidationSummary can be very helpful here. First, ValidationSummary displays all the messages on the page, not just the first message for any property. Second, if you pass true as the first parameter to ValidationSummary, it will display only the messages that have String.Empty as their name.

This code, for example, displays a list of all the String.Empty messages with the heading, "Please fix these problems:":

@Html.ValidationSummary(True, "Please fix these problems:")

This opens up a strategy for displaying these different classes of errors. First, use nameless messages for errors not tied to a single property; use a ValidationSummary at the top of the page to display all of those errors. Use named messages for errors tied to a particular property (either with the names of properties or with arbitrary names). Use ValidationMessageFor or ValidationMessage to display your named errors in the part of your page where the message will be most helpful to your users.

By taking advantage of property names, arbitrary names and nameless messages, you can help your users through your application by letting them know what’s gone wrong this time. Your users won’t thank you …but they probably won’t call you, either.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

  • TypeScript Tops New JetBrains 'Language Promise Index'

    In its latest annual developer ecosystem report, JetBrains introduced a new "Language Promise Index" topped by Microsoft's TypeScript programming language.

Subscribe on YouTube