65.9K
CodeProject is changing. Read more.
Home

ASP.NET Core Global Model Validation

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2 votes)

Aug 1, 2017

MIT
viewsIcon

10311

In this article, you will learn about ASP.NET Core global model validation

Introduction

Have you ever been writing these codes all the time:

[HttpPost]
public IActionResult Create(Movie movie)
{
    if (ModelState.IsValid)
    {
        // Do something
        // ...
        return Json(..your data...);
    }
    return Json(..error...);
}

But if we can do something much better like:

[HttpPost]
public IActionResult Create(Movie movie)
{
    // Do something
    // ...
    return Json(..your data...);
}

Using the Code

When creating ASP.NET Core (.NET Core/.NET Framework) project, we choose Web API project template.

To implement global model validation, we implement these classes/interfaces:

  • ActionFilterAttribute: Check ModelState.IsValid and throw exception if there is any error
  • ModelStateDictionary: Read all model errors
  • IExceptionFilter: To handle global error

For the full source code, refer to my GitHub.

Implementing ActionFilterAttribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class ValidateModelStateAttribute : ActionFilterAttribute
    {
        public ValidateModelStateAttribute()
        {
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                // When ModelState is not valid, we throw exception
                throw new ValidationException(context.ModelState.GetErrors());
            }
        }
    }

Implementing ModelStateDictionary:

public static class ModelStateExtensions
{
    public static List GetErrors(this ModelStateDictionary modelState)
    {
        var validationErrors = new List();

        foreach (var state in modelState)
        {
            validationErrors.AddRange(state.Value.Errors
                .Select(error => error.ErrorMessage)
                .ToList());
        }

        return validationErrors;
    }
}

Implementing IExceptionFilter:

public class HttpGlobalExceptionFilter : IExceptionFilter
{
    public HttpGlobalExceptionFilter()
    {
    }

    public void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        var code = HttpStatusCode.InternalServerError;
        var ajaxResponse = new AjaxResponse
        {
            IsSuccess = false
        };

        if (exception is ValidationException)
        {
            code = HttpStatusCode.BadRequest;
            ajaxResponse.Message = "Bad request";
            // We can serialize exception message here instead of throwing Bad request message
        }
        else
        {
            ajaxResponse.Message = "Internal Server Error";
        }

        context.Result = new JsonResult(ajaxResponse);
        context.HttpContext.Response.StatusCode = (int)code;
        context.ExceptionHandled = true;
    }
}

Modifying ConfigureServices method in Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc(options =>
        {
            options.Filters.Add(new ValidateModelStateAttribute());
            options.Filters.Add(typeof(HttpGlobalExceptionFilter));
        })
        .AddJsonOptions(options =>
        {
            options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        });
}

Result

Now when methods are executed in controller classes, the validation process will be triggered and we can remove Model.IsValid checking.

[Route("api/[controller]")]
public class MoviesController : Controller
{
    // POST api/movies
    [HttpPost]
    public IActionResult Create([FromBody]Movie value)
    {
        return Json("OK");
    }
}