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

时间:2018-07-30 14:19:19

标签: c# asp.net-web-api asp.net-core

我正在将应用程序从旧版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参数仍然为空。

3 个答案:

答案 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,它将通过ModelJsonInputFormatter绑定到JsonInputFormatter

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

对于解决方案,您可以实现自定义格式化程序以返回CustomFormatter

  1. 基于JsonInputFormatter实施自定义格式化程序InputFormatterResult.Failure()
  2. InputFormatterResult.Success(model)替换为 if (!(exception is JsonException || exception is OverflowException)) { var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return InputFormatterResult.Success(model);

    CustomFormatter
  3. 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);