【问题标题】:Handling Model Binding Errors when using [FromBody] in .NET Core 2.1在 .NET Core 2.1 中使用 [FromBody] 时处理模型绑定错误
【发布时间】:2019-08-01 16:26:50
【问题描述】:

我正在尝试了解如何在 .net 核心中拦截和处理模型绑定错误。

我想这样做:

    // POST api/values
    [HttpPost]
    public void Post([FromBody] Thing value)
    {
        if (!ModelState.IsValid)
        {
            // Handle Error Here
        }
    }

“事物”的模型在哪里:

public class Thing
{
    public string Description { get; set; }
    public int Amount { get; set; }
}

但是,如果我传入无效的金额,例如:

{ 
   "description" : "Cats",
   "amount" : 21.25
}

我收到这样的错误:

{"amount":["输入字符串'21.25'不是有效整数。路径'amount',第1行,位置38。"]}

控制器代码永远不会被击中。

如何自定义返回的错误? (因为基本上我需要将这个序列化错误包装在一个更大的错误对象中)

【问题讨论】:

    标签: c# .net-core asp.net-core-webapi .net-core-2.1


    【解决方案1】:

    所以,我之前错过了这个,但我在这里找到了:

    https://docs.microsoft.com/en-us/aspnet/core/web-api/index?view=aspnetcore-2.2#automatic-http-400-responses

    如果你使用了

    [ApiController] 
    

    属性在你的控制器上,它会自动处理序列化错误并提供400响应,相当于:

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    

    您可以像这样在 Startup.cs 中关闭此行为:

    services.AddMvc()
        .ConfigureApiBehaviorOptions(options =>
        {
            options.SuppressModelStateInvalidFilter = true;
        });
    

    如果您希望自定义响应,更好的选择是使用 InvalidModelStateResponseFactory,它是一个接受 ActionContext 并返回将被调用以处理序列化错误的 IActionResult 的委托。

    看这个例子:

    services.Configure<ApiBehaviorOptions>(options =>
    {
        options.InvalidModelStateResponseFactory = actionContext => 
        {
            var errors = actionContext.ModelState
                .Where(e => e.Value.Errors.Count > 0)
                .Select(e => new Error
                {
                Name = e.Key,
                Message = e.Value.Errors.First().ErrorMessage
                }).ToArray();
    
            return new BadRequestObjectResult(errors);
        }
    });
    

    【讨论】:

    • 更好的方法是通过options.InvalidModelStateResponseFactory 提供自定义处理程序。这样,您仍然不需要到处检查ModelState.IsValid,您仍然可以返回您的自定义回复。
    • 对不起。错过了最后一行。但是,是的,使用InvalidModelStateResponseFactory。这就是要走的路。
    • 这是我能找到的最佳答案。但是,如果在模型绑定过程中出现错误——比如尝试将整数绑定到 guid,aspnet 会绕过 InvalidModelStateResponseFactory 并返回默认的 400 错误响应。我找不到任何方法来自定义错误。使用 SuppressModelStateInvalidFilter 只会给你一个空模型,并且没有关于为什么模型没有绑定的信息。
    • 如果您要实现新的InvalidModelStateResponseFactory,请确保不要将SuppressModelStateInvalidFilter 设置为true,它必须保持为false。
    【解决方案2】:

    该框架使用模型绑定器将请求字符串映射到一个复杂的对象,所以我猜您将需要创建一个自定义模型绑定器。请参考Custom Model Binding in ASP.Net Core

    但在此之前,更简单的尝试方法是在模型中尝试 Binder 属性。如果绑定不能发生,BindRequired 属性会添加模型状态错误。因此,您可以将模型修改为:

    public class Thing 
    {
        [BindRequired]
        public string Description {get;set;}
    
        [BindRequired]
        public int Amount {get;set;}
    }
    

    如果这对您不起作用,那么您可以尝试创建自定义模型绑定器。文章中的一个例子:

    [ModelBinder(BinderType = typeof(AuthorEntityBinder))]
    public class Author
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string GitHub { get; set; }
        public string Twitter { get; set; }
        public string BlogUrl { get; set; }
    }
    
    public class AuthorEntityBinder : IModelBinder
    {
       private readonly AppDbContext _db;
       public AuthorEntityBinder(AppDbContext db)
       {
           _db = db;
       }
    
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }
    
        var modelName = bindingContext.ModelName;
    
        // Try to fetch the value of the argument by name
        var valueProviderResult =
            bindingContext.ValueProvider.GetValue(modelName);
    
        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }
    
        bindingContext.ModelState.SetModelValue(modelName,
            valueProviderResult);
    
        var value = valueProviderResult.FirstValue;
    
        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }
    
        int id = 0;
        if (!int.TryParse(value, out id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                                    modelName,
                                    "Author Id must be an integer.");
            return Task.CompletedTask;
        }
    
        // Model will be null if not found, including for 
        // out of range id values (0, -3, etc.)
        var model = _db.Authors.Find(id);
        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
       }
    }
    

    您可能还想查看Model Validation

    【讨论】:

      猜你喜欢
      • 2019-01-07
      • 2018-01-02
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多