Apply Custom Model Binder to Object Property in asp.net core

前端 未结 1 1049
不思量自难忘°
不思量自难忘° 2021-01-06 10:39

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

public clas         


        
1条回答
  •  迷失自我
    2021-01-06 10:49

    1. Reason

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

    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. Quick Fix

    One approach is to use application/x-www-form-urlencoded (avoid using the application/json)

    Remove the [FromBody] attribute:

    [HttpPost("/test2")]
    public IActionResult test2(Time time)
    {
        return Ok(time);
    }
    

    and send the payload in the format of 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. Working with 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.

    Change your Model as below :

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

    And now you can receive the time using [FromBody]

        [HttpPost("/test")]
        public IActionResult test([FromBody]Time time)
        {
    
            return Ok(time);
        }
    

    0 讨论(0)
提交回复
热议问题