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

问题描述 投票:2回答:1

我正在尝试为模型的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属性可以应用于单个模型属性(例如在视图模型上)或操作方法参数,以仅为该类型或操作指定特定模型绑定器或模型名称。

但似乎无法使用我的代码。

c# asp.net-core model-binding
1个回答
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);
    }
© www.soinside.com 2019 - 2024. All rights reserved.