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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

У меня была такая же проблема с привязкой формата коротких дат к свойствам модели 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;
            }
        }
    }
}

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

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

garik - fooobar.com/questions/49925/...

Дмитрий - fooobar.com/questions/49919/...

Ответ 4

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

Ответ 5

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

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

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

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

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

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

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

Ответ 6

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

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

Ответ 7

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

Метод GET

Javascript

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

С#

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

С#

[HttpPost]
public JsonResult Do(DateTime date)
{
     return Json(date.ToString());
}

Ответ 8

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

            }
        }
    }
}

Ответ 9

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

Ответ 10

Я установил 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");
    }