MVC DateTime-Bindung mit falschem Datumsformat

132

Asp.net-MVC ermöglicht jetzt die implizite Bindung von DateTime-Objekten. Ich habe eine Aktion in der Art von

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

Dadurch wird eine Zeichenfolge aus einem Ajax-Aufruf erfolgreich in eine DateTime konvertiert. Wir verwenden jedoch das Datumsformat TT / MM / JJJJ; MVC konvertiert in MM / TT / JJJJ. Wenn Sie beispielsweise einen Aufruf der Aktion mit der Zeichenfolge '09 / 02/2009 'senden, wird in unseren lokalen Einstellungen eine DateTime von '02 / 09/2009 00:00:00' oder der 2. September angezeigt.

Ich möchte aus Gründen eines Datumsformats keinen eigenen Modellordner rollen. Es scheint jedoch unnötig zu sein, die Aktion zu ändern, um eine Zeichenfolge zu akzeptieren und dann DateTime.Parse zu verwenden, wenn MVC dies für mich tun kann.

Gibt es eine Möglichkeit, das im Standardmodellordner für DateTime verwendete Datumsformat zu ändern? Sollte der Standardmodellbinder Ihre Lokalisierungseinstellungen nicht trotzdem verwenden?

Sam Wessel
quelle
Hey .. Ändern Sie einfach Ihr System-Datumsformat - TT / MM / JJJJ in MM / TT / JJJJ und fertig. Ich habe auch das gleiche Problem, ich habe es durch Ändern des System-Datumsformats gelöst.
Banny
@banny Wenn die Anwendung global ist und möglicherweise nicht alle das gleiche Datums- und Uhrzeitformat haben, wie können Sie dies tun? , Sie nehmen nicht an, jedes Datum und Uhrzeitformat zu ändern.
Ravi Mehta
Keine dieser Antworten hilft mir. Das Formular muss lokalisiert werden. Einige Benutzer haben möglicherweise das Datum in die eine oder andere Richtung. Etwas in der web.config einrichten. oder global.asax wird nicht helfen. Ich werde weiter nach einer besseren Antwort suchen, aber eine Möglichkeit wäre, das Datum als Zeichenfolge zu behandeln, bis ich wieder zu c # komme.
Astrosteve

Antworten:

164

Ich habe gerade die Antwort darauf mit etwas ausführlicherem googeln gefunden:

Melvyn Harbour hat eine ausführliche Erklärung, warum MVC so mit Datumsangaben arbeitet und wie Sie diese bei Bedarf überschreiben können:

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

Bei der Suche nach dem zu analysierenden Wert sucht das Framework in einer bestimmten Reihenfolge, nämlich:

  1. RouteData (oben nicht gezeigt)
  2. URI-Abfragezeichenfolge
  3. Anfrageformular

Nur die letzten davon werden jedoch kulturbewusst sein. Dafür gibt es aus Sicht der Lokalisierung einen sehr guten Grund. Stellen Sie sich vor, ich habe eine Webanwendung geschrieben, die Fluginformationen enthält, die ich online veröffentliche. Ich suche Flüge zu einem bestimmten Datum nach, indem ich auf einen Link für diesen Tag klicke (vielleicht so etwas wie http://www.melsflighttimes.com/Flights/2008-11-21 ) und möchte diesen Link dann per E-Mail an meinen Kollegen in senden die USA. Wir können nur garantieren, dass wir beide dieselbe Datenseite betrachten, wenn die InvariantCulture verwendet wird. Wenn ich dagegen ein Formular verwende, um meinen Flug zu buchen, geschieht alles in einem engen Zyklus. Die Daten können die CurrentCulture berücksichtigen, wenn sie in das Formular geschrieben werden, und müssen sie daher berücksichtigen, wenn sie aus dem Formular zurückkehren.

Sam Wessel
quelle
Wird besorgt. Diese Funktion ist nach dem Posten der Frage für 48 Stunden deaktiviert.
Sam Wessel
43
Ich werde stark widersprechen, dass dies technisch korrekt ist. Der Modellbinder sollte sich bei POST und GET IMMER gleich verhalten. Wenn invariante Kultur der richtige Weg für GET ist, machen Sie es auch für POST. Das Verhalten in Abhängigkeit vom http-Verb zu ändern, ist unsinnig.
Bart Calixto
Ich habe eine Frage, unsere Website wird in einem anderen Land gehostet. Sie benötigt ein MM/dd/yyyyFormat. Andernfalls wird ein Validierungsfehler angezeigt The field BeginDate must be a date.. Wie kann ich den Server dazu bringen, das dd/MM/yyyyFormat zu akzeptieren ?
Shaijut
Der URL-Parameter sollte eindeutig sein, wie bei Verwendung der ISO-Standardformatierung. Dann wären Kultureinstellungen nicht wichtig.
Nforss
Dies gibt einen Link, der die Ursache erklärt, bietet jedoch keine einfachste Lösung für dieses Problem (z. B. durch Veröffentlichen einer ISO-Datums- / Uhrzeitzeichenfolge aus dem Skript). Dies funktioniert immer und wir müssen uns nicht darum kümmern, eine bestimmte Kultur auf dem zu setzen Server oder stellen Sie sicher, dass das Datum / Uhrzeit-Format zwischen Server und Client identisch ist.
Hoffnungsloser
36

Ich würde Ihre Kulturen global bestimmen. ModelBinder hol das ab!

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

Oder Sie ändern dies einfach für diese Seite.
Aber global in web.config finde ich das besser

Peter Gfader
quelle
27
Nicht für mich. Das Datum wird immer noch als null angezeigt, wenn ich den 23.10.2010 bestanden habe.
GONeale
Ich denke, es ist die einfachste Lösung. Für mich ändert es das Format in allen Date.ToString (). Ich denke, es wird auch mit Bindung funktionieren, aber nicht überprüft, sorry :-(
msa.im
1
Ich habe das Datumsformat meines Entwicklerservers auf TT / MM / JJJJ gesetzt. Der Modellbinder hat das Format MM / TT / JJJJ verwendet. Wenn Sie das Format in web.config auf TT / MM / JJJJ setzen, wird der Modellbinder nun gezwungen, das europäische Format zu verwenden. Meiner Meinung nach sollte es jedoch die Datumseinstellungen des Servers verwenden. Wie auch immer, du hast mein Problem gelöst.
Karel
Das hat perfekt für mich funktioniert ... irgendwie fühlte es sich seltsam an, da ich weiß, dass mein Anwendungsserver in Großbritannien ein britisches Betriebssystem hat ...: /
Tallmaris
Funktionierte perfekt in MVC4 auf Azure-Websites
Matty Bear
31

Ich habe das gleiche Problem mit der Bindung des kurzen Datumsformats an die DateTime-Modelleigenschaften. Nachdem ich mir viele verschiedene Beispiele angesehen habe (nicht nur in Bezug auf DateTime), habe ich Folgendes zusammengestellt:

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

Um sicherzustellen, dass Routen usw. in der globalen ASAX-Datei registriert werden, habe ich dem Ordner App_Start meines MVC4-Projekts mit dem Namen CustomModelBinderConfig eine neue sytatische Klasse hinzugefügt:

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

Ich rufe dann einfach die statischen RegisterCustomModelBinders von meinem globalen ASASX Application_Start wie folgt auf:

protected void Application_Start()
{
    /* bla blah bla the usual stuff and then */

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Ein wichtiger Hinweis hierbei ist, dass, wenn Sie einen DateTime-Wert in ein verstecktes Feld wie folgt schreiben:

@Html.HiddenFor(model => model.SomeDate) // a DateTime property
@Html.Hiddenfor(model => model) // a model that is of type DateTime

Ich habe das getan und der tatsächliche Wert auf der Seite hatte das Format "MM / TT / JJJJ hh: mm: ss tt" anstelle von "TT / MM / JJJJ hh: mm: ss tt", wie ich wollte. Dies führte dazu, dass meine Modellvalidierung entweder fehlschlug oder das falsche Datum zurückgab (offensichtlich wurden die Werte für Tag und Monat vertauscht).

Nach vielen Kopfkratzern und fehlgeschlagenen Versuchen bestand die Lösung darin, die Kulturinformationen für jede Anfrage festzulegen, indem Sie dies in Global.ASAX tun:

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

Es funktioniert nicht, wenn Sie es in Application_Start oder sogar Session_Start stecken, da es dem aktuellen Thread für die Sitzung zugewiesen wird. Wie Sie wissen, sind Webanwendungen zustandslos, sodass der Thread, der Ihre Anfrage zuvor bearbeitet hat, nicht derselbe Thread ist, der Ihre aktuelle Anfrage bearbeitet. Daher wurden Ihre Kulturinformationen an den großen GC im digitalen Himmel gesendet.

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

garik - https://stackoverflow.com/a/2468447/578208

Dmitry - https://stackoverflow.com/a/11903896/578208

WernerVA
quelle
13

In MVC 3 wird es etwas anders sein.

Angenommen, wir haben einen Controller und eine Ansicht mit der Get-Methode

public ActionResult DoSomething(DateTime dateTime)
{
    return View();
}

Wir sollten ModelBinder hinzufügen

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
}

und den Befehl in Application_Start () von Global.asax

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
Dmitry
quelle
Dies ist ein anständiger Ausgangspunkt, der jedoch IModelBinderinsbesondere in Bezug auf die Validierung nicht korrekt implementiert wird. Es auch, funktioniert nur , wenn der Name der DateTimeist Datums- und Uhrzeit .
Sam
2
Außerdem habe ich festgestellt, dass dies DateTime?nur funktioniert, wenn Sie einen weiteren Anruf ModelBinders.Binders.Addmit hinzufügen typeof(DateTime?).
Sam
8

Es ist auch erwähnenswert, dass auch ohne Erstellen eines eigenen Modellbinders mehrere verschiedene Formate analysiert werden können.

In den USA sind beispielsweise alle folgenden Zeichenfolgen gleichwertig und werden automatisch an denselben DateTime-Wert gebunden :

/ company / press / may% 2001% 202008

/ company / press / 2008-05-01

/ company / press / 05-01-2008

Ich würde dringend empfehlen, JJJJ-MM-TT zu verwenden, da es viel portabler ist. Sie möchten sich wirklich nicht mit der Verarbeitung mehrerer lokalisierter Formate befassen. Wenn jemand am 1. Mai statt am 5. Januar einen Flug bucht, werden Sie große Probleme haben!

NB: Ich bin mir nicht sicher, ob JJJJ-MM-TT in allen Kulturen allgemein analysiert wird. Vielleicht kann jemand, der es weiß, einen Kommentar hinzufügen.

Simon_Weaver
quelle
3
Da niemand sagt, dass JJJJ-MM-TT nicht universell ist, denke ich, dass es so ist.
Deerchao
Dies ist meiner Meinung nach besser als die Verwendung eines Modellbinders. .datepicker ("Option", "DateFormat", "JJ-MM-TT") oder setzen Sie es einfach auf die Standardeinstellungen.
Bart Calixto
6

Versuchen Sie, toISOString () zu verwenden. Es gibt eine Zeichenfolge im ISO8601-Format zurück.

GET-Methode

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-Methode

Javascript

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

c #

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

Ich habe die folgende Konfiguration auf meinem MVC4 eingestellt und es funktioniert wie ein Zauber

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
quelle
3
Das hat auch bei mir funktioniert. Beachten Sie, dass das Globalisierungselement unter Konfiguration> System.Web angezeigt wird. 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);
                    }
                }

            }
        }
    }
}
Tobias
quelle
Das funktioniert nicht. TT-MM-JJJJ wird immer noch nicht erkannt
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;
}
Teth
quelle
1
Sie sollten Ihre Antwort durch Hinzufügen einiger Erklärungen bereichern.
Alexandre Lavoie
Aus dem Speicher stimmt dies nicht mit der Funktionsweise der integrierten Modellbinder überein, sodass möglicherweise nicht das gleiche sekundäre Verhalten wie das Beibehalten des eingegebenen Werts für die Validierung vorliegt.
Sam
1

Ich setze CurrentCultureund CurrentUICulturemeinen benutzerdefinierten Basis-Controller

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

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