modelstate

Consuming Web API Custom Validation in MVC using RestSharp

I previously wrote a post called Custom Validation in ASP.NET Web API with FluentValidation, in which I showed how my group set up a validation framework over WebAPI using the FluentValidation NuGet package.

In this post, we'll go over how to consume those responses in an ASP.NET MVC app, as well as how to take the error messages from the API response and automatically add them to the MVC ModelState.

I have updated the sample project on GitHub with the code from this post, so it may be easier for you to pull that down first and then follow along. Whichever way you like to learn, let's get started!

Goals and Dependencies

In building this system, we have two major goals in mind:

  1. All validation will be done on the Web API side, but the consuming MVC app will need to display the errors to the user.
  2. In order to make the first goal easier, we want the errors from the Web API project automatically imported into the MVC ModelState.

For this solution, we are using a couple of packages from NuGet:

  • JSON.NET (Allows simple serializing and deserializing to and from JSON).
  • RestSharp (Enables simpler calling of API clients)

Finally, we are using a pattern called POST-REDIRECT-GET in our MVC app which enables us to pass ModelStates from one action to another (check out that post for more details).

Remember the Alamo Models!

First, let's remind ourselves what the models and package looked like from the previous post.

public class User  
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string Username { get; set; }
}

public class UserValidator : AbstractValidator<User>  
{
    public UserValidator()
    {
        RuleFor(x => x.FirstName).NotEmpty().WithMessage("The First Name cannot be blank.")
                                    .Length(0, 100).WithMessage("The First Name cannot be more than 100 characters.");

        RuleFor(x => x.LastName).NotEmpty().WithMessage("The Last Name cannot be blank.");

        RuleFor(x => x.BirthDate).LessThan(DateTime.Today).WithMessage("You cannot enter a birth date in the future.");

        RuleFor(x => x.Username).Length(8, 999).WithMessage("The user name must be at least 8 characters long.");
    }
}

public class ResponsePackage  
{
    public List<string> Errors { get; set; }
    public object Result { get; set; }
    public bool HasErrors
    {
        get
        {
            if (Errors != null)
            {
                return Errors.Any();
            }
            return false;
        }
    }
    public ResponsePackage(object result, List<string> errors)
    {
        Errors = errors;
        Result = result;
    }
}

Recall that in this system, any response at all will be wrapped by a ResponsePackage, so one of the things the consumer system will have to do is extract the JSON from the package and deserialize it to an object.

Creating the Clients

In order to call the API using RestSharp, we can use "client" classes that implement methods which call API functions. In our case, we decided to create a "base" client class which all other clients would inherit from. The base client needs three things:

  1. A constructor which passes in the current ModelState (so that validation errors from the API can be inserted into it).
  2. An Execute() method which executes the current request.
  3. An Execute<T>() method which executes the current request and automatically extracts the content of the ResponsePackage to the specified type.

Here's the code for the base client:

public class ClientBase : RestClient  
{
    private ModelStateDictionary _modelstate;

    public ClientBase(ModelStateDictionary modelstate) : base(Constants.ApiUrl)
    {
        _modelstate = modelstate;
    }

    //This method executes the request and does not attempt to deserialize the response.  We use this for updates, deletes, etc.
    public new void Execute(IRestRequest request)
    {
        var response = base.Execute(request);
        var parsedObj = JObject.Parse(response.Content);
        var apiResponse = JsonConvert.DeserializeObject<ResponsePackage>(parsedObj.ToString());
        if (apiResponse.HasErrors)
        {
            AddErrors(apiResponse);
        }
    }

    //This method expects the Result property of the response to be a JSON object that can be deserialized into an object of type T
    public new T Execute<T>(IRestRequest request) where T : new()
    {
        var response = base.Execute(request);
        var parsedObj = JObject.Parse(response.Content);
        var apiResponse = JsonConvert.DeserializeObject<ResponsePackage>(parsedObj.ToString());
        if (apiResponse.HasErrors)
        {
            AddErrors(apiResponse);
            return default(T);
        }
        return response.Extract<T>();
    }

    private void AddErrors(ResponsePackage response)
    {
        List<string> listMessagesAdded = new List<string>();
        for (int i = 0; i < response.Errors.Count; i++)
        {
            if (listMessagesAdded.Contains(response.Errors[i])) continue;
            _modelstate.AddModelError("error" + i.ToString(), response.Errors[i]);
            listMessagesAdded.Add(response.Errors[i]);
        }
    }
}

Take particular note of the AddErrors() method. This method is key to the whole operation: it is what takes the error messages out of the ResponsePackage and inserts them into the API.

Now that we've got the base client, let's create the derivative UserClient. Recall from the previous post that we have two methods in the API: a method to get all the users and a method to add an additional one. We need corresponding methods in our UserClient, like so:

public class UserClient : ClientBase  
{
    public UserClient(ModelStateDictionary modelstate) : base(modelstate)
    {
    }

    public List<User> GetAll()
    {
        RestRequest request = new RestRequest("users/all");
        return Execute<List<User>>(request);
    }

    public void Add(User user)
    {
        RestRequest request = new RestRequest("users/add", Method.POST);
        request.AddJsonBody(user);
        Execute(request);
    }
}

There's still a piece missing. Exactly what does the Extract<T>() method above do? It reads the Result property of the ResponsePackage and deserializes it into an object of type T. We implement that in an extension class, like so:

public static class RestResponseExtensions  
{
    private static string ResultPropertyName = "Result";

    public static T Extract<T>(this IRestResponse response) where T : new()
    {
        var parsedObj = JObject.Parse(response.Content);
        return JsonConvert.DeserializeObject<T>(parsedObj[ResultPropertyName].ToString());
    }
}

NOTE: IRestResponse is an interface implemented in RestSharp

We're on our way! Now that we've got the clients defined, let's set up the MVC app.

Chaos Control(ler)

Let's do the code first. Here's the Controller:

[RoutePrefix("users")]
public class UserController : Controller  
{
    [HttpGet]
    [Route("all")]
    [Route("")]
    [Route("~/")]
    public ActionResult Index()
    {
        UserClient client = new UserClient(ModelState);
        var users = client.GetAll();
        return View(users);
    }

    [HttpGet]
    [Route("add")]
    [ImportModelState]
    public ActionResult Add()
    {
        var user = new User();
        return View(user);
    }

    [HttpPost]
    [Route("add")]
    [ExportModelState]
    public ActionResult Add(User user)
    {
        UserClient client = new UserClient(ModelState);
        client.Add(user);
        if(!ModelState.IsValid)
        {
            return RedirectToAction("Add");
        }
        return RedirectToAction("Index");
    }
}

The [ExportModelState] and [ImportModelState] attributes are part of the POST-REDIRECT-GET pattern that I've written about before. For now, just remember that those attributes allow the ModelState to get passed from one action to another (otherwise it gets lost on redirects).

We also need to set up the Add view (If you want the code for the Index view, take a look at the GitHub project)

@model WebApiValidationDemo.Mvc.Lib.Models.User

@{
    ViewBag.Title = "Add a User";
}

@Html.ActionLink("Back to Index", "Index")

<h2>Add a User</h2>

@using (Html.BeginForm("Add", "User", FormMethod.Post))
{
    @Html.ValidationSummary() //This is key
    <div>
        <div>
            @Html.LabelFor(x => x.FirstName)
            @Html.TextBoxFor(x => x.FirstName)
        </div>
        <div>
            @Html.LabelFor(x => x.LastName)
            @Html.TextBoxFor(x => x.LastName)
        </div>
        <div>
            @Html.LabelFor(x => x.BirthDate)
            @Html.TextBoxFor(x => x.BirthDate, new { type = "date" })
        </div>
        <div>
            @Html.LabelFor(x => x.Username)
            @Html.TextBoxFor(x => x.Username)
        </div>
        <div>
            <input type="submit" value="Save" />
        </div>
    </div>
}

The Html.ValidationSummary() is key, since that will display the error messages found in the ModelState.

What Does THIS Button Do?

It saves the User. Sheesh, if you just wait a minute, you'll find out.

(My son asked me this question regarding a different app as I was writing this section. My response was the same.)

We will now test the app to ensure that it is receiving validation errors and displaying them on the page in the Html.ValidationSummary() control. As a reminder, here's the rules that validate the User:

  1. The FirstName cannot be blank.
  2. The FirstName cannot be more than 100 characters.
  3. The LastName cannot be blank.
  4. The BirthDate cannot be in the future (relative to the current date).
  5. The Username must be at least 8 characters long.

If we run the MVC app, we'll end up on a page that looks like this:

Now let's test adding a valid user and an invalid user.

Valid User

We can click on "Add a User" to see the Add a User page. Let's attempt to add a valid user, like so:

Clicking on save causes none of the validation rules to fire, so we end up back at the index page. Success!

Invalid User

Now let's try to add an invalid user.

Note that this user violates three of the rules:

  • The first name is blank.
  • The birth date is in the future.
  • The username is less than 8 characters long.

Attempting to save this user results in this:

Success! The error messages were successfully returned to the ModelState and shown to the user.

The Best Part

Here's the best part about this whole situation: adding new validation rules on the API causes zero changes on the consumers! We've removed any dependency the MVC app has on the validation implemented by the API.

Let's say we implement a new validation rule:

  • The Last Name cannot be less than 5 characters.

The resulting change to our UserValidator looks like this:

public class UserValidator : AbstractValidator<User>  
{
    public UserValidator()
    {
        ...
        RuleFor(x => x.LastName).Length(5, 999).WithMessage("The Last Name cannot be less than 5 characters");
        ...
    }
}

Now, let's resubmit that invalid user we just created, and here's the error messages we get:

Summary

We have now built an ASP.NET MVC app that can successfully:

  • Consume the responses sent by the Web API's custom validation layer, even though they're all wrapped in ResponsePackage.
  • Display the error message returned by the API.

Take a look at the GitHub project if you haven't already done so, and feel free to point out what I did wrong (or right, hey I can hope) in the comments.

Happy Coding!

Custom Validation in ASP.NET Web API with FluentValidation

Validation is one of the key components in any web app. After all, we should never trust any input to our applications, ever. Up until now, my group has been building MVC-based web apps, in those apps we've been content to use built-in or custom-built validation solutions for any validation-type procedures we needed to write.

Now, though, we've transferred over to building a web service written in ASP.NET Web API, and the old validation structure we had in place just doesn't work very well in that new environment. Consequently we had to hammer out a new validation structure, and I'm particularly proud of how we set that up, so I wanted to share it here. Let me know if it helps you as well. First, though, a little background on how our app was set up.

Architecture of the App

Our app has the following layers:

Clients (ASP.NET MVC, iPhone apps, etc)  
|
API (Web API)  
|
Database  

It's a pretty standard service-based application, at least from the 10,000-foot view.

The problem is that we want our validation to be performed in the API layer, and not make the clients responsible for implementing their own validation. In other words, we need to do the following:

  1. Validate all incoming requests according to a set of rules.
  2. Return any validation error messages to the calling clients.

We can tackle Problem 1 by using one of my favorite NuGet packages, FluentValidation.

Setting Up FluentValidation

Let's add FluentValidation's WebAPI package to our app, like so:

Now we can set up a class that we will need to validate. Here's a sample User class.

public class User  
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string Username { get; set; }
}

In order to set up FluentValidation, we need to create a Validator class which inherits from AbstractValidator, and add a Validate attribute to the User class. Finally, we need to define the validation rules that apply to the properties of User. The final result looks like this:

[Validator(typeof(UserValidator))]
public class User  
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string Username { get; set; }
}

public class UserValidator : AbstractValidator<User>  
{
    public UserValidator()
    {
        RuleFor(x => x.FirstName).NotEmpty().WithMessage("The First Name cannot be blank.")
                                    .Length(0, 100).WithMessage("The First Name cannot be more than 100 characters.");

        RuleFor(x => x.LastName).NotEmpty().WithMessage("The Last Name cannot be blank.");

        RuleFor(x => x.BirthDate).LessThan(DateTime.Today).WithMessage("You cannot enter a birth date in the future.");

        RuleFor(x => x.Username).Length(8, 999).WithMessage("The user name must be at least 8 characters long.");
    }
}

I'm not going to get into how to set up these rules; see my earlier tutorial for how to do this.

Only thing left to do to enable FluentValidation is to turn on the ModelValidatorProvider in WebApiConfig.cs, like so:

public static class WebApiConfig  
{
    public static void Register(HttpConfiguration config)
    {
        ...
        FluentValidationModelValidatorProvider.Configure(config);
    }
}

Validating the Request

Now that we have the validation rules in place, we need a way to validate that all requests submitted to this service are valid. We can do this by using an ActionFilterAttribute. Remember that FluentValidation places validation errors into the ModelState so our ActionFilterAttribute will need to check the ModelState's status.

The filter looks like this:

public class ValidateModelStateFilter : ActionFilterAttribute  
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

What this filter does is return a new error response if the submitted Request has validation errors. We know that ModelState.IsValid will represent whether or not the Request has validation errors because FluentValidation automatically inserts its errors into the ModelState.

Now we just need to add this filter to the Web API Pipeline in WebApiConfig.cs, like so:

public static class WebApiConfig  
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        config.Filters.Add(new ValidateModelStateFilter());

        // Web API routes
        ...

        FluentValidationModelValidatorProvider.Configure(config);
    }
}

Wrapping The Response

Now we come to a fundamental problem with this setup: the type of the response could be anything, yet we need to append existing validation errors to the response. To solve this, we created a simple ResponsePackage class:

public class ResponsePackage  
{
    public List<string> Errors { get; set; }

    public object Result { get; set; }
    public ResponsePackage(object result, List<string> errors)
    {
        Errors = errors;
        Result = result;
    }
}

The idea is that every response from the API will be wrapped by an instance of this class, which contains any errors that occurred.

We now need something that will check every response from the API and wrap it in ResponsePackage. To do this, we use a DelegatingHandler:

public class ResponseWrappingHandler : DelegatingHandler  
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        //Step 1: Wait for the Response
        var response = await base.SendAsync(request, cancellationToken);

        return BuildApiResponse(request, response);
    }

    private HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
    {
        object content;
        List<string> modelStateErrors = new List<string>();

        //Step 2: Get the Response Content
        if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
        {
            HttpError error = content as HttpError;
            if (error != null)
            {
                //Step 3: If content is an error, return nothing for the Result.
                content = null; //We have errors, so don't return any content
                //Step 4: Insert the ModelState errors              
                if (error.ModelState != null)
                {
                    //Read as string
                    var httpErrorObject = response.Content.ReadAsStringAsync().Result;

                    //Convert to anonymous object
                    var anonymousErrorObject = new { message = "", ModelState = new Dictionary<string, string[]>() };

                    // Deserialize anonymous object
                    var deserializedErrorObject = JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);

                    // Get error messages from ModelState object
                    var modelStateValues = deserializedErrorObject.ModelState.Select(kvp => string.Join(". ", kvp.Value));

                    for (int i = 0; i < modelStateValues.Count(); i++)
                    {
                        modelStateErrors.Add(modelStateValues.ElementAt(i));
                    }
                }
            }
        }

        //Step 5: Create a new response
        var newResponse = request.CreateResponse(response.StatusCode, new ResponsePackage(content, modelStateErrors));

        //Step 6: Add Back the Response Headers
        foreach (var header in response.Headers) //Add back the response headers
        {
            newResponse.Headers.Add(header.Key, header.Value);
        }

        return newResponse;
    }
}

Let's walk through what this does.

  • Step 1: We wait for the response. After all, for this handler, we don't care about the request or anything besides the response.
  • Step 2: We get the response content. What that content is determines whether or not we need to run Steps 3-5.
  • Step 3: If the content is an error (of type HttpError), set the returned content to null.
  • Step 4: Insert the ModelState errors. We do this by reading the response content, deserializing that first to an anonymous object then to a JObject (using Json.Net) and finally extracting the error messages themselves.
  • Step 5: Create a new response.
  • Step 6: Add back the response headers (they should be the same in the new response as in the generated response).

In short, this wrapper will wrap all responses in an instance of ResponsePackage, and if there are any errors in the ModelState, those errors get included in the ResponsePackage.

The final step, of course, is to place the handler in the Web API Pipeline. We're done with all the steps, so here's the complete WebApiConfig.cs file:

public static class WebApiConfig  
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        config.Filters.Add(new ValidateModelStateFilter());
        config.MessageHandlers.Add(new ResponseWrappingHandler());

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        FluentValidationModelValidatorProvider.Configure(config);
    }
}

Testing the Validation

Now we can test that this works using Postman. First, we'll need some controller actions. Here's a GET for retrieving users and a POST for submitting a new one:

[RoutePrefix("users")]
public class HomeController : ApiController  
{
    [HttpGet]
    [Route("all")]
    [ValidateModelStateFilter]
    public IHttpActionResult GetAll()
    {
        var users = new List<User>();
        users.Add(new User()
        {
            FirstName = "Mark",
            LastName = "Watney",
            BirthDate = new DateTime(2003, 8, 7),
            Username = "mwatney549"
        });

        users.Add(new Models.User()
        {
            FirstName = "Melissa",
            LastName = "Lewis",
            BirthDate = new DateTime(1999, 1, 26),
            Username = "cmdrlewis"
        });

        users.Add(new Models.User()
        {
            FirstName = "Rick",
            LastName = "Martinez",
            BirthDate = new DateTime(1998, 4, 5),
            Username = "mmartinez"
        });

        return Ok(users);
    }

    [HttpPost]
    [Route("add")]
    public IHttpActionResult Add(User user)
    {
        return Ok();
    }
}

Note that, because we've already included the ActionFilter and the DelegatingHandler in the pipeline, the Controller doesn't explicitly call them anywhere.

Let's have Postman submit the GET request. Here's what that looks like:

As you can see, we get back the users as a collection under the property Result, with no error messages.

Now let's submit a valid request to add a user. Here's the JSON we'll be submitting:

{
    "FirstName": "Beth",
    "LastName": "Johanssen",
    "BirthDate": "2004-5-5",
    "Username": "bjohanssen"
}

And here's the response we get from Postman:

Postman is showing no errors, with status code 200 OK and no result (remember, the controller action didn't do anything except return Ok();).

Now let's submit invalid JSON, like this:

{
    "FirstName": "Beth",
    "LastName": "",
    "BirthDate": "2004-5-5",
    "Username": "bjoha"
}

This JSON object violates two of our custom validation rules:

  • The last name must not be blank AND
  • The username must be at least 8 characters long.

Here's the Postman response for that JSON:

This is, of course, what we expect. We violated two validation rules, and therefore got back two errors. Success!

Summary

This architecture allows us to do the validation on the Web API side of the app, and automatically include all validation errors in the response. We need two NuGet packages and three major pieces to implement this solution. The NuGet packages are FluentValidation.WebApi and Newtonsoft.Json, and the other pieces are as follows:

  • A ResponsePackage class which will have properties for both the result and the errors collection.
  • An ActionFilterAttribute that validates the ModelState and returns an error if it is invalid.
  • A DelegatingHandler which wraps all responses from the API into the ResponsePackage class.

I've got a sample project over on GitHub, go check it out, and let me know if it helps you with your projects in the comments.

Happy Coding!

What is the ModelState? - ASP.NET MVC Demystified

Ever wondered just what the ModelState was that keeps popping up in your ASP.NET MVC controllers? So did I. Let's break down what the ModelState is and why we use it.

What is ModelState?

ModelState is a property of a Controller, and can be accessed from those classes that inherit from System.Web.Mvc.Controller.

The ModelState represents a collection of name and value pairs that were submitted to the server during a POST. It also contains a collection of error messages for each value submitted. Despite its name, it doesn't actually know anything about any model classes, it only has names, values, and errors.

ModelState has two purposes: to store the value submitted to the server, and to store the validation errors associated with those values.

But that's the boring explanation. Show me the code!

The Setup

First, we have the AddUserVM view model:

ViewModels/Home/AddUserVM.cs
public class AddUserVM  
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
}

Next, we have a simple view:

Views/Home/Add.cshtml
@model ModelStateDemo.ViewModels.Home.AddUserVM
<h2>Add</h2>

@using(Html.BeginForm())
{
    <div>
        <div>
            @Html.TextBoxFor(x => x.FirstName)
        </div>
        <div>
            @Html.TextBoxFor(x => x.LastName)
        </div>
        <div>
            @Html.TextBoxFor(x => x.EmailAddress)
        </div>
        <div>
            <input type="submit" value="Save" />
        </div>
    </div>
}

Finally, we have the controller actions:

Controllers/HomeController.cs
...
[HttpGet]
public ActionResult Add()  
{
    AddUserVM model = new AddUserVM();
    return View(model);
}

[HttpPost]
public ActionResult Add(AddUserVM model)  
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }
    return RedirectToAction("Index");
}

When we submit the form to the POST action, all of the values we entered will show up in the AddUserVM instance. But how did they get there?

The ModelStateDictionary Class

Let's look at the rendered HTML form for the Add page:

<form action="/Home/Add" method="post">  
    <div>
        <div>
            <label for="FirstName">First Name:</label>
            <input id="FirstName" name="FirstName" type="text" value="">
        </div>
        <div>
            <label for="LastName">Last Name:</label>
            <input id="LastName" name="LastName" type="text" value="">
        </div>
        <div>
            <label for="EmailAddress">Email Address:</label>
            <input id="EmailAddress" name="EmailAddress" type="text" value="">
        </div>
        <div>
            <input type="submit" value="Save">
        </div>
    </div>
</form>  

In a POST, all values in <input> tags are submitted to the server as key-value pairs. When MVC receives a POST, it takes all of the post parameters and adds them to a ModelStateDictionary instance. When debugging the controller POST action in Visual Studio, we can use the Locals window to investigate this dictionary:

The Values property of the ModelStateDictionary contains instances that are of type System.Web.Mvc.ModelState. What does a ModelState actually contain?

What's in a ModelState?

Here's what those values look like, from the same debugger session:

Each of the properties has an instance of ValueProviderResult that contains the actual values submitted to the server. MVC creates all of these instances automatically for us when we submit a POST with data, and the POST action has inputs that map to the submitted values. Essentially, MVC is wrapping the user inputs into more server-friendly classes (ModelState and ValueProviderResult) for easier use.

There's still two important properties that we haven't discussed, though: the ModelState.Errors property and the ModelStateDictionary.IsValid property. They're used for the second function of ModelState: to store the errors found in the submitted values.

Validation Errors in ModelState

Let's change our AddUserVM class:

ViewModels/Home/AddUserVM.cs
public class AddUserVM  
{
    [Required(ErrorMessage = "Please enter the user's first name.")]
    [StringLength(50, ErrorMessage = "The First Name must be less than {1} characters.")]
    [Display(Name = "First Name:")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Please enter the user's last name.")]
    [StringLength(50, ErrorMessage = "The Last Name must be less than {1} characters.")]
    [Display(Name = "Last Name:")]
    public string LastName { get; set; }

    [EmailAddress(ErrorMessage = "The Email Address is not valid")]
    [Required(ErrorMessage = "Please enter an email address.")]
    [Display(Name = "Email Address:")]
    public string EmailAddress { get; set; }
}

We've added validation attributes, specifically Required, StringLength, and EmailAddress. We've also set the error messages that are to be displayed if the corresponding validation errors occur.

With the above changes in place, let's modify the Add view to display the error messages if they occur:

Views/Home/Add.cshtml
@model ModelStateDemo.ViewModels.Home.AddUserVM

<h2>Add</h2>

@using(Html.BeginForm())
{
    @Html.ValidationSummary()
    <div>
        <div>
            @Html.LabelFor(x => x.FirstName)
            @Html.TextBoxFor(x => x.FirstName)
            @Html.ValidationMessageFor(x => x.FirstName)
        </div>
        <div>
            @Html.LabelFor(x => x.LastName)
            @Html.TextBoxFor(x => x.LastName)
            @Html.ValidationMessageFor(x => x.LastName)
        </div>
        <div>
            @Html.LabelFor(x => x.EmailAddress)
            @Html.TextBoxFor(x => x.EmailAddress)
            @Html.ValidationMessageFor(x => x.EmailAddress)
        </div>
        <div>
            <input type="submit" value="Save" />
        </div>
    </div>
}

Notice the two helpers we are using now, ValidationSummary and ValidationMessageFor.

  • ValidationSummary reads all errors from the model state and displays them in a bulleted list.
  • ValidationMessageFor displays only errors for to the property specified.

Let's see what happens when we attempt to submit an invalid POST that is missing the email address. When we get to the POST action while debugging, we have the following values in our ModelStateDictionary:

Note that the ModelState instance for the email address now has an error in the Errors collection. When MVC creates the model state for the submitted properties, it also goes through each property in the ViewModel and validates the property using attributes associated to it. If any errors are found, they are added to the Errors collection in the property's ModelState.

Also note that IsValid is false now. That's because an error exists; IsValid is false if any of the properties submitted have any error messages attached to them.

What all of this means is that by setting up the validation in this manner, we allow MVC to just work the way it was designed. ModelState stores the submitted values, allows them to be mapped to class properties (or just as parameters to the action) and keeps a collection of error messages for each property. In simple scenarios, this is all we need, and all of it is happening behind the scenes!

Custom Validation

But what if we needed to perform more complex validation than what is provided by attributes? Say we needed to validate that the first and last names are not identical, and display a particular error message when this happens.

We can actually add errors to the model state via the AddModelError method on ModelStateDictionary:

[HttpPost]
public ActionResult Add(AddUserVM model)  
{
    if(model.FirstName == model.LastName)
    {
        ModelState.AddModelError("LastName", "The last name cannot be the same as the first name.");
    }
    if(!ModelState.IsValid)
    {
        return View(model);
    }
    return RedirectToAction("Index");
}

The first parameter to the AddModelError method is the name of the property that the error applies to. In this case, we set it to LastName. You could also set it to nothing (or a fake name) if you just want it to appear in the ValidationSummary and not in a ValidationMessage.

Now the error will be displayed on the page:

Summary

The ModelState represents the submitted values and errors in said values during a POST. The validation process respects the attributes like Required and EmailAddress, and we can add custom errors to the validation if we so desire. ValidationSummary and ValidationMessageFor read directly from ModelState to display errors to the user.

For a bit more info, check out Professional ASP.NET MVC 5, specifically Chapter 6, which details validation methods using the ModelState.

I've also got a very simple sample project on Github that demonstrates how the ModelState works and provides all the code and markup in this post. Take a look!

Happy Coding!