首页 > 解决方案 > 将自定义模型绑定器应用于 asp.net 核心中的对象属性

问题描述

我正在尝试为模型的 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 活页夹。根据官方文档

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

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

标签: c#asp.net-coremodel-binding

解决方案


1. 原因

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

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);
    }

推荐阅读