将自定义模型绑定器应用于 asp.net 核心中的对象属性

Posted

技术标签:

【中文标题】将自定义模型绑定器应用于 asp.net 核心中的对象属性【英文标题】:Apply Custom Model Binder to Object Property in asp.net core 【发布时间】:2019-07-06 18:59:43 【问题描述】:

我正在尝试为模型的 DateTime 类型属性应用自定义模型绑定器。 这是 IModelBinder 和 IModelBinderProvider 的实现。

public class DateTimeModelBinderProvider : IModelBinderProvider

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    
        if (context == null)
        
            throw new ArgumentNullException(nameof(context));
        

        if (context.Metadata.ModelType == typeof(DateTime))
        
            return new BinderTypeModelBinder(typeof(DateTime));
        

        return null;
    


public class DateTimeModelBinder : IModelBinder


    private string[] _formats = new string[]  "yyyyMMdd", "yyyy-MM-dd", "yyyy/MM/dd"
    , "yyyyMMddHHmm", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm"
    , "yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss";

    private readonly IModelBinder baseBinder;

    public DateTimeModelBinder()
    
        baseBinder = new SimpleTypeModelBinder(typeof(DateTime), null);
    

    public Task BindModelAsync(ModelBindingContext bindingContext)
    
        if (bindingContext == null)
        
            throw new ArgumentNullException(nameof(bindingContext));
        

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (valueProviderResult != ValueProviderResult.None)
        
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

            var value = valueProviderResult.FirstValue;

            if (DateTime.TryParseExact(value, _formats, new CultureInfo("en-US"), DateTimeStyles.None, out DateTime dateTime))
            
                bindingContext.Result = ModelBindingResult.Success(dateTime);
            
            else
            
                bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, $"bindingContext property value format error.");
            
            return Task.CompletedTask;
        

        return baseBinder.BindModelAsync(bindingContext);
    

这里是模型类

public class Time
 
        [ModelBinder(BinderType = typeof(DateTimeModelBinder))]
        public DateTime? validFrom  get; set; 

        [ModelBinder(BinderType = typeof(DateTimeModelBinder))]
        public DateTime? validTo  get; set; 
 

这里是控制器动作方法。

[HttpPost("/test")]
public IActionResult test([FromBody]Time time)


     return Ok(time);

测试时,不会调用自定义活页夹,但会调用默认的 dotnet 活页夹。据官方documentation称,

ModelBinder 属性可以应用于单个模型属性 (例如在视图模型上)或以操作方法参数指定一个 仅用于该类型或操作的特定模型绑定器或模型名称。

但它似乎不适用于我的代码。

【问题讨论】:

【参考方案1】:

1.原因

根据您操作中的[FromBody]Time time,我猜您正在发送带有Content-Typeapplication/json 的有效负载。在这种情况下,当接收到 josn 有效负载时,模型绑定系统将检查参数time,然后尝试为其找到合适的绑定器。因为context.Metadata.ModelType 等于typeof(Time) 而不是typeof(DateTime),并且typeof(Time) 没有自定义ModelBinder,所以您的GetBinder(context) 方法将返回null

public class DateTimeModelBinderProvider : IModelBinderProvider

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    
        if (context == null)
        
            throw new ArgumentNullException(nameof(context));
        

        if (context.Metadata.ModelType == typeof(DateTime))     // not typeof(Time)
        
            return new BinderTypeModelBinder(typeof(DateTime));  
        

        return null;
    

因此它回退到 application/json 的默认模型绑定器。默认的 json 模型绑定器在后台使用Newtonsoft.Json,并将简单地将洞有效负载反序列化为Time 的实例。因此,您的DateTimeModelBinder 不会被调用。

2。快速修复

一种方法是使用application/x-www-form-urlencoded(避免使用application/json

移除[FromBody]属性:

[HttpPost("/test2")]
public IActionResult test2(Time time)

    return Ok(time);

并以application/x-www-form-urlencoded的格式发送payload

POST https://localhost:5001/test2
Content-Type: application/x-www-form-urlencoded

validFrom=2018-01-01&validTo=2018-02-02

它现在应该可以工作了。

3。使用 JSON

如下创建一个自定义转换器:

public class CustomDateConverter : JsonConverter

    public override bool CanConvert(Type objectType)
    
         return true;
    
    public static string[] _formats = new string[]  
        "yyyyMMdd", "yyyy-MM-dd", "yyyy/MM/dd"
        , "yyyyMMddHHmm", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm"
        , "yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss"
    ;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        var dt= reader.Value;
        if (DateTime.TryParseExact(dt as string, _formats, new CultureInfo("en-US"), DateTimeStyles.None, out DateTime dateTime)) 
            return dateTime;
        else 
            return null;
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        serializer.Serialize(writer, value as string);
    

我只是复制您的代码以格式化日期。

如下更改您的模型:

public class Time

    [ModelBinder(BinderType = typeof(DateTimeModelBinder))]
    [JsonConverter(typeof(CustomDateConverter))]
    public DateTime? validFrom  get; set; 

    [ModelBinder(BinderType = typeof(DateTimeModelBinder))]
    [JsonConverter(typeof(CustomDateConverter))]
    public DateTime? validTo  get; set; 

现在您可以使用[FromBody] 接收时间

    [HttpPost("/test")]
    public IActionResult test([FromBody]Time time)
    

        return Ok(time);
    

【讨论】:

以上是关于将自定义模型绑定器应用于 asp.net 核心中的对象属性的主要内容,如果未能解决你的问题,请参考以下文章

asp.net core mvc 2中抽象类的模型绑定器

复杂对象和模型绑定器 ASP.NET MVC

Asp.net Core 模型绑定器接受布尔类型的随机整数

为啥 ASP.Net MVC 模型绑定器将空 JSON 数组绑定到 null?

使用模型绑定器时在 ASP.NET MVC 中往返视图数据

Asp.Net Core Razor Page PUT Handler 模型绑定器未绑定来自 ajax put 上的 java 脚本的序列化表单