如果存在无效的属性值,则Asp.net核心不会绑定后模型



我正在将一个应用程序从遗留的asp.net webapi迁移到asp.net核心mvc。我注意到一个问题。对于某些请求,我们在POST正文中发送部分甚至无效的值。而asp.net核心拒绝对其进行反序列化

例如,后模型

public class PostModel
{
public int Id { get; set; }
public Category? Category { get; set; }
}
public enum Category
{
Public,
Personal
}

动作

[HttpPost]
public async Task<Response> Post([FromBody]PostModel model)
=> this.Service.Execute(model);

以下样品请求

POST /endpoint
{
id: 3,
category: "all"
}

ModelState集合记录了一个错误,指示all是无效类别,而PostModel参数model为null。是否可以禁用这种行为,只尝试绑定帖子正文中所有可能的属性,而忽略无法绑定的属性?这就是在我们的遗留api中为我们所做的,现在,我需要将其移植到各处。

禁用模型验证对我们没有帮助。model参数仍然为null。

对于FromBody,它将通过JsonInputFormatterrequest body绑定到Model

对于JsonInputFormatter,当没有错误时将调用return InputFormatterResult.Success(model),当有任何错误时调用return InputFormatterResult.Failure();。对于return InputFormatterResult.Failure();,它不会绑定有效的属性。

对于解决方案,您可以实现自定义格式化程序以返回return InputFormatterResult.Success(model)

  1. 基于JsonInputFormatter实现自定义格式化程序CustomFormatter
  2. InputFormatterResult.Failure()替换为InputFormatterResult.Success(model)

    if (!(exception is JsonException || exception is OverflowException))
    {
    var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
    exceptionDispatchInfo.Throw();
    }
    return InputFormatterResult.Success(model);
    
  3. Startup.cs中注入CustomFormatter

    services.AddMvc(o =>
    {
    var serviceProvider = services.BuildServiceProvider();
    var customJsonInputFormatter = new CustomFormatter(
    serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<CustomFormatter>(),
    serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value.SerializerSettings,
    serviceProvider.GetRequiredService<ArrayPool<char>>(),
    serviceProvider.GetRequiredService<ObjectPoolProvider>(),
    o,
    serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value
    );
    o.InputFormatters.Insert(0, customJsonInputFormatter);
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    

实际上,您的问题与数据绑定有关,而不是与验证有关,这就是为什么禁用模型验证没有帮助的原因。您可以实现自定义绑定器,并将其配置为手动绑定您的属性,例如:

public class PostModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
string valueFromBody = string.Empty;
using (var sr = new StreamReader(bindingContext.HttpContext.Request.Body))
{
valueFromBody = sr.ReadToEnd();
}
if (string.IsNullOrEmpty(valueFromBody))
{
return Task.CompletedTask;
}
string idString = Convert.ToString(((JValue)JObject.Parse(valueFromBody)["id"]).Value);
string categoryString = Convert.ToString(((JValue)JObject.Parse(valueFromBody)["category"]).Value);
if (string.IsNullOrEmpty(idString) || !int.TryParse(idString, out int id))
{
return Task.CompletedTask;
}
Category? category = null;
if(Enum.TryParse(categoryString, out Category parsedCategory))
{
category = parsedCategory;
}
bindingContext.Result = ModelBindingResult.Success(new PostModel()
{
Id = id,
Category = category
});
return Task.CompletedTask;
}
}

然后你可以把这个活页夹应用到你的课堂上:

[ModelBinder(BinderType = typeof(PostModelBinder))]
public class PostModel
{
public int Id { get; set; }
public Category? Category { get; set; }
}

或行动:

[HttpPost]
public async Task<Response> Post([ModelBinder(BinderType = typeof(PostModelBinder))][FromBody]PostModel model)
=> this.Service.Execute(model);

或创建CustomModelBinderProvider:

public class CustomModelBinderProvider : IModelBinderProvider  
{  
public IModelBinder GetBinder(ModelBinderProviderContext context)  
{  
if (context.Metadata.ModelType == typeof(PostModel))  
return new PostModelBinder();  
return null;  
}  
}

并将其注册到Startup类的ConfigureServices方法中:

public void ConfigureServices(IServiceCollection services)  
{  
...
services.AddMvc(  
config => config.ModelBinderProviders.Insert(0, new CustomModelBinderProvider())  
); 
... 
} 

不可以,因为属性绑定到枚举。如果你真的想成为你发布的内容,那么把模型改为

public class PostModel
{
public int Id { get; set; }
public string Category { get; set; }
}

然后在端点中,将字符串解析为类似的枚举

Enum.TryParse("All", out Category cat);

最新更新