MVC DateTime привязка с неправильным форматом даты
Asp.net-MVC теперь позволяет неявно связывать объекты DateTime. У меня есть действие в соответствии с
public ActionResult DoSomething(DateTime startDate)
{
...
}
Это успешно преобразует строку из вызова ajax в DateTime. Однако мы используем формат даты dd/MM/yyyy; MVC конвертируется в MM / дд / гггг. Например, отправка вызова к действию со строкой '09/02/2009'приводит к DateTime '02/09/2009 00:00:00' или 2 сентября в наших локальных настройках.
Я не хочу накатывать свою собственную модель для подшивки ради формата даты. Но, кажется, нет необходимости менять действие, чтобы принимать строку, а затем использовать DateTime.Parse, если MVC способен сделать это для меня.
Есть ли способ изменить формат даты, используемый в связывателе модели по умолчанию для DateTime? Разве связыватель модели по умолчанию не должен использовать ваши настройки локализации в любом случае?
10 ответов
Я только что нашел ответ на это с помощью более исчерпывающего поиска в Google:
У Melvyn Harbour есть подробное объяснение того, почему 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, когда они записаны в форму, и поэтому должны учитывать их при возврате из формы.
Я бы глобально установил ваши культуры. ModelBinder подними это!
<system.web>
<globalization uiCulture="en-AU" culture="en-AU" />
Или вы просто измените это для этой страницы.
Но глобально в web.config я думаю, что лучше
У меня возникла та же проблема с короткой привязкой формата даты к свойствам модели 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, я также добавил новый систатический класс в папку 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
Я сделал это, и фактическое значение на странице было в формате "ММ / дд / гггг чч: мм: сс тт" вместо "дд / мм / гггг чч: мм: сс тт", как я и хотел. Это привело к сбою проверки моей модели или возвращению неверной даты (очевидно, меняя местами значения дня и месяца).
После многих попыток поцарапать голову и потерпеть неудачу, было решено установить информацию о культуре для каждого запроса, выполнив это в 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/
Гарик - /questions/5763474/zadat-format-datyi-v-tege-globalizatsii-aspnet-webconfig/5763493#5763493
Дмитрий - /questions/8643024/mvc-datetime-privyazka-s-nepravilnyim-formatom-datyi/8643040#8643040
Это будет немного отличаться в 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());
Стоит также отметить, что даже без создания собственной связующей модели несколько разных форматов могут быть проанализированы.
Например, в США все следующие строки эквивалентны и автоматически привязываются к одному и тому же значению DateTime:
/ Компания / Пресс / май% 2001% 202008
/ Компания / Пресса / 2008-05-01
/ Компания / Пресса /05.01.2008
Я настоятельно рекомендую использовать yyyy-mm-dd, потому что он намного более портативный. Вы действительно не хотите иметь дело с обработкой нескольких локализованных форматов. Если кто-то забронирует рейс 1 мая вместо 5 января, у вас будут большие проблемы!
NB: Я не совсем уверен, если yyyy-mm-dd универсально анализируется во всех культурах, так что, возможно, кто-то, кто знает, может добавить комментарий.
Попробуйте использовать 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());
}
Я установил следующий конфиг на моем MVC4, и он работает как шарм
<globalization uiCulture="auto" culture="auto" />
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;
}
Я поставил 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");
}
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);
}
}
}
}
}
}