Handle Modelstate Validation in ASP.NET Web API

Handle ModelState Validation in ASP.NET Web API

For separation of concern, I would suggest you use action filter for model validation, so you don't need to care much how to do validation in your api controller:

using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace System.Web.Http.Filters
{
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;

if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}

Model State Validation in Web API

When [ApiController] attribute is applied ,ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body :

Automatic HTTP 400 responses

To disable the automatic 400 behavior, set the SuppressModelStateInvalidFilter property to true :

services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});

Then you can create globally action filter to check the ModelState.IsValid and add your custom model validation ,something like :

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

And register it globally to apply the filter to each request .

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

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

ModelState is always valid in ASP.NET.CORE 2.2 Web Api

Model state validation is not happening (or it's right to say that model binding does not happening) during unit-testing. This article describes some ways to implement what you want

How to make Web API return all errors for the validated model?

Considering your scenario, it can be said that,

In InvalidModelStateResponseFactory there is a constructor callled ModelStateDictionary() under this you would get ErrorCount which can get you number of error.

But if you are expecting you would get all the error details together, you cannot do that. It will always fetch first one and terminates execution and entered to your custom InvalidModelStateResponseFactory middleware.

Note: So when any error encounters it terminates and return rather executing further as a results we always get first one. You
could get our official document here

Hope it would help you to guide you through.

.NET Core Web API model validation

You can use the Validator object to manually test objects:

var obj = Mapper.Map(request);
var context = new ValidationContext(obj, serviceProvider: null, items: null);
var results = new List<ValidationResult>();

var isValid = Validator.TryValidateObject(obj, context, results);

See this short tutorial for more information.

Note that this does not work recursively, depending on the complexity of AnotherObject, you may need to use Reflection to recurse through the object yourself.

asp net core web api custom model validation

The link shows how we have to disable the default Model Binding Exception filter that is returning a 400 error and short circuits the pipeline. Only then the OnActionExecuting method in our custom ActionFilterAttribute is actually getting executed. Everything then works as expected.

https://stackoverflow.com/a/51522487/14924779

How can I wrap failed model validation result of an ASP.NET Core 5 Web API controller action into another class and return response as OK

You can use Jason Taylor's Clean Architecture approach. Instead of using attribute validation, use FluentValidation:

public class CreatePersonCommandValidator : AbstractValidator<SavePerson.Command>
{
public CreatePersonCommandValidator()
{
RuleFor(v => v.Title)
.NotEmpty().WithMessage("Title is required.")
.MinimujLength(200).WithMessage("Title at least should have 3 characters.");
}
}

Use MediatR behavior to perform validation and translate errors into validation exception:

public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;

public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}

public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);

var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();

if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}

Validation exception:

public class ValidationException : Exception
{
public ValidationException()
: base("One or more validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}

public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}

public IDictionary<string, string[]> Errors { get; }
}

And finally, implement an exception filter or exception handling middleware to catch that exception and return desired response:

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{

private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;

public ApiExceptionFilterAttribute()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(ValidationException), HandleValidationException }
};
}

public override void OnException(ExceptionContext context)
{
HandleException(context);

base.OnException(context);
}

private void HandleException(ExceptionContext context)
{
Type type = context.Exception.GetType();
if (_exceptionHandlers.ContainsKey(type))
{
_exceptionHandlers[type].Invoke(context);
return;
}

if (!context.ModelState.IsValid)
{
HandleInvalidModelStateException(context);
return;
}

HandleUnknownException(context);
}

private void HandleValidationException(ExceptionContext context)
{
var exception = context.Exception as ValidationException;

//var details = new ValidationProblemDetails(exception.Errors)
//{
//Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
//};

context.Result = Returns your response type //new BadRequestObjectResult(details);

context.ExceptionHandled = true;
}
}


Related Topics



Leave a reply



Submit