Привязка MVC DateTime с неправильным форматом даты

132

Asp.net-MVC теперь позволяет неявное связывание объектов DateTime. У меня есть действие в духе

public ActionResult DoSomething(DateTime startDate) 
{ 
... 
}

Это успешно преобразует строку из вызова ajax в DateTime. Однако мы используем формат даты дд / ММ / гггг; MVC конвертируется в MM / dd / yyyy. Например, отправка вызова к действию со строкой «02/09/2009» приводит к значению DateTime «09/02/2009 00:00:00» или 2 сентября в наших локальных настройках.

Я не хочу сворачивать свою собственную подшивку модели ради формата даты. Но кажется излишним изменять действие, чтобы принять строку, а затем использовать DateTime.Parse, если MVC может сделать это за меня.

Есть ли способ изменить формат даты, используемый в привязке модели по умолчанию для DateTime? Разве связщик модели по умолчанию не должен использовать ваши настройки локализации?

Сэм Вессель
источник
Эй .. Просто измените формат системной даты - ДД / ММ / гггг на ММ / ДД / гггг и сделайте это .. У меня тоже есть такая же проблема, я решил ее, изменив формат системной даты.
Бэнни
@banny, если приложение является глобальным и может быть у всех не один и тот же формат даты и времени, как вы могли это сделать? , вы не должны идти и менять каждый формат даты и времени ..
Рави Мехта
Ни один из этих ответов мне не помогает. Форма должна быть локализована. У некоторых пользователей дата может быть одна, у других - другая. Настраиваем что-нибудь в web.config. или global.asax не поможет. Я буду продолжать искать лучший ответ, но один из способов - это просто обрабатывать дату как строку, пока я не вернусь к С #.
Astrosteve

Ответы:

164

Я только что нашел ответ на этот вопрос с помощью более подробного поиска в Google:

У Мелвина Харбора есть подробное объяснение того, почему MVC работает с датами именно так, и как вы можете переопределить это при необходимости:

http://weblogs.asp.net/melvynharbour/archive/2008/11/21/mvc-modelbinder-and-localization.aspx

При поиске значения для анализа структура смотрится в определенном порядке, а именно:

  1. RouteData (не показано выше)
  2. Строка запроса URI
  3. Форма запроса

Однако только последние из них будут осведомлены о культуре. Для этого есть очень веская причина с точки зрения локализации. Представьте, что я написал веб-приложение, показывающее информацию о рейсах авиакомпаний, которую я публикую в Интернете. Я ищу рейсы на определенную дату, щелкнув ссылку на этот день (возможно, что-то вроде http://www.melsflighttimes.com/Flights/2008-11-21 ), а затем хочу отправить эту ссылку своему коллеге в Соединенные штаты. Единственный способ гарантировать, что мы оба будем смотреть на одну и ту же страницу данных, - это использовать InvariantCulture. Напротив, если я использую форму для бронирования своего рейса, все происходит в жестком цикле. Данные могут уважать CurrentCulture, когда они записываются в форму, и поэтому должны уважать ее при возврате из формы.

Сэм Вессель
источник
Сделаю. Эта функция отключена на 48 часов после публикации вопроса.
Сэм Вессел,
43
Я категорически не согласен с тем, что технически это правильно. Связывание модели ВСЕГДА должно вести себя одинаково с POST и GET. Если инвариантная культура - это путь для GET, сделайте это и для POST. Менять поведение в зависимости от http-глагола бессмысленно.
Барт Каликсто
У меня вопрос, наш веб-сайт размещен в другой стране, ему нужен MM/dd/yyyyформат, иначе он показывает ошибку проверки The field BeginDate must be a date., как я могу заставить сервер принимать dd/MM/yyyyформат?
Shaijut
Параметр URL должен быть однозначным, как при использовании стандартного форматирования ISO. Тогда настройки культуры не имели бы значения.
nforss
это дает ссылку, объясняющую причину, но на самом деле не обеспечивает простейшего решения этой проблемы (например, путем публикации строки даты и времени ISO из сценария), этот способ всегда работает, и нам не нужно заботиться о настройке определенной культуры для сервер или убедитесь, что формат даты и времени идентичен между сервером и клиентом.
Hopeless
36

Я бы установил ваши культуры глобально. ModelBinder возьмите это!

  <system.web>
    <globalization uiCulture="en-AU" culture="en-AU" />

Или вы просто измените это для этой страницы.
Но глобально в web.config думаю лучше

Питер Гфадер
источник
27
Не для меня. Дата по-прежнему остается нулевой, если я прохожу 23/10/2010.
GONeale,
Думаю, это самое простое решение. Для меня он меняет формат во всех Date.ToString (). Думаю, с привязкой тоже пойдет, но не проверял, извините :-(
msa.im
1
У меня установлен формат даты сервера разработки на dd / MM / yyyy. Modelbinder использовал формат MM / dd / yyyy. Установка формата в web.config на dd / MM / yyyy теперь заставляет привязку моделей использовать европейский формат. На мой взгляд, он должен использовать настройки даты сервера. В любом случае вы решили мою проблему.
Карел
Это отлично сработало для меня ... как-то это показалось странным, поскольку я знаю, что мой сервер приложений находится в Великобритании и работает под управлением британской ОС ...: /
Tallmaris
Отлично работал в MVC4, запущенном на веб-сайтах Azure,
Мэтти Беар,
31

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

using System;
using System.Globalization;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    public class CustomDateBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext", "controllerContext is null.");
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext", "bindingContext is null.");

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (value == null)
                throw new ArgumentNullException(bindingContext.ModelName);

            CultureInfo cultureInf = (CultureInfo)CultureInfo.CurrentCulture.Clone();
            cultureInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

            try
            {
                var date = value.ConvertTo(typeof(DateTime), cultureInf);

                return date;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
        }
    }

    public class NullableCustomDateBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext", "controllerContext is null.");
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext", "bindingContext is null.");

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (value == null) return null;

            CultureInfo cultureInf = (CultureInfo)CultureInfo.CurrentCulture.Clone();
            cultureInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

            try
            {
                var date = value.ConvertTo(typeof(DateTime), cultureInf);

                return date;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
        }
    }
}

Чтобы сохранить то, как маршруты и т. Д. Регистрируются в файле Global ASAX, я также добавил новый класс sytatic в папку App_Start моего проекта MVC4 с именем CustomModelBinderConfig:

using System;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    public static class CustomModelBindersConfig
    {
        public static void RegisterCustomModelBinders()
        {
            ModelBinders.Binders.Add(typeof(DateTime), new CustomModelBinders.CustomDateBinder());
            ModelBinders.Binders.Add(typeof(DateTime?), new CustomModelBinders.NullableCustomDateBinder());
        }
    }
}

Затем я просто вызываю статический RegisterCustomModelBinders из моего Global ASASX Application_Start следующим образом:

protected void Application_Start()
{
    /* bla blah bla the usual stuff and then */

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Здесь важно отметить, что если вы напишете значение DateTime в скрытое поле следующим образом:

@Html.HiddenFor(model => model.SomeDate) // a DateTime property
@Html.Hiddenfor(model => model) // a model that is of type DateTime

Я сделал это, и фактическое значение на странице было в формате «MM / dd / yyyy hh: mm: ss tt» вместо «dd / MM / yyyy hh: mm: ss tt», как я хотел. Это привело к тому, что проверка моей модели либо завершилась неудачно, либо вернула неправильную дату (очевидно, поменяв местами значения дня и месяца).

После множества головокружений и неудачных попыток решением было установить информацию о культуре для каждого запроса, сделав это в Global.ASAX:

protected void Application_BeginRequest()
{
    CultureInfo cInf = new CultureInfo("en-ZA", false);  
    // NOTE: change the culture name en-ZA to whatever culture suits your needs

    cInf.DateTimeFormat.DateSeparator = "/";
    cInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
    cInf.DateTimeFormat.LongDatePattern = "dd/MM/yyyy hh:mm:ss tt";

    System.Threading.Thread.CurrentThread.CurrentCulture = cInf;
    System.Threading.Thread.CurrentThread.CurrentUICulture = cInf;
}

Это не сработает, если вы вставите его в Application_Start или даже в Session_Start, поскольку это назначает его текущему потоку для сеанса. Как вы хорошо знаете, веб-приложения не имеют состояния, поэтому поток, который ранее обслуживал ваш запрос, не является тем же потоком, который обслуживает ваш текущий запрос, поэтому информация о вашей культуре отправилась в великий сборщик мусора в цифровом небе.

Спасибо: Иван Златев - http://ivanz.com/2010/11/03/custom-model-binding-using-imodelbinder-in-asp-net-mvc-two-gotchas/

гарик - https://stackoverflow.com/a/2468447/578208

Дмитрий - https://stackoverflow.com/a/11903896/578208

WernerVA
источник
13

В MVC 3 все будет немного иначе.

Предположим, у нас есть контроллер и представление с методом Get

public ActionResult DoSomething(DateTime dateTime)
{
    return View();
}

Мы должны добавить ModelBinder

public class DateTimeBinder : IModelBinder
{
    #region IModelBinder Members
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        DateTime dateTime;
        if (DateTime.TryParse(controllerContext.HttpContext.Request.QueryString["dateTime"], CultureInfo.GetCultureInfo("en-GB"), DateTimeStyles.None, out dateTime))
            return dateTime;
        //else
        return new DateTime();//or another appropriate default ;
    }
    #endregion
}

и команда в Application_Start () Global.asax

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
Дмитрий
источник
Это неплохая отправная точка, но она реализуется неправильно IModelBinder, особенно в отношении проверки. Кроме того , он работает только если имя из DateTimeявляется DATETIME .
Сэм
2
Кроме того, я обнаружил, что это DateTime?работает, только если вы добавляете еще один вызов с ModelBinders.Binders.Addпомощью typeof(DateTime?).
Сэм
8

Также стоит отметить, что даже без создания собственного связывателя модели можно анализировать несколько различных форматов.

Например, в США все следующие строки эквивалентны и автоматически привязываются к одному и тому же значению DateTime:

/ Компания / Пресс / май% 2001% 202008

/ Компания / Пресса / 2008-05-01

/ Компания / Пресса / 05.01.2008

Я настоятельно рекомендую использовать гггг-мм-дд, потому что он намного более переносимый. Вы действительно не хотите иметь дело с обработкой нескольких локализованных форматов. Если кто-то забронирует рейс 1 мая вместо 5 января, у вас будут большие проблемы!

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

Simon_Weaver
источник
3
Поскольку никто не говорит, что yyyy-MM-dd не универсален, я думаю, что это так.
deerchao 03
на мой взгляд, это лучше, чем использовать скоросшиватель моделей. .datepicker ("option", "dateFormat", "yy-mm-dd") или просто установите его по умолчанию.
Барт Каликсто
6

Попробуйте использовать toISOString (). Возвращает строку в формате ISO8601.

ПОЛУЧИТЬ метод

Javascript

$.get('/example/doGet?date=' + new Date().toISOString(), function (result) {
    console.log(result);
});

C #

[HttpGet]
public JsonResult DoGet(DateTime date)
{
    return Json(date.ToString(), JsonRequestBehavior.AllowGet);
}

Метод POST

Javascript

$.post('/example/do', { date: date.toISOString() }, function (result) {
    console.log(result);
});

C #

[HttpPost]
public JsonResult Do(DateTime date)
{
     return Json(date.ToString());
}
rnofenko
источник
5

Я установил приведенную ниже конфигурацию на свой MVC4, и она работает как шарм

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
источник
3
Это сработало и для меня. Обратите внимание, что элемент глобализации находится в разделе Configuration> System.Web. msdn.microsoft.com/en-us/library/hy4kkhe0%28v=vs.85%29.aspx
Jowen
1
  public class DateTimeFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.RequestType == "GET")
        {

            foreach (var parameter in filterContext.ActionParameters)
            {
                var properties = parameter.Value.GetType().GetProperties();

                foreach (var property in properties)
                {
                    Type type = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

                    if (property.PropertyType == typeof(System.DateTime) || property.PropertyType == typeof(DateTime?))
                    {
                        DateTime dateTime;

                        if (DateTime.TryParse(filterContext.HttpContext.Request.QueryString[property.Name], CultureInfo.CurrentUICulture, DateTimeStyles.None, out dateTime))
                            property.SetValue(parameter.Value, dateTime,null);
                    }
                }

            }
        }
    }
}
Тобиас
источник
Это не работает дд-ММ-гггг все еще не распознается
jao
1
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var str = controllerContext.HttpContext.Request.QueryString[bindingContext.ModelName];
    if (string.IsNullOrEmpty(str)) return null;
    var date = DateTime.ParseExact(str, "dd.MM.yyyy", null);
    return date;
}
Тет
источник
1
Вам следует сделать свой ответ более насыщенным, добавив несколько пояснений.
Александр Лавуа
По памяти это не согласуется с тем, как работают встроенные связыватели модели, поэтому может не иметь такого же вторичного поведения, как сохранение типизированного значения для проверки.
Сэм
1

Я установил CurrentCultureи CurrentUICultureсвой собственный базовый контроллер

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }
Korayem
источник