Пользовательский связыватель модели 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. Настроить настраиваемые атрибуты привязки (аналогично пример Скотта Хансельмана) здесь нельзя.


person Robert Koritnik    schedule 01.03.2010    source источник
comment
это помогает? hanselman.com/blog/   -  person Marek    schedule 01.03.2010
comment
не совсем, потому что он не использует никаких настраиваемых атрибутов. Я мог бы использовать BindAttribute, но это не универсальное решение. Вы можете легко забыть написать это в своем действии.   -  person Robert Koritnik    schedule 01.03.2010
comment
У вас есть рабочее решение этой проблемы? У меня такая же проблема, и я хотел бы знать, какое решение вы выбрали   -  person Davide Vosti    schedule 10.06.2010
comment
@Davide Vosti: Я закончил преобразование значения datetime на клиенте в скрытое поле. Он заполняется, когда пользователь размывает поле выбора даты. И это работает. Это обходной путь, он не содержит большого количества дополнительного кода и работает в моем сценарии.   -  person Robert Koritnik    schedule 15.06.2010
comment
Благодарность! Тем временем я смог найти хорошее решение. В любом случае спасибо за ваше предложение   -  person Davide Vosti    schedule 15.06.2010


Ответы (4)


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

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

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

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

person Craig Stuntz    schedule 01.03.2010
comment
На самом деле это не связано с форматированием, зависящим от локали. Проблема в том, что DateTime должен иметь дату и время в строке, чтобы связыватель по умолчанию правильно проанализировал его. Неважно, какая локализация используется. Я просто хочу предоставить дату от клиента и правильно проанализировать ее для экземпляра DateTime (время установлено на 00:00:00) при привязке модели. - person Robert Koritnik; 01.03.2010
comment
Если это вообще возможно, я бы хотел избежать написания собственного поставщика метаданных. но я думаю, это может быть именно то, что мне нужно. Я мог бы прикрепить свои собственные атрибуты к информации ModelMetadata. - person Robert Koritnik; 01.03.2010
comment
Неправда, что DateTime.Parse требует строки. Попробуйте var dt = DateTime.Parse("2010-03-01"); Я гарантирую, что он работает! Однако конкретный формат DateTime может. - person Craig Stuntz; 01.03.2010
comment
DateTime.parse может быть в порядке с этим, DefaultModelBinder, очевидно, нет. Мой формат даты в любом случае такой же, как и в локали. Я попытался загрузить модель представления с датой и временем и отобразить представление строгого типа, которое его использует, и отображаемую дату, включая время. Nad, когда я включаю время в свое свойство DateTime, все работает нормально. В противном случае я дал ошибку проверки (используя DataAnnotations) - person Robert Koritnik; 01.03.2010
comment
Я бы не был так уверен, что DefaultModelBinder явно не так. Для этого он отлично работает, по крайней мере, здесь. Я отмечаю, что вы находитесь в Словении, поэтому возможно (хотя и не очевидно!) Машина в определенной конфигурации не будет анализировать гггг-мм-дд, хотя этот должен Работайте в любой культуре. Но, возвращаясь к сути, связанный поставщик метаданных состоит всего из 20 строк кода или около того и предоставит вашему связующему необходимую информацию. - person Craig Stuntz; 01.03.2010
comment
На основании поставщика метаданных я принимаю ваш ответ как наиболее подходящее решение. Спасибо, Крейг. - person Robert Koritnik; 02.03.2010

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

person gdoron is supporting Monica    schedule 07.11.2011
comment
спасибо, этот способ сработал лучше всего. Я вижу, что другие ответы тоже могут работать должным образом, но здесь, если мы добавим это ниже вместо value.convertTo, формат datetime можно будет контролировать, не беспокоясь о культуре, потому что мы также настроили формат для fornt end (например, jQueryUI datepicker ). - ››› var dateFormat = bindingContext.ModelMetadata.EditFormatString; return DateTime.ParseExact (((строка []) значение.RawValue) [0], dateFormat, CultureInfo.InvariantCulture); - person Vasil Popov; 13.07.2012
comment
Совет вам, сэр. - person TheGwa; 01.03.2013
comment
Добавление подшивки, кажется, ломает Url.Action. Когда я передаю DateTime, он больше не использует правильную культуру. Кажется, вместо этого используется en-US. - person Kamil Szot; 22.05.2015
comment
@KamilSzot, я не вижу причин, почему бы это сломалось, откройте вопрос со всеми подробностями и оставьте ссылку, пожалуйста. - person gdoron is supporting Monica; 25.05.2015
comment
@gdoron Моя ошибка. Url.Action, кажется, всегда использует InvariantCulture для генерации строки запроса, переданной DateTime значениями маршрута, независимо от привязок. - person Kamil Szot; 25.05.2015
comment
@KamilSzot, ОК, можешь удалить комментарии, если так. - person gdoron is supporting Monica; 26.05.2015
comment
Я думаю, что реализация NullableDateTimeBinder тоже подходит для ненулевого DateTime. И мы можем оставить только NullableDateTimeBinder класс. - person sDima; 12.06.2015
comment
Вы также должны добавить поле в ModelState, в противном случае помощники ввода HTML не смогут использовать его для повторного заполнения (например, когда страница повторно отображается из-за ошибки проверки), и у вас будут пустые поля: bindingContext.ModelState.Add(bindingContext.ModelName, new ModelState { Value = value }); - person ajbeaven; 11.01.2016

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

Прежде всего, поскольку привязка только к свойству невозможна, вам необходимо реализовать полную привязку 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

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

person Davide Vosti    schedule 11.06.2010

Вы можете реализовать собственный DateTime Binder таким образом, но вы должны позаботиться о предполагаемой культуре и значении из фактического клиентского запроса. Можете ли вы получить дату, например, мм / дд / гггг в en-US, и хотите, чтобы она была преобразована в системную культуру en-GB (которая будет иметь вид дд / мм / гггг) или инвариантную культуру, как это делаем мы, тогда вы нужно сначала проанализировать его и использовать статический фасад 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 и в обратном направлении.

person Phil    schedule 05.09.2013