How to avoid calling ModelState.IsValid on every PostBack? - c#

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!!

Related

ASP.NET core it's possible to configure an action in controller only in development mode?

In my ASP.NET core web application, I want to have an action that runs only in development mode. In production mode, maybe a 404 error will be good enough. Is it possible to do that?
This can be achieved by injecting IHostEnvironment into your controller and using its IsDevelopment() method inside of the action itself. Here's a complete example that returns a 404 when running in anything other than the Development environment:
public class SomeController : Controller
{
private readonly IHostEnvironment hostEnvironment;
public SomeController(IHostEnvironment hostEnvironment)
{
this.hostEnvironment = hostEnvironment;
}
public IActionResult SomeAction()
{
if (!hostEnvironment.IsDevelopment())
return NotFound();
// Otherwise, return something else for Development.
}
}
If you want to apply this more globally or perhaps you just want to separate out the concerns, Daboul explains how to do so with an action filter in this answer.
For ASP.NET Core < 3.0, use IHostingEnvironment in place of IHostEnvironment.
One nice way to do it is to create a DevOnlyActionFilter filter https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2
The filter would look like that:
public class DevOnlyActionFilter : ActionFilterAttribute
{
private IHostingEnvironment HostingEnv { get; }
public DevOnlyActionFilter(IHostingEnvironment hostingEnv)
{
HostingEnv = hostingEnv;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if(!HostingEnv.IsDevelopment())
{
context.Result = new NotFoundResult();
return;
}
base.OnActionExecuting(context);
}
}
And to annotate your controller action with [TypeFilter(typeof(DevOnlyActionFilter))]
#Daboul's answer is pretty good but I didn't like the TypeFilter(typeof(x)) which feels clunky.
Turns out that implementing IFilterFactory allows for attribute filters to have DI while still being clean to use.
public class DevOnlyAttribute : Attribute, IFilterFactory
{
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new DevOnlyAttributeImpl(serviceProvider.GetRequiredService<IWebHostEnvironment>());
}
public bool IsReusable => true;
private class DevOnlyAttributeImpl : Attribute, IAuthorizationFilter
{
public DevOnlyAttributeImpl(IWebHostEnvironment hostingEnv)
{
HostingEnv = hostingEnv;
}
private IWebHostEnvironment HostingEnv { get; }
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!HostingEnv.IsDevelopment())
{
context.Result = new NotFoundResult();
}
}
}
}
Now the controller/action can be annotated with [DevOnly].

How do I make an ASP.NET Core void/Task action method return 204 No Content

How do I configure the response type of a void/Task action method to be 204 No Content rather than 200 OK?
For example, consider a simple controller:
public class MyController : Controller
{
[HttpPost("foo")]
public async Task Foo() {
await Task.CompletedTask;
}
[HttpPost("bar")]
public async Task<IActionResult> Bar() {
await Task.CompletedTask;
return NoContent();
}
}
I'd like both methods to return 204 No Content, but the default (and thus what /foo returns) seems to be 200 OK. I tried various things, such as adding a [ProducesResponseType(204)] attribute to /foo, but I haven't found anything that seems to have any effect.
Here is a solution that returns 204 instead of 200 for all controller methods that return void or Task. First, create a result filter:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using static Microsoft.AspNetCore.Http.StatusCodes;
namespace StackOverflow.SampleCode
{
/// <summary>
/// A filter that transforms http status code 200 OK to 204 No Content for controller actions that return nothing,
/// i.e. <see cref="System.Void"/> or <see cref="Task"/>.
/// </summary>
internal class VoidAndTaskTo204NoContentFilter : IResultFilter
{
/// <inheritdoc/>
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
{
var returnType = actionDescriptor.MethodInfo.ReturnType;
if (returnType == typeof(void) || returnType == typeof(Task))
{
context.HttpContext.Response.StatusCode = Status204NoContent;
}
}
}
/// <inheritdoc/>
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
}
Then register the filter globally:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options => options.Filters.Add<VoidAndTaskTo204NoContentFilter>());
}
This will affect all your controller methods.
A workable solution provided by Nish26.
But if you don't want to clog up the controller by an excess code then another option is to create a ResultFilter:
public class ResponseCodeAttribute : Attribute, IResultFilter
{
private readonly int _statusCode;
public ResponseCode(int statusCode)
{
this._statusCode = statusCode;
}
public void OnResultExecuting(ResultExecutingContext context)
{
}
public void OnResultExecuted(ResultExecutedContext context)
{
context.HttpContext.Response.StatusCode = _statusCode;
}
}
And then use it with the action method:
[ResponseCode(204)]
[HttpPost("foo")]
public async Task Foo() {
await Task.CompletedTask;
}
BTW, ProducesResponseType is just an attribute that helps to create an API metadata.
First of all, I don't think this is a good idea. Controller actions should be testable without creating the entire pipeline. You can test Bar() to ensure it returns 204 simply by checking the return result. You can't do that with an action that modifies the return result through filters.
That said, it is possible to modify the result by using Result Filters, attributes that implement the IResultFilter, IAsyncResultFilter interfaces. There's also an abstract ResultFilterAttribute class that implements both interfaces provides an implementation for IAsyncResultFilter that calls the IResultFilter methods.
You could create an attribute that modifies the status code like this :
public class ResponseCodeAttribute : ResultFilterAttribute
{
//Public property to enable reflection, inspection
public int StatusCode {get;}
public ResponseCodeAttribute(int statusCode)=>StatusCode=statusCode;
public override void OnResultExecuted(ResultExecutedContext context)
{
context.HttpContext.Response.StatusCode = StatusCode;
}
}
And use it with :
[HttpPost,ResponseCode(204)]
public async Task Foo() {
await Task.CompletedTask;
}
This is not enough though.
Callers of this method have no way of knowing that it will return 204 instead of the expected 200. That's where the metadata-only attribute ProducesResponseTypeAttribute comes in. This attribute implements the IApiResponseMetadataProvider which is used to provide metadata to API Explorer and the proxy/documentation tools like Swagger. At the very least you should use both attributes, eg :
[HttpPost,ResponseCode(204),ProducesResponseType(204)]
public async Task Foo() {
await Task.CompletedTask;
}
Even better, combine ResponseCodeAttribute with IApiResponseMetadataProvider :
public class ResponseCodeAttribute : ResultFilterAttribute,IApiResponseMetadataProvider
{
public int StatusCode {get;}
public Type Type { get; }=typeof(void);
public ResponseCodeAttribute(int statusCode)=>StatusCode=statusCode;
public override void OnResultExecuted(ResultExecutedContext context)
{
context.HttpContext.Response.StatusCode = StatusCode;
}
void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
{
}
}
And apply it with the initial :
[HttpPost,ResponseCode(204)]
public async Task Foo() {
await Task.CompletedTask;
}
Controller base class has a Response property of type HttpResponse.
You can directly set :
Response.StatusCode = 204 ;
You can remove HttpNoContent formatter from outputformatters.Ms doc
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
// requires using Microsoft.AspNetCore.Mvc.Formatters;
options.OutputFormatters.RemoveType<StringOutputFormatter>();
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});
}

Validate ModelState.IsValid globally for all controllers

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);
}
}

Restrict action filter attribute for one action method [duplicate]

I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]

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