Привязка 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


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


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

5

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

<globalization uiCulture="auto" culture="auto" />

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");
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.