Применить привязку пользовательской модели к свойству объекта в ядре asp.net - PullRequest
0 голосов
/ 13 февраля 2019

Я пытаюсь применить пользовательскую привязку модели для свойства типа DateTime модели.Вот реализации 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; }
 }

А вот метод действия контроллера.

[HttpPost("/test")]
public IActionResult test([FromBody]Time time)
{

     return Ok(time);
}

Когдапротестировано, пользовательское связующее не вызывается, но вызывается связующее по умолчанию dotnet.В соответствии с официальной документацией ,

атрибут ModelBinder может применяться к отдельным свойствам модели (например, к модели представления) или к параметрам метода действия для указания определенного связующего элемента или моделиимя только для этого типа или действия.

Но, похоже, не работает с моим кодом.

1 Ответ

0 голосов
/ 13 февраля 2019

1.Причина

В соответствии с [FromBody]Time time в вашем действии, я полагаю, вы отправляете полезную нагрузку с Content-Type из application/json.В этом случае при получении полезной нагрузки josn система привязки модели проверит параметр time и попытается найти для него подходящее связующее.Поскольку context.Metadata.ModelType равно typeof(Time) вместо typeof(DateTime), а нет пользовательского ModelBinder для typeof(Time), ваш метод 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 .Связующее по умолчанию для модели 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);
    }
...