c# - 将自定义模型绑定(bind)器应用于 asp.net 核心中的对象属性

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

我正在尝试为模型的 DateTime 类型属性应用自定义模型绑定(bind)器。
这是 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; }
 }

这是 Controller 操作方法。
[HttpPost("/test")]
public IActionResult test([FromBody]Time time)
{

     return Ok(time);
}

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

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.



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

最佳答案

一、原因

根据 [FromBody]Time time在您的操作中,我猜您正在发送带有 Content-Type 的有效负载的 application/json .在这种情况下,当接收到 josn 有效负载时,模型绑定(bind)系统将检查参数 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 的默认模型绑定(bind)器.默认的 json 模型绑定(bind)器使用 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);
    }

关于c# - 将自定义模型绑定(bind)器应用于 asp.net 核心中的对象属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54661799/

相关文章:

c# - 我需要在 ASP .Net Core 3.1 Web API 启动时执行异步方法

asp.net-core - expires_in 或 expires_at 用于 OpenId 连接中的访问 token ?

asp.net-mvc - TryUpdateModel在ASP.NET MVC 3单元测试中引发NullReferenceException

c# - 上传大文件(1GB)-ASP.net

c# - 基于角色的授权属性在 ASP.NET Core MVC 中不起作用

c# - 绑定(bind)一个 [Serializable] 类的 ASP.Net MVC 模型

asp.net-web-api - asp.net web api 是否有类似 [Bind(Exclude ="Property")] 的东西?

c# - 当用户离开对话并稍后加入时重新开始对话

c# - 我如何推断参数并从 SQL 查询字符串返回列?

c# - 如何阅读 MVC OWIN AuthenticationProperties?