ASP.NET Core – Checking ModelState.IsValid is boring

Checking if a model is valid is pretty an automatic behavior at this point. Whenever a request expects a model, we need to make sure that the validation is good, and we use ModelState.IsValid for that.

But that becomes boring, most of the time (if not always) we redirect to the same page, another page or return a bad request in case of an API.
Why not use an Action Filter to make this easier?

How model validation works

Setup

Imagine you have this ViewModel,:

public class CodeViewModel
{

    [Required]
    public string UserId { get; set; }

    [Required]
    [StringLength(40, MinimumLength = 40)]
    public string Code { get; set; }

}

This action in your controller:

[HttpGet]
public IActionResult ConfirmEmail(CodeViewModel model)
{
    // Logic here
    return View();
}

Model Binding

Model binding is a step that happens after ASP.NET Core receives an HTTP request and has figured out which action/controller (or razor page) that will handle it.

It simply tries to bind the route data (e.g. query data if it’s a GET request and body data if it’s a POST) and checks whether the parameters you’re waiting for (either direct parameters or objects) are what was received in the request.

Model binding errors are mostly casting/conversion errors. For example you’re waiting for an integer userId but you receive a string containing ”test”.

Model validation

Now when model binding is done, the model validation starts (if defined). ASP.NET Core looks at your ViewModel, checking if it has any validation attributes and applies them. Some of the much used attributes are:

Name Description
Required A value for this property is required
DataType Specifies the type of the property, e.g. EmailAddress
Range Specifies the range of the property, works on numbers
Compare Compares the value of the property with another one, used to confirm passwords/emails

ModelState

The Controller class provides a ModelState that you can check to see if the model binding and/or model validation succeeded or not, with the list of errors generated.

if (!ModelState.IsValid)
{
    // Do something about it!
    // Usually return the user to the same page
    // while showing the errors.
}

Using an ActionFilter to check the ModelState for you

After a couple of years working with ASP.NET Core, I got bored of having to check if the ModelState is valid in almost every request, so I searched a bit and found out that Action Filters.

Action filters execute after model validation, so it’s very handy as we can access the ModelState while working with them.

ASP.NET MVC Lifecycle - order of execution

ASP.NET MVC Lifecycle - order of execution

IfModelIsInvalid attribute

I ended up creating a IfModelIsInvalid attribute that checks if the ModelState is invalid, if yes, it redirects to either an action/controller or a page (in case you’re working with a mixed project that includes Razor Pages).

public class IfModelIsInvalidAttribute : ActionFilterAttribute
{

    #region Properties

    public string RedirectToController { get; set; }

    public string RedirectToAction { get; set; }

    public string RedirectToPage { get; set; }

    #endregion

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new RedirectToRouteResult(ConstructRouteValueDictionary());
        }
    }

    #region Private Methods

    private RouteValueDictionary ConstructRouteValueDictionary()
    {
        var dict = new RouteValueDictionary();

        if (!string.IsNullOrWhiteSpace(RedirectToPage))
        {
            dict.Add("page", RedirectToPage);
        }
        // Assuming RedirectToController & RedirectToAction are set
        else
        {
            dict.Add("controller", RedirectToController);
            dict.Add("action", RedirectToAction);
        }

        return dict;
    }

    #endregion
}

The code is pretty simple and straight forward.
We check context.ModelState.IsValid to see if something failed, if yes, we redirect the user to either an action/controller or a razor page.

To set the result of the request to a redirect in an Action Filter, we have to create a RedirectToRouteResuilt that contains a RouteValueDictionary containing where we want to redirect.

Usage

Lastly, the time comes where you forget about checking the ModelState in every request and use the attribute:

[HttpGet]
[IfModelIsInvalid(RedirectToAction = "Index", RedirectToController = "Account")]
public IActionResult ConfirmEmail(CodeViewModel model)
{
    // Logic here
    return View();
}

If something fails on the model validation, the user will be redirected to the Index action of the Account controller.

This results in a pretty readable solution that saves us the trouble to write the same code on every request.
Of course, the Action Filter can be personalized however you like!

Zanid Haytam Written by:

Zanid Haytam is an enthusiastic programmer that enjoys coding, reading code, hunting bugs and writing blog posts.

comments powered by Disqus