为所有控制器

时间:2017-01-26 11:03:49

标签: c# asp.net-core

在我的ASP.NET核心控制器中,我总是检查ModelState是否有效:

[HttpPost("[action]")]
public async Task<IActionResult> DoStuff([FromBody]DoStuffRequest request)
{
  if (!ModelState.IsValid)
  {
    return BadRequest("invalid parameters");
  }
  else
  {
    return Ok("some data"));
  }
}

有没有办法使用过滤器全局检查ModelState的有效性,所以我不必再次在每个控制器的每个API项目中执行此操作?如果动作可以依赖于模型状态有效且不需要检查,那将是很好的:

[HttpPost("[action]")]
public async Task<IActionResult> DoStuff([FromBody]DoStuffRequest request)
{
  return Ok("some data"));
}

6 个答案:

答案 0 :(得分:8)

您可以使用ActionFilter。它不是全局的,但它将问题从您的方法体转移到属性中。我意识到它并没有完全解决你的问题,但它可能比什么都没有好。

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

在你的控制器中:

[HttpPost]
[ModelStateValidationActionFilter]
public IHttpActionResult Post(object model)
{

}

我相信你也可以在你的控制器上设置它。我还没有真正尝试过,但according to this它可以奏效。

[ModelStateValidationActionFilter]
public class MyApiController : ApiController
{

}

修改

正如@Camilo Terevinto提到的那样,Core有点不同。如果您想使用Core,请使用此ActionFilter

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

答案 1 :(得分:8)

到目前为止,现有答案适用于 ASP.NET Web API ,而不适用于 ASP.NET Core 。在ASP.NET Core中实现它的实际方法是:

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

您可以在 Startup.cs 中全局注册此过滤器,因此这将在每次调用中执行,您不必在每个操作/控制器中重复它:

options.Filters.Add(typeof(SampleActionFilter));

查看更多in the official documentation

答案 2 :(得分:5)

作为后续操作:在ASP.NET Core 2.1中,有一个名为[ApiController]的控制器属性。如果包含该属性,它将自动使用名为ActionFilter的内置ModelStateInvalidFilter,这意味着将永远无法访问检查ModelState的任何自定义操作过滤器,因为[ApiController]属性已经注册了自己的过滤器

为抑制这种行为,当前文档提供了此选项:

services.Configure<ApiBehaviorOptions>(options =>
{
   options.SuppressModelStateInvalidFilter = true; // This is the setting
});

答案 3 :(得分:3)

对于ASP.NET Core 2.0,要避免将属性单独应用于所有ControllersActions;

定义过滤器:

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

然后在Startup.cs中,将其添加为过滤器:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(config =>
        {
            config.Filters.Add(new ModelStateCheckFilter());
        });
    }

答案 4 :(得分:1)

使用HandleInvalidModelState

PM> Install-Package HandleInvalidModelState

示例

[HttpPost]
[TypeFilter(typeof(HandleInvalidModelWithViewActionFilterAttribute))]
public IHttpActionResult Post(object model)
{}

除了基本案例场景(返回带有无效模型的视图)包之外,还支持返回请求的JsonRedirection

免责声明:软件包的作者

答案 5 :(得分:0)

对于async方法,过滤器必须实现IAsyncActionFilter。我把这个过滤器放在一起,其中:

    如果ModelState有效,则
  • 不执行任何操作

OR

  • 将结果设置为BadRequestObjectResult,并提供有关失败的详细信息
  • 使用更多详细信息记录ModelState
public class ValidModelStateAsyncActionFilter : IAsyncActionFilter
{
    // https://docs.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);
    }
}