Custom Model Binder for a Property

Custom model binder for a property

override BindProperty and if the
property is "PropertyB" bind the
property with my custom binder

That's a good solution, though instead of checking "is PropertyB" you better check for your own custom attributes that define property-level binders, like

[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}

You can see an example of BindProperty override here.

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

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 json 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 whole 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 copied your code to format the 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);
}


Related Topics



Leave a reply



Submit