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

时间:2019-02-13 02:56:49

标签: c# asp.net-core model-binding

我正在尝试为模型的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属性可以应用于单个模型属性   (例如在ViewModel上)或以动作方法参数指定   特定于该类型或动作的特定模型联编程序或模型名称。

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

1 个答案:

答案 0 :(得分:3)

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

的格式发送有效载荷
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);
    }