将自定义模型绑定器应用于 asp.net 核心中的对象 属性
Apply Custom Model Binder to Object Property in asp.net core
我正在尝试为模型的日期时间类型 属性 应用自定义模型联编程序。
这是 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);
}
}
这是模型 class
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 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.
但它似乎不适用于我的代码。
1.原因
根据您的操作中的 [FromBody]Time time
,我猜您发送的负载 Content-Type
为 application/json
。在这种情况下,当收到 josn 有效载荷时,模型绑定系统将检查参数 time
,然后尝试为其找到合适的绑定器。因为 context.Metadata.ModelType
等于 typeof(Time)
而不是 typeof(DateTime)
,并且 没有针对 typeof(Time)
的自定义 ModelBinder,您的 GetBinder(context)
方法将 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
未被调用。
2。快速修复
一种方法是使用 application/x-www-form-urlencoded
(避免使用 application/json
)
删除 [FromBody]
属性:
[HttpPost("/test2")]
public IActionResult test2(Time time)
{
return Ok(time);
}
并以application/x-www-form-urlencoded
的格式发送payload
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);
}
我正在尝试为模型的日期时间类型 属性 应用自定义模型联编程序。 这是 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);
}
}
这是模型 class
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 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.
但它似乎不适用于我的代码。
1.原因
根据您的操作中的 [FromBody]Time time
,我猜您发送的负载 Content-Type
为 application/json
。在这种情况下,当收到 josn 有效载荷时,模型绑定系统将检查参数 time
,然后尝试为其找到合适的绑定器。因为 context.Metadata.ModelType
等于 typeof(Time)
而不是 typeof(DateTime)
,并且 没有针对 typeof(Time)
的自定义 ModelBinder,您的 GetBinder(context)
方法将 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
未被调用。
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);
}