我正在将应用程序从旧版ASP.NET WebAPI迁移到ASP.NET Core 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
为空。是否可以禁用此行为,而仅尝试绑定帖子主体中所有可能的属性,而忽略它无法绑定的属性?这是在我们的旧版api中为我们完成的,现在,我需要将其移植。
禁用模型验证对我们没有帮助。 model
参数仍然为空。
答案 0 :(得分:1)
实际上,您的问题与数据绑定有关,与验证无关,因此禁用模型验证无济于事。您可以实现自定义Binder并将其配置为手动绑定属性,例如:
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())
);
...
}
答案 1 :(得分:1)
对于request body
,它将通过Model
将JsonInputFormatter
绑定到JsonInputFormatter
。
对于return InputFormatterResult.Success(model)
,它会在没有错误的情况下调用return InputFormatterResult.Failure();
,在有任何错误的情况下会调用return InputFormatterResult.Failure();
。对于return InputFormatterResult.Success(model)
,它将不会绑定有效属性。
对于解决方案,您可以实现自定义格式化程序以返回CustomFormatter
。
InputFormatterResult.Failure()
。 将InputFormatterResult.Success(model)
替换为 if (!(exception is JsonException || exception is OverflowException))
{
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
exceptionDispatchInfo.Throw();
}
return InputFormatterResult.Success(model);
。
CustomFormatter
在Startup.cs
中注入 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);
ArrayList<DifferentDataType>
答案 2 :(得分:0)
不,因为属性绑定到枚举,所以您不能这样做。如果您真的想成为您发布的内容,则将模型更改为
public class PostModel
{
public int Id { get; set; }
public string Category { get; set; }
}
然后在端点中将字符串解析为枚举之类的
Enum.TryParse("All", out Category cat);