MVC日期时间绑定格式不正确

138

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',或者说是在我们本地设置中的九月二日。

我不想为了一个日期格式而自己编写模型绑定器。但如果 MVC 能够为我完成这个工作,那么更改操作以接受一个字符串,然后使用 DateTime.Parse 看起来有点不必要。

是否有任何方法可以更改 DateTime 默认模型绑定器使用的日期格式?默认模型绑定器不应该使用您的本地化设置吗?


嘿..只需将您的系统日期格式从DD/MM/yyyy更改为MM/DD/yyyy,问题就解决了..我也遇到了同样的问题,通过更改系统日期格式来解决它。 - banny
@banny,如果应用程序是全局的,并且每个人的日期时间格式可能不同,你该怎么办?你不应该去更改每个人的日期时间格式。 - Ravi Mehta
这些答案都没有帮到我。表单需要本地化。有些用户可能会按一种方式显示日期,而其他用户则按另一种方式显示。在web.config或global.asax中设置某些内容是行不通的。我会继续寻找更好的答案,但其中一种方法就是将日期作为字符串处理,直到回到c#。 - astrosteve
10个回答

166

通过更全面的谷歌搜索,我刚找到了答案:

Melvyn Harbour非常详细地解释了MVC为什么会以这种方式处理日期,以及如何在必要时覆盖它:

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

在查找要解析的值时,框架按特定顺序查找,即:

  1. RouteData(未在上面显示)
  2. URI查询字符串
  3. 请求表单

然而,只有最后一个是与文化相关的。从本地化的角度来看,这是有很好的原因的。想象一下,我编写了一个显示航空公司航班信息的Web应用程序,发布在网上。我通过点击那天的链接(例如http://www.melsflighttimes.com/Flights/2008-11-21)查找某个日期的航班,然后想将该链接电邮给美国的同事。为了保证我们都在查看相同的数据页面,我们只能使用InvariantCulture。相比之下,如果我使用表单预订我的航班,则一切都在一个紧密的周期内发生。当将数据写入表单时,可以遵循CurrentCulture,因此当从表单返回时需要遵循它。


好的。在发布问题后的48小时内,该功能将被禁用。 - Sam Wessel
48
我强烈反对技术上这是正确的说法。模型绑定器在处理POST和GET时应该始终表现一致。如果不变文化是GET的正确方式,那么在处理POST时也应该采用相同的方式。根据HTTP动词改变行为是荒谬的。 - Bart Calixto
我有一个问题,我们的网站托管在另一个国家,它需要 MM/dd/yyyy 格式,否则会显示验证错误 The field BeginDate must be a date.,我该如何让服务器接受 dd/MM/yyyy 格式? - Shaiju T
URL参数应该是明确的,像使用ISO标准格式化。那么,文化设置将不重要。 - nforss
这个链接解释了问题的原因,但并没有提供任何最简单的解决方案(例如通过从脚本中发布ISO日期时间字符串),这种方法总是有效的,我们不必担心在服务器上设置特定的文化或确保日期时间格式在服务器和客户端之间完全相同。 - Hopeless

38

我建议全局设置你的文化信息。这样ModelBinder就可以识别它了!

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

或者您只需更改此页面的设置。
但我认为在web.config文件中进行全局配置更好。


28
对我来说并没有成功。如果我输入 23/10/2010,日期仍会显示为空值。 - GONeale
我认为这是最简单的解决方案。对于我来说,只需更改所有Date.ToString()中的格式即可。我认为它也适用于绑定,但我没有检查,抱歉 :-( - Sergey Makridenkov
1
我将我的开发服务器日期格式设置为dd/MM/yyyy。 ModelBinder使用的是MM/dd/yyyy格式。 在web.config中设置格式为dd/MM/yyyy现在强制ModelBinder使用欧洲格式。 在我看来,它应该使用服务器的日期设置。 无论如何,你解决了我的问题。 - Karel
那个对我完美地运作了......但是感觉有点奇怪,因为我知道我的应用服务器在英国运行英国的操作系统... :/ - Tallmaris
在 Azure 网站上运行的 MVC4 中完美运作。 - Matty Bear

33

我一直遇到将短日期格式绑定到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 文件中注册的方式,我还在我的 MVC4 项目的 App_Start 文件夹中添加了一个名为 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());
        }
    }
}
我只需要在Global ASASX的Application_Start中调用静态的RegisterCustomModelBinders方法,方法如下:

我然后只需像这样调用静态的RegisterCustomModelBinders:

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中,它都不会起作用,因为那会将它分配给会话的当前线程。正如你所知,Web应用程序是无状态的,因此服务于先前请求的线程与服务于当前请求的线程不同,因此你的区域性信息已经消失在数字天空的大GC中。

感谢:

Ivan Zlatev - http://ivanz.com/2010/11/03/custom-model-binding-using-imodelbinder-in-asp-net-mvc-two-gotchas/

garik - https://dev59.com/RUzSa4cB1Zd3GeqPoaQz#2468447

Dmitry - https://dev59.com/uXRB5IYBdhLWcg3wvpmc#11903896


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
}

在 Global.asax 的 Application_Start() 中执行的命令

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());

这是一个不错的起点,但它没有正确实现IModelBinder,特别是在验证方面。此外,它只适用于DateTime名称为dateTime的情况。 - Sam
2
另外,我发现只有在使用 typeof(DateTime?) 并添加另一个 ModelBinders.Binders.Add 调用时,DateTime? 才能正常工作。 - Sam

8
值得注意的是,即使没有创建自己的模型绑定器,也可以解析多种不同的格式。例如,在美国,以下所有字符串都是等效的,并自动绑定到相同的日期时间值:
/company/press/may%2001%202008 /company/press/2008-05-01 /company/press/05-01-2008
我强烈建议使用yyyy-mm-dd,因为它更加便携。您真的不想处理多个本地化格式。如果有人在5月1日而不是1月5日预订航班,您将面临巨大问题!
注:我不确定yyyy-mm-dd是否在所有文化中都被解析,因此也许有人知道可以添加评论。

4
既然没有人说 yyyy-MM-dd 不是通用格式,那我猜应该是通用的。 - deerchao
在我看来,这比使用模型绑定器更好。.datepicker("option", "dateFormat", "yy-mm-dd")或者只需在默认设置中设置它。 - Bart Calixto
yyyy-MM-dd是“国际”日期格式(ISO 8601)。同时也能很好地进行字母表排序。 - Nathan Goings

6

尝试使用toISOString()方法。它以ISO8601格式返回字符串。

GET方法

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

6

我在我的MVC4上设置了以下配置,它非常有效。

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

3
这对我也起作用。请注意,全球化元素位于“配置”>“System.Web”下。http://msdn.microsoft.com/en-us/library/hy4kkhe0%28v=vs.85%29.aspx - Jowen

1
我在我的自定义基础控制器中设置了 CurrentCultureCurrentUICulture
    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }

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

            }
        }
    }
}

这个不行。dd-MM-yyyy 仍然无法识别。 - 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
你应该添加一些解释,使你的答案更加丰富。 - Alexandre Lavoie
从记忆来看,这与内置的模型绑定器的工作方式不一致,因此可能没有相同的次要行为,例如保留类型化值以进行验证。 - Sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接