Пользовательская привязка модели DateTime в Asp.net MVC
-
23-09-2019 - |
Вопрос
Я хотел бы написать свою собственную модель binder для DateTime
Тип.Прежде всего, я хотел бы написать новый атрибут, который я мог бы прикрепить к своему свойству модели, например:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
Это самая легкая часть.Но связующая часть немного сложнее.Я хотел бы добавить новую модель binder для type DateTime
.Я могу либо
- реализовать
IModelBinder
интерфейс и написать свой собственныйBindModel()
способ - наследовать от
DefaultModelBinder
и переопределитьBindModel()
способ
Моя модель обладает свойством, как показано выше (Birth
).Поэтому, когда модель пытается привязать данные запроса к этому свойству, моя связующая модель BindModel(controllerContext, bindingContext)
вызывается.Все в порядке, но. Как мне получить атрибуты свойства из controller / BindingContext, чтобы правильно проанализировать мою дату?Как я могу добраться до PropertyDesciptor
собственности Birth
?
Редактировать
Из-за разделения задач мой класс модели определен в сборке, которая не ссылается (и не должна) на сборку System.Web.MVC.Настройка пользовательской привязки (аналогично Пример Скотта Хансельмана) атрибуты здесь ни к чему.
Решение
Я не думаю, что вам следует добавлять к модели атрибуты, зависящие от локали.
Двумя другими возможными решениями этой проблемы являются:
- Пусть ваши страницы транслитерируют даты из формата, зависящего от локали, в общий формат, такой как гггг-мм-дд в JavaScript.(Работает, но требует JavaScript.)
- Напишите привязку модели, которая учитывает текущую культуру пользовательского интерфейса при анализе дат.
Чтобы ответить на ваш актуальный вопрос, способ получить пользовательские атрибуты (для MVC 2) заключается в напишите ассоциированный поставщик metadataprovider.
Другие советы
вы можете изменить привязку модели по умолчанию, чтобы использовать культуру пользователя, используя IModelBinder
public class DateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
return value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
public class NullableDateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
return value == null
? null
: value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
И в глобальном.Asax добавьте следующее в Application_Start():
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());
Читайте больше на этот отличный блог это объясняет, почему команда Mvc framework внедрила Культуру по умолчанию для всех пользователей.
У меня самого была эта очень большая проблема, и после нескольких часов попыток и неудач я получил рабочее решение, как вы просили.
Прежде всего, поскольку наличие привязки только к свойству невозможно, вам нужно реализовать полный ModelBinder.Поскольку вы не хотите привязывать все отдельное свойство, а только то, которое вам нужно, вы можете наследовать от DefaultModelBinder, а затем привязать единственное свойство:
public class DateFiexedCultureModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(DateTime?))
{
try
{
var model = bindingContext.Model;
PropertyInfo property = model.GetType().GetProperty(propertyDescriptor.Name);
var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
if (value != null)
{
System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("it-CH");
var date = DateTime.Parse(value.AttemptedValue, cultureinfo);
property.SetValue(model, date, null);
}
}
catch
{
//If something wrong, validation should take care
}
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
В моем примере я анализирую дату с фиксированной культурой, но то, что вы хотите сделать, возможно.Вы должны создать пользовательский атрибут (например, DateTimeFormatAttribute) и поместить его поверх вашего свойства:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
Теперь в методе BindProperty вместо поиска свойства DateTime вы можете найти свойство с помощью атрибута DateTimeFormatAttribute, захватить формат, указанный вами в конструкторе, а затем проанализировать дату с помощью DateTime.Синтаксический анализ
Я надеюсь, что это поможет, мне потребовалось очень много времени, чтобы прийти к этому решению.На самом деле было легко получить это решение, как только я узнал, как его искать: (
Вы могли бы реализовать пользовательскую привязку DateTime следующим образом, но вы должны позаботиться о предполагаемой культуре и ценности из фактического запроса клиента.Если вы получаете дату, подобную mm / dd / yyyy в en-US, и хотите преобразовать ее в системном языке en-GB (который был бы похож на dd / mm / yyyy) или в инвариантном языке, как это делаем мы, тогда вам нужно проанализировать ее перед этим и с помощью преобразования статического фасада изменить ее поведение.
public class DateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
var modelState = new ModelState {Value = valueResult};
var resDateTime = new DateTime();
if (valueResult == null) return null;
if ((bindingContext.ModelType == typeof(DateTime)||
bindingContext.ModelType == typeof(DateTime?)))
{
if (bindingContext.ModelName != "Version")
{
try
{
resDateTime =
Convert.ToDateTime(
DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture,
DateTimeStyles.AdjustToUniversal).ToUniversalTime(), CultureInfo.InvariantCulture);
}
catch (Exception e)
{
modelState.Errors.Add(EnterpriseLibraryHelper.HandleDataLayerException(e));
}
}
else
{
resDateTime =
Convert.ToDateTime(
DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture), CultureInfo.InvariantCulture);
}
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return resDateTime;
}
}
В любом случае, синтаксический анализ даты и времени, зависящий от культуры, в приложении без состояния может быть жестоким ... Особенно когда вы работаете с JSON на стороне клиента javascript и в обратном направлении.