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
How to Update a Table Using Oledb Parameters
Why Firefox Requires Geckodriver
Windows Application Startup Error Exception Code: 0Xe0434352
"Self Referencing Loop Detected" Exception with JSON.Net
Token Based Authentication in ASP.NET Core
How to Return a Value from a Form in C#
What Does $ Mean Before a String
How to Update the Entityset - Because It Has a Definingquery and No <Updatefunction> Element Exist
How to Copy Data to Clipboard in C#
How Exactly Do Static Fields Work Internally
How to Unserialize PHP Serialized Array/Variable/Class and Return Suitable Object in C#
Does C++11 Have C#-Style Properties
Why Does the Default Parameterless Constructor Go Away When You Create One with Parameters
How Do Prefix (++X) and Postfix (X++) Operations Work
Sharing Memory Between Two Applications
Creating a Zip Archive in Memory Using System.Io.Compression