Подтвердить что ты не робот

Пользовательское связующее устройство DateTime в Asp.net MVC

Я хотел бы написать собственное связующее устройство для типа DateTime. Прежде всего, я хотел бы написать новый атрибут, который я могу прикрепить к свойству модели, например:

[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}

Это легкая часть. Но связующая часть немного сложнее. Я хотел бы добавить новое связующее устройство для типа DateTime. Я могу либо

  • реализовать интерфейс IModelBinder и написать собственный метод BindModel()
  • наследовать от DefaultModelBinder и переопределить BindModel() метод

Моя модель имеет свойство, как показано выше (Birth). Поэтому, когда модель пытается связать данные запроса с этим свойством, вызывается мое связующее устройство BindModel(controllerContext, bindingContext). Все нормально, но. Как получить атрибуты свойств из контроллера /bindingContext, правильно ли вывести дату?? Как я могу получить PropertyDesciptor свойства Birth?

Изменить

Из-за разделения проблем мой модельный класс определен в сборке, которая не (и не должна) ссылается на сборку System.Web.MVC. Настройка настраиваемого привязки (аналогично пример Scott Hanselman), здесь нет необходимости.

4b9b3361

Ответ 1

Я не думаю, что вы должны поместить локальные атрибуты на модель.

Два других возможных решения этой проблемы:

  • Пусть ваши страницы транслитерируют даты из формата, определенного в локали, в общий формат, такой как yyyy-mm-dd в JavaScript. (Работает, но требует JavaScript.)
  • Напишите привязку модели, которая учитывает текущую культуру пользовательского интерфейса при анализе дат.

Чтобы ответить на ваш реальный вопрос, способ получения пользовательских атрибутов (для MVC 2) - написать AssociatedMetadataProvider.

Ответ 2

вы можете изменить связующее устройство по умолчанию, чтобы использовать культуру пользователя, используя 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);
    }
}

И в Global.Asax добавьте следующее в Application_Start():

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());

Подробнее читайте в этом отличном блоге, в котором описывается, почему команда Mvc Framework реализовала культуру по умолчанию для всех пользователей.

Ответ 3

У меня была эта очень большая проблема, и после нескольких часов попыток и неудач я получил рабочее решение, как вы просили.

Прежде всего, поскольку наличие связующего только на свойстве не позволяет реализовать полный 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);
        }
    }
}

В моем примере я занимаюсь синтаксической обработкой даты с культурой фигеда, но то, что вы хотите сделать, возможно. Вы должны создать атрибут CustomAttribute (например, DateTimeFormatAttribute) и поместить его в ваше свойство:

[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}

Теперь в методе BindProperty вместо поиска свойства DateTime вы можете найти свойство с вами DateTimeFormatAttribute, захватить формат, указанный вами в конструкторе, а затем проанализировать дату с помощью DateTime.ParseExact

Надеюсь, это поможет, мне потребовалось очень много времени с этим решением. На самом деле было легко получить это решение, как только я знал, как его искать: (

Ответ 4

Вы можете реализовать пользовательское связывание DateTime так, но вы должны заботиться о предполагаемой культуре и ценности из фактического запроса клиента. Можете ли вы получить дату, например, mm/dd/yyyy в en-US, и хотите, чтобы она конвертировалась в культуру системы en-GB (которая была бы как dd/mm/yyyy) или инвариантная культура, как и мы, тогда вы необходимо проанализировать его раньше и использовать статический фасад Convert, чтобы изменить его в своем поведении.

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

В любом случае, зависимость от культуры в формате DateTime в приложении без состояния может быть жестоко... Особенно, когда вы работаете с JSON на javascript clientide и backwards.