关于asp.net mvc:MVC DateTime绑定的日期格式不正确

MVC DateTime binding with incorrect date format

Asp.net-MVC现在允许隐式绑定DateTime对象。 我有一个行动

1
2
3
4
public ActionResult DoSomething(DateTime startDate)
{
...
}

这成功地将字符串从ajax调用转换为DateTime。 但是,我们使用日期格式dd / MM / yyyy; MVC正在转换为MM / dd / yyyy。
例如,使用字符串'09 / 02/2009'提交对操作的调用会导致DateTime为'02 / 09/2009 00:00:00',或者在我们的本地设置中为9月2日。

我不想为了日期格式而滚动我自己的模型绑定器。 但是,如果MVC能够为我执行此操作,则无需更改操作以接受字符串然后使用DateTime.Parse。

有没有办法改变DateTime的默认模型绑定器中使用的日期格式? 默认型号绑定器不应该使用您的本地化设置吗?


我刚刚用一些更详尽的谷歌搜索找到了答案:

Melvyn Harbour详细解释了为什么MVC使用日期的方式,以及如何在必要时覆盖它:

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

When looking for the value to parse, the framework looks in a specific order namely:

  • RouteData (not shown above)
  • URI query string
  • Request form
  • Only the last of these will be culture aware however. There is a very good reason for this, from a localization perspective. Imagine that I have written a web application showing airline flight information that I publish online. I look up flights on a certain date by clicking on a link for that day (perhaps something like http://www.melsflighttimes.com/Flights/2008-11-21), and then want to email that link to my colleague in the US. The only way that we could guarantee that we will both be looking at the same page of data is if the InvariantCulture is used. By contrast, if I'm using a form to book my flight, everything is happening in a tight cycle. The data can respect the CurrentCulture when it is written to the form, and so needs to respect it when coming back from the form.


    我会全球设定你的文化。 ModelBinder选择了!

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

    或者您只需更改此页面。
    但是在web.config中我认为更好


    我一直遇到与DateTime模型属性绑定的短日期格式相同的问题。在查看了许多不同的例子(不仅仅是关于DateTime)之后,我将以下内容放在一起:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    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文件夹中添加了一个新的sytatic类,名为CustomModelBinderConfig:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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,如下所示:

    1
    2
    3
    4
    5
    6
    protected void Application_Start()
    {
        /* bla blah bla the usual stuff and then */

        CustomModelBindersConfig.RegisterCustomModelBinders();
    }

    这里一个重要的注意事项是,如果您将DateTime值写入这样的隐藏字段:

    1
    2
    @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中为每个请求设置文化信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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/

    加里克 - https://stackoverflow.com/a/2468447/578208

    德米特里 - https://stackoverflow.com/a/11903896/578208


    它在MVC 3中会略有不同。

    假设我们有一个控制器和一个带Get方法的视图

    1
    2
    3
    4
    public ActionResult DoSomething(DateTime dateTime)
    {
        return View();
    }

    我们应该添加ModelBinder

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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()中的命令

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


    值得注意的是,即使没有创建自己的模型绑定器,也可以解析多种不同的格式。

    例如,在美国,以下所有字符串都是等效的,并自动绑定到相同的DateTime值:

    /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在所有文化中得到普遍解析,我也不清楚,所以也许有人知道可以添加评论。


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

    GET方法

    JavaScript的

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

    C#

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

    POST方法

    JavaScript的

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

    C#

    1
    2
    3
    4
    5
    [HttpPost]
    public JsonResult Do(DateTime date)
    {
         return Json(date.ToString());
    }


    我在我的MVC4上设置了以下配置,它就像一个魅力

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


    1
    2
    3
    4
    5
    6
    7
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
      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);
                        }
                    }

                }
            }
        }
    }


    我设置CurrentCultureCurrentUICulture我的自定义基本控制器

    1
    2
    3
    4
    5
    6
    7
        protected override void Initialize(RequestContext requestContext)
        {
            base.Initialize(requestContext);

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