将自定义模型活页夹应用于asp.net核心中的对象属性 [英] Apply Custom Model Binder to Object Property in asp.net core

查看:68
本文介绍了将自定义模型活页夹应用于asp.net核心中的对象属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为模型的DateTime类型属性应用自定义模型联编程序. 这是IModelBinder和IModelBinderProvider实现.

I am trying to apply custom model binder for DateTime type property of model. Here is the IModelBinder and IModelBinderProvider implementations.

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绑定程序.根据官方

When tested, the custom binder is not invoked but the default dotnet binder is invoked. According to the official documentation,

ModelBinder属性可以应用于单个模型属性 (例如在ViewModel上)或以动作方法参数指定 特定于该类型或动作的特定模型联编程序或模型名称.

ModelBinder attribute could be applied to individual model properties (such as on a viewmodel) or to action method parameters to specify a certain model binder or model name for just that type or action.

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

But it seems not working with my code.

推荐答案

1.原因

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

According to the [FromBody]Time time in your action, I guess you're sending a payload with Content-Type of application/json. In that case, when a josn payload received, the Model Binding System will inspect the parameter time and then try to find a proper binder for it. Because the context.Metadata.ModelType equals typeof(Time) instead of the typeof(DateTime), and there's no custom ModelBinder for typeof(Time) , your GetBinder(context) method will return 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.

Thus it falls back to the default model binder for application/json. The default json model binder uses Newtonsoft.Json under the hood and will simply deserialize the hole payload as an instance of Time. As a result, your DateTimeModelBinder is not invoked.

2.快速修复

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

One approach is to use application/x-www-form-urlencoded (avoid using the 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

现在应该可以正常工作了.

It should work now.

3.使用JSON

创建如下的自定义转换器:

Create a custom converter as below :

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

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

I simply copy your code to format date.

如下更改模型:

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核心中的对象属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆