【问题标题】:Validate ModelState.IsValid globally for all controllers为所有控制器全局验证 ModelState.IsValid
【发布时间】:2018-07-17 08:48:35
【问题描述】:

在我的 ASP.NET Core 控制器中,我总是检查 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"));
}

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

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

    为了抑制这种行为,当前文档给出了这个选项:

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

    【讨论】:

      【解决方案2】:

      您可以使用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);
          }
      }
      

      【讨论】:

      • ApiController 来自 Web Api 2,它在 ASP.NET Core 中不再存在。除此之外,您不应该附加到您的过滤器ActionFilterAttribute 而只是Attribute。更多的意图是ValidateModelStateAttribute
      • 另外,动作过滤器可以在Startup全局注册
      • @CamiloTerevinto,是的,你是对的。我错过了核心部分。我已经使用适用于 Core 的正确 ActionFilterAttribute 更新了我的答案。
      • 为什么在你的示例代码中两次调用 base.OnActionExecuting(context)?
      • 好的 - 但在 ASp.Net Core 中,您的代码甚至无法编译 :)
      【解决方案3】:

      目前已有的答案是针对 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 中全局注册此过滤器,因此这将在每个调用中执行,您不必在每个 Action/Controller 中重复它:

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

      查看更多in the official documentation。

      【讨论】:

        【解决方案4】:

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

        定义一个过滤器:

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

        【讨论】:

          【解决方案5】:

          使用HandleInvalidModelState

          PM&gt; Install-Package HandleInvalidModelState

          例子

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

          除了基本案例场景(返回无效模型的视图)包支持返回请求的Json和Redirection。

          免责声明:包的作者

          【讨论】:

            【解决方案6】:

            对于async 方法,过滤器必须实现IAsyncActionFilter。我整理了这个过滤器,其中:

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

            或

            • 将 Result 设置为 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);
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2020-04-10
              • 1970-01-01
              • 1970-01-01
              • 2014-10-01
              • 1970-01-01
              • 1970-01-01
              • 2017-08-08
              • 1970-01-01
              • 2011-03-05
              相关资源
              最近更新 更多