How to do model validation in every method in ASP.NET Core Web API?

PassionateDeveloper picture PassionateDeveloper · May 23, 2018 · Viewed 25.5k times · Source

I am getting into ASP.NET Core 2.0 with Web API. One of my first methods are my login:

/// <summary>
/// API endpoint to login a user
/// </summary>
/// <param name="data">The login data</param>
/// <returns>Unauthorizied if the login fails, The jwt token as string if the login succeded</returns>
[AllowAnonymous]
[Route("login")]
[HttpPost]
public IActionResult Login([FromBody]LoginData data)
{
    var token = _manager.ValidateCredentialsAndGenerateToken(data);
    if (token == null)
    {
        return Unauthorized();
    }
    else
    {
        return Ok(token);
    }
}

My LoginData using DataAnnotations:

public class LoginData
{
    [Required]
    [MaxLength(50)]
    public string Username { get; set; }

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

    [Required]
    [MaxLength(16)]
    public string IpAddress { get; set; }
}

So my ModelState is well filled automatically when the login happens and e.g. the password is empty (of course on client side there should be a validation too for it later).

What is the best way to

  • check the model state,
  • getting a readable string out of all errors and
  • return a BadRequest with this error?

Of course I could write it all myself in a helper method. But I thought about a filter maybe?

Answer

Nkosi picture Nkosi · May 23, 2018

How to check the model state?

Check the controller's ModelState in the action to get the state of the model.

getting a readable string out of all errors and return a BadRequest with this error?

Use BadRequest(ModelState) to return HTTP bad request response which will inspect the model state and construct message using errors.

Completed code

/// <summary>
/// API endpoint to login a user
/// </summary>
/// <param name="data">The login data</param>
/// <returns>Unauthorizied if the login fails, The jwt token as string if the login succeded</returns>
[AllowAnonymous]
[Route("login")]
[HttpPost]
public IActionResult Login([FromBody]LoginData data) {
    if(ModelState.IsValid) {
        var token = _manager.ValidateCredentialsAndGenerateToken(data);
        if (token == null) {
            return Unauthorized();
        } else {
            return Ok(token);
        }
    }
    return BadRequest(ModelState);
}

Of course I could write it all myself in a helper method... But I thought about a filter maybe?

To avoid the repeated ModelState.IsValid code in every action where model validation is required you can create a filter to check the model state and short-circuit the request.

For example

public class ValidateModelAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext context) {
        if (!context.ModelState.IsValid) {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Can be applied to the action directly

[ValidateModel] //<-- validation
[AllowAnonymous]
[Route("login")]
[HttpPost]
public IActionResult Login([FromBody]LoginData data) {
    var token = _manager.ValidateCredentialsAndGenerateToken(data);
    if (token == null) {
        return Unauthorized();
    } else {
        return Ok(token);
    }    
}

or added globally to be applied to all request where model state should be checked.

Reference Model validation in ASP.NET Core MVC