Getting Started with Validation

Something every application does is validation. There is a little variety in how this can be done in ASP.NET MVC. We will start with the manual, you could say "brute force", way of doing validation in this web platform and explain the validation internals along the way. The manual way is sometimes appropriate so this is not wasted time, but I find myself doing this much less now than I did when I started. But you have to start somewhere, and this is probably the best place to start anyway.

The Code

Let us get a look at the code for this little tutorial. First is the view model for the form we want to validate.

    using System;

    namespace ACoolNamespace
    {
        public class MonkeyViewModel
        {
            public string MonkeyName { get; set; }
            public int Age { get; set; }
        }
    }
    

Next comes the form. We will be improving on this.

    <h2>Create a New Monkey</h2>

    <% using (Html.BeginForm()) { %>
        <fieldset>
            <label for="MonkeyName">Name: </label>
            <br />
            <%= Html.TextBox("MonkeyName", Model.MonkeyName) %>
            <br />
            <br />
            <label for="Age">Age: </label>
            <br />
            <%= Html.TextBox("Age", Model.Age) %>
            <br />
            
            <input type="submit" value="Post" />
        </fieldset>
    <% } %>
    

And finally, the controller code. This we will also be improving. As it is it does not really do much at all other than re-render the form.

using System;
using System.Web.Mvc;
using TheNamespaceThatHasMyViewModelInIt;

namespace ANamespace.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult CreateANewMonkey()
        {
            var vm = new MonkeyViewModel();
            return View(vm);
        }

        [HttpPost]
        public ActionResult CreateANewMonkey(MonkeyViewModel vm)
        {
            return View(vm);
        }
    }
}
    
    

How It Works

First, we want to actually trigger validation errors. Our view model has two properties. We will validate the monkey name to make sure it filled in with something and we will validate that the age is a valid integer and is less than 50. As was discussed in the last tutorial, the model binding is done based on name. So is validation. Here is how you can manually validate those properties:

        [HttpPost]
        public ActionResult CreateANewMonkey(MonkeyViewModel vm)
        {
            if (String.IsNullOrEmpty(vm.MonkeyName))
                ModelState.AddModelError("MonkeyName", "Please enter a name.");

            if (vm.Age > 50)
                ModelState.AddModelError("Age", "The age of the monkey cannot be greater than 50.");

            if (ModelState.IsValid)
                return RedirectToAction("SomeConfirmationScreenOrSomething");

            return View(vm);
        }
    

Usually if a form is valid some action will take place. I went ahead and added that for completeness (lines 10 and 11). Now, so what is that ModelState thing? The framework uses that object (of type ModelStateDictionary) to store the validation state of the model. First time through the property IsValid would have a value of true. However, if an error is added to the model state through the AddModelError method (lines 5 and 8), the IsValid flag will be updated to false. If you debug, you can watch this value change in the Watch window in Visual Studio.

Note that the errors are added with a name. This allows the framework to associate the error with a particular property. And now that we have validation errors, we should pimp out the form to actually show them. We will do two things, add a validation summary to list the errors and inline, with each field, an asterisk showing that the validation for that property failed. Here is how you would do that.

    <h2>Create a New Monkey</h2>

    <%= Html.ValidationSummary() %>

    <% using (Html.BeginForm()) { %>
        <fieldset>
            <label for="MonkeyName">Name: </label>
            <%= Html.ValidationMessage("MonkeyName", "*") %>
            <br />
            <%= Html.TextBox("MonkeyName", Model.MonkeyName) %>
            <br />
            <br />
            <label for="Age">Age: </label>
            <%= Html.ValidationMessage("Age", "*") %>
            <br />
            <%= Html.TextBox("Age", Model.Age) %>
            <br />
            
            <input type="submit" value="Post" />
        </fieldset>
    <% } %>
    

So, if you submit the form and post invalid values, you get some nice and easy error reporting. Sweet. Here is the rendered source after validation fails for both fields:

    
    <div class="validation-summary-errors">
        <ul>
            <li>Please enter a name.</li>
            <li>The age of the monkey cannot be greater than 50.</li>
        </ul>
    </div>

    <form action="/Home/CreateANewMonkey" method="post">
        <fieldset>
            <label for="MonkeyName">Name: </label>
            <span class="field-validation-error">*</span>
            <br />
            <input class="input-validation-error" id="MonkeyName" name="MonkeyName" type="text" value="" />

            <br />
            <br />
            <label for="Age">Age: </label>
            <span class="field-validation-error">*</span>
            <br />
            <input class="input-validation-error" id="Age" name="Age" type="text" value="70" />
            <br />
            
            <input type="submit" value="Post" />
        </fieldset>

    </form>
    

First, what you would expect. You would expect a block of Html with a list of errors from the ValidationSummary, which you get as a div wrapping a unordered list (lines 1 through 6). You also get a span with an asterisk in it for the field-level validation errors (lines 11 and 18). The HtmlHelpers for the ValidationSummary and the ValidationMessage both look in the ModelState to determine what to render. The ValidationSummary will take the entire list of errors and render them. The ValidationMessage helpers will look in the ModelState for the specific property to which they are assigned and see if there is an error for that one specifically. Cool.

Second, what you might not expect. Note that the class input-validation-error is added to the input elements. This is a nifty feature as it gives you a Css hook for changing the display of the input in the case of an error. The key to this is the ModelState object once again. Just like the ValidationMessage helper, the TextBox helper can also look in the ModelState to see if there is a problem with the value in that field.

Other Interesting Behavior: Non-Nullable Types

If you were to run this, you would notice that when the form is rendered the "Age" input has a zero in it by default. This is because the model value that is assigned to that field is non-nullable (a plain old int) and its default value is zero. If the value is removed and the form is posted, a validation message that we have not added shows up: "The Age field is required." This comes from the DefaultModelBinder. As was mentioned in the last tutorial, the DefaultModelBinder does type conversion for you to take the form values and assign them to the view model. So this error actually makes sense. It does not make sense to say that the empty string in the field is equal to any sort of integer, and since you have a non-nullable type, the DefaultModelBinder is telling you that you need to supply a value.

If a null value is acceptable for that property in the model, the easy fix is to change the non-nullable int to a nullable int. This will fix the problem of the zero being rendered as the default value of the field and allow the value to be blank.

Testing

The above code is easy to test in an automated fashion. The following is an example unit test (using the built-in MS Test project stuff) that can test the action method on the controller and the validation.

        [TestMethod]
        public void CreateAMonkey_InvalidViewModel()
        {
            var ctrl = new HomeController();

            var vm = new MonkeyViewModel();
            vm.Age = 200;

            var result = ctrl.CreateANewMonkey(vm) as ViewResult;
            Assert.IsNotNull(result, "Expected a ViewResult.");

            Assert.AreEqual(2, result.ViewData.ModelState.Count, "Wrong number of errors.");
        }
    

Thoughts

This kind of validation is appropriate at times but there are other ways of doing this that are better for most scenarios, which is the subject of the next few tutorials. Before we move on, though, let us list a few thoughts on doing validation this way.

  • It is verbose. There are more concise ways to do those validations...next tutorial.
  • Is it the controller's job to validate? As a general rule, I like to keep my action methods as thin as possible and limit the controller as much as possible to controlling flow (whether that is flow done through redirects or the choosing of views) and constructing view models.
  • It limits validation reuse. If you wanted to reuse the view model in another action method, in this controller or another, then the validation logic would need to be duplicated (bad) or refactored into another method. It is also questionable whether or not a view model should be reused, but if you end up with a "yes" to that question, there are better ways of doing this validation other than what has been listed here.
  • For manual validation, considering moving the logic to the view model. Instead of doing the value checks in the controller method, have a method on the view model for doing that. The ModelStateDictionary can be passed in as a parameter. Doing this above would have removed several lines of code from the controller.

Speak Your Mind!

Have something to say? Find a grammatical mistake? Think I said something incorrect? Don't like my perspective? Hate my color scheme? Whatever it is, you can let me know. I would appreciate it if you did.