Validate ModelState.IsValid globally for all controllers - c#

In my ASP.NET Core Controllers I always check if the ModelState is valid:
[HttpPost("[action]")]
public async Task<IActionResult> DoStuff([FromBody]DoStuffRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest("invalid parameters");
}
else
{
return Ok("some data"));
}
}
Is there a way to check validity of the ModelState globally using a filter so I don't have to do this in every API item in every controller again? It would be nice if the action could rely on the modelstate being valid and not needing to check:
[HttpPost("[action]")]
public async Task<IActionResult> DoStuff([FromBody]DoStuffRequest request)
{
return Ok("some data"));
}

As a followup to this: in ASP.NET Core 2.1, there is a controller attribute called [ApiController]. If you include that attribute, it automatically uses a built-in ActionFilter called ModelStateInvalidFilter, meaning any custom action filter that checks ModelState will never be reached since the [ApiController] attribute already registers its own filter.
To suppress the behavior, the current documents give this option:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true; // This is the setting
});

You can use a ActionFilter. It's not globally, but it moves the problem from your method body into an attribute. I realize that it doesn't solve your problem completely, but it might be better than nothing.
public class ModelStateValidationActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
And in your controller:
[HttpPost]
[ModelStateValidationActionFilter]
public IHttpActionResult Post(object model)
{
}
I believe that you can set it on your controller as well. I haven't actually tried that, but according to this it could work.
[ModelStateValidationActionFilter]
public class MyApiController : ApiController
{
}
EDIT:
As #Camilo Terevinto mentioned it is a bit different for Core. Just use this ActionFilter if you want to use Core.
public class ModelStateValidationActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var modelState = context.ModelState;
if (!modelState.IsValid)
context.Result = new ContentResult()
{
Content = "Modelstate not valid",
StatusCode = 400
};
base.OnActionExecuting(context);
}
}

The existing answers so far are for ASP.NET Web API and not for ASP.NET Core. The actual way to do it in ASP.NET Core is:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
And you can register this filter globally in Startup.cs, so this will execute in every single call and you do not have to repeat it in every Action/Controller:
options.Filters.Add(typeof(SampleActionFilter));
See more in the official documentation.

For ASP.NET Core 2.0, to avoid applying attributes to all Controllers or Actions individually;
Define a filter:
namespace Test
{
public sealed class ModelStateCheckFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context) { }
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
}
Then in your Startup.cs, add it as filter:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.Filters.Add(new ModelStateCheckFilter());
});
}

Use HandleInvalidModelState
PM> Install-Package HandleInvalidModelState
example
[HttpPost]
[TypeFilter(typeof(HandleInvalidModelWithViewActionFilterAttribute))]
public IHttpActionResult Post(object model)
{}
Besides basic cases scenario (returning view with invalid model) package supports returning Json and Redirection of request.
disclaimer : author of the package

For async methods the filter has to implement IAsyncActionFilter. I have put together this filter, which:
does nothing if the ModelState is valid
OR
sets the Result to BadRequestObjectResult with some details about what failed
logs the ModelState with more details
public class ValidModelStateAsyncActionFilter : IAsyncActionFilter
{
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/loggermessage?view=aspnetcore-2.1
private static readonly Action<ILogger, IList<(string Key, string ErrorMessage, string ExceptionMessage)>, Exception> ModelStateLoggerAction;
private readonly ILogger<ValidModelStateAsyncActionFilter> logger;
static ValidModelStateAsyncActionFilter()
{
ModelStateLoggerAction = LoggerMessage.Define<IList<(string Key, string ErrorMessage, string ExceptionMessage)>>(LogLevel.Warning, new EventId(1, nameof(ValidModelStateAsyncActionFilter)), "{ModelState}");
}
public ValidModelStateAsyncActionFilter(ILogger<ValidModelStateAsyncActionFilter> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.ModelState.IsValid)
await next();
this.LogModelState(context);
context.Result = new BadRequestObjectResult(GetErrorResponse(context));
}
private static ErrorResponse GetErrorResponse(ActionContext context)
{
return new ErrorResponse
{
ErrorType = ErrorTypeEnum.ValidationError,
Message = "The input parematers are invalid.",
Errors = context.ModelState.Values.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
.Where(x => !string.IsNullOrEmpty(x))
.ToList()
};
}
private void LogModelState(ActionContext context)
{
// credit: https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/
var items = from ms in context.ModelState
where ms.Value.Errors.Any()
let fieldKey = ms.Key
let errors = ms.Value.Errors
from error in errors
select (Key: fieldKey, ErrorMessage: error.ErrorMessage, ExceptionMessage: error.Exception.Message);
ModelStateLoggerAction(this.logger, items.ToList(), null);
}
}

Related

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

I have ASP.NET Web API controller with some actions (methods). Let's say something like this:
[HttpPost]
public async Task<ActionResult> SavePerson([FromBody]PersonDto person)
{
await _mediatr.Send(new SavePerson.Command(person));
return Ok();
}
and the PersonDto looks something like this:
public record PersonDto([Required, MinLength(3)]string Name, int? Age);
When I call my Web API action 'SavePerson' with invalid person data (Name.Length < 3 and etc...), ASP.NET Core model binding validation interrupts the execution and returns 400 (Bad Request) as it should. When I pass valid person data, it works fine.
My questions are:
How can I catch this model binding validation result (400 Bad Request) and transform it into different format, so our front-end developers will be happy?
Should I validate my DTOs (PersonDto) in Web API layer or it's better to validate it in MediatR command handler? I'm trying to adhere Uncle Bob's Clean Architecture. I have Domain, Application, Infrastructure, Web API. And my MediatR CQRS handlers are placed in the Application layer.
Automatic 400 bad request responses is enabled by default. To disable it use the following code in Startup ConfigureServices method:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Then you can handle invalid model states manually like this:
[HttpPost]
public async Task<ActionResult> SavePerson([FromBody]PersonDto person)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);// or what ever you want
await _mediatr.Send(new SavePerson.Command(person));
return 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;
}
}
You can perform ModelState.isValid() in the biginig of your Api method and return a BadRequestResult() if the model is invalid. You can can return the validation errors along with BadRequestResult.
You need to get validation errors from model state and fill your custom error object. This way your customers can see more meaningful errors.

How to avoid calling ModelState.IsValid on every PostBack?

I pretty much always want to check if ModelSate.IsValid is called when I do a postback. And having to check at the start of every post back violates the DRY principle, is there a way to have it checked automatically?
Example:
[HttpPost("RegisterUser")]
[AllowAnonymous]
public async Task<IActionResult> RegisterUser([FromBody] UserRegisterViewModel vmodel)
{
if(!ModelState.IsValid) // This code is repeated at every postback
return ModelInvalidAction(); // Is there a way to avoid having to write it down?
// do other things
return StatusCode(201);
}
The framework provides an abstract ActionFilterAttribute that you can subclass.
You can use an action filter to automatically validate model state and return any errors if the state is invalid:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
You can either then use it on individual actions or register it globally
Reference Asp.Net Core : Action Filters
You can try something like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
filterContext.Result = new BadRequestResult();
}
}
}
You can request any registered service like this filterContext.HttpContext.RequestServices.GetService<ILogger>().
You can decorate by action filter your action or controller:
[HttpPost("RegisterUser")]
[AllowAnonymous]
[ValidateModel]
public async Task<IActionResult> RegisterUser([FromBody] UserRegisterViewModel vmodel)
{
...
}
I've researched this and found the best answer I think. Even if I implement what's mentioned in the other answers, I'll still be repeating myself by having to put a [ValidateModel] attribute on each POST and PUT request, that's something I want to avoid, I would also like to log things if a model is invalid, other answers don't really allow for this. So here is my answer:
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class ValidateViewModelAttribute : Attribute, IFilterFactory
{
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetService<ILogger>();
return new InternalValidateModel(logger);
}
private class InternalValidateModel : IActionFilter
{
private ILogger _log;
public InternalValidateModel(ILogger log)
{
_log = log;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (IsInvalidModelState(context))
{
_log.Information("Invalid ModelState: {Model}", context.ModelState.ErrorMessages());
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
private bool IsInvalidModelState(ActionExecutingContext context)
{
var method = context.HttpContext.Request.Method;
return (method == "POST" ||
method == "PUT") &&
!context.ModelState.IsValid;
}
}
public bool IsReusable => true;
}
I don't want to repeat myself by having to add a [ValidateViewModel] on every POST and PUT. So I do the following:
services.AddMvc(config =>
{
config.Filters.Add(new ValidateViewModelAttribute());
});
Now all POST and PUT methods are validated!!

Handle validation failure in exception filter, and re-render view

I'm using ASP.NET Core and FluentValidation.
When a POST action receives invalid input, it's customary to re-render the input form view, with validation errors:
if (!ModelState.IsValid)
return View("nameOfViewRenderedByGetAction", model);
But my validation is actually performed in a service, by FluentValidation, which throws ValidationException. I want to handle it in an exception filter:
public class ValidationFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// only handle ValidationException
var ex = context.Exception as ValidationException;
if (ex == null) return;
// re-render get action's view, or redirect to get action
// ??
}
}
I'm stuck at the "??" part, because Core has changed the signatures of many types, and ExceptionContext doesn't surface the data I need to make this work.
How do I do this?
It's a little late for an answer but I have a working solution for exactly the same application design. I use ASP.NET Core 3.0 and FluentValidation 8.x.
public class MvcValidationExceptionFilterAttribute : ExceptionFilterAttribute
{
private IModelMetadataProvider ModelMetadataProvider { get; }
public MvcValidationExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
ModelMetadataProvider = modelMetadataProvider;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Framework calls without null")]
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException ex)
{
var validationResult = new ValidationResult(ex.Errors);
validationResult.AddToModelState(context.ModelState, null);
context.Result = new ViewResult { ViewData = new ViewDataDictionary(ModelMetadataProvider, context.ModelState) };
context.ExceptionHandled = true;
}
}
}
As this filter has a dependency we can't use the Attribute directly but register it with dependency injection in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<MvcValidationExceptionFilterAttribute>();
To use the ExceptionFilter either apply it via the ServiceFilterAttribute:
[ServiceFilter(typeof(MvcValidationExceptionFilterAttribute))]
public class MyController : Controller
{
Or apply it globally in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<MvcValidationExceptionFilterAttribute>();
})
From an exception filter, You can render a custom view by setting the context result.
public class ValidationFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// only handle ValidationException
var ex = context.Exception as ValidationException;
if (ex == null) return;
// re-render get action's view, or redirect to get action
var result = new ViewResult { ViewName = "GetView" }
context.HttpContext.Response.Clear();
context.Result = result;
}
}
Where GetView should be the name of your Get action's view.
Sample exception filter that uses a custom developer error view to display details about exceptions.
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new ViewResult {ViewName = "CustomError"};
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState);
result.ViewData.Add("Exception", context.Exception);
// TODO: Pass additional detailed data via ViewData
context.Result = result;
}
}
Note that the above code is sending the context, model state and exception to the view.
In case all you need is custom error page refer to ASP.NET Core Error Handling
Generally, you should not be using an exception filter to turn an error into success. Consider using an action filter if you have a requirement like that.
Having said that, for some reason if you still need to redirect from an exception filter, this is how it can be done
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
}
public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Home", action = "Error" }));
context.Result = result;
}
}

How can I consolidate all of my "setup code" away from my WebAPI controllers?

All of my controllers look like this:
[HttpPut]
[Route("api/businessname")]
[Authorize]
public HttpResponseMessage UpdateBusinessName(BusinessNameDto model) {
if (!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
try {
_userService.UpdateBusinessName(User.Identity.Name, model.BusinessName);
return Request.CreateResponse(HttpStatusCode.OK, new ApiResponseDto() {});
} catch (Exception e) {
// logging code
//return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
return Request.CreateResponse(HttpStatusCode.OK, new ApiResponseDto() { Success = false, Error = "Something bad happened :(" });
}
}
There's a lot of repeated stuff across my controllers. Ideally I could just have this:
[HttpPut]
[Route("api/businessname")]
[Authorize]
public HttpResponseMessage UpdateBusinessName(BusinessNameDto model) {
_userService.UpdateBusinessName(User.Identity.Name, model.BusinessName);
return Request.CreateResponse(HttpStatusCode.OK, new ApiResponseDto() {});
}
And tell WebAPI to do all that other stuff with every controller... but I don't know if that's possible. How can I make that happen?
You ca do the following:
1) Create a validation filter so that your action method executes ONLY if the model state is valid. So that you don't need to check ModelState.IsValid anymore in your action methods.
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
ModelStateDictionary modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, modelState);
}
}
}
2) Create a exception handling filter which will catch any exception thrown by the action method, serialize it and create a HTTP BadRequest response message to the client. So that you don't have to use try catch anymore in your action methods.
public class HandleExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var responseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest);
responseMessage.Content = new StringContent(context.Exception.Message);
context.Response = responseMessage;
}
}
You can register these filters in WebApiConfig.cs by adding the below lines to it
config.Filters.Add(new ValidationActionFilter());
config.Filters.Add(new HandleExceptionFilter());
UPDATE: To be more specific to SB2055's scenario, i am adding the below code
public class HandleExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var model = new ApiResponseDto() { Success = false, Error = context.Exception.Message })
context.Response = context.Request.CreateResponse(HttpStatusCode.OK,
model);
}
}
You can use ActionFilterAttributes. For example, to validate requests you can create a class similar to this:
public class ValidateRequestAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)
}
}
}
You could then selectively apply the filter to your Web API actions by decorating them with the attribute:
[ValidateRequest]
[HttpPut]
[Route("api/businessname")]
[Authorize]
public HttpResponseMessage UpdateBusinessName(BusinessNameDto model) {
...
}
Or apply them to all your actions during Web API setup using HttpConfiguration.Filters:
config.Filters.Add(new ValidateRequestAttribute());

How can I centralize modelstate validation in asp.net mvc using action filters?

I write this code in several places and always repeat this logic:
public ActionResult MyMethod(MyModel collection)
{
if (!ModelState.IsValid)
{
return Json(false);//to read it from javascript, it's always equal
}
else
{
try
{
//logic here
return Json(true);//or Json(false);
}
catch
{
return Json(false);//to read it from javascript, it's always equal
}
}
}
Is there any way using action filters, not to be repeating the try-catch, ask if the model is valid and return Json(false) as ActionResult?
To conform with REST, you should return http bad request 400 to indicate that the request is malformed (model is invalid) instead of returning Json(false).
Try this attribute from asp.net official site for web api:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
A version for asp.net mvc could be like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
}
If you want to do this in MVC6 or Mvc Core and without specifying your attribute on all of your Action methods then this is how you do it.
First create your ActionFilter
public class ModelStateValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext context )
{
if ( context.HttpContext.Request.Method == "POST" && !context.ModelState.IsValid )
context.Result = new BadRequestObjectResult( context.ModelState );
}
}
Now create a convention in which you will apply this ActionFilter to all of your controllers.
public class ModelStateValidatorConvension : IApplicationModelConvention
{
public void Apply( ApplicationModel application )
{
foreach ( var controllerModel in application.Controllers )
{
controllerModel.Filters.Add( new ModelStateValidationFilterAttribute() );
}
}
}
And the last thing is to register this convention in MVC
public void ConfigureServices( IServiceCollection services )
{
services.Configure<MvcOptions>( x => x.Conventions.Add( new ModelStateValidatorConvension() ) );
}
Starting from ASP.Net Core 2.1, the [ApiController] attribute will trigger automatic model validation:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
Consequently, the following code is unnecessary in action methods or custom ActionFilterAttribute:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Source: https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.1#automatic-http-400-responses-1
Here is how to use the code from Khanh TO (from asp.net official site):
To apply this filter to all Web API controllers, add an instance of the filter to the HttpConfiguration.Filters collection during configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
Another option is to set the filter as an attribute on individual controllers or controller actions:
public class ProductsController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(Product product)
{
// ...
}
}
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ViewResult();
}
}
}

Categories