wake-up-neo.com

MVC DateTime-Bindung mit falschem Datumsformat

Asp.net-MVC ermöglicht jetzt die implizite Bindung von DateTime-Objekten. Ich habe eine Aktion nach dem Vorbild 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 an die Aktion mit der Zeichenfolge '09/02/2009 'senden, ergibt sich ein DateTime von '02/09/2009 00:00:00' oder der 2. September in unseren lokalen Einstellungen.

Ich möchte aus Gründen eines Datumsformats keinen eigenen Aktenordner rollen. Es erscheint jedoch unnötig, 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 Standard-Modellordner Ihre Lokalisierungseinstellungen nicht trotzdem verwenden?

127
Sam Wessel

Ich habe gerade die Antwort auf diese Frage mit etwas ausführlicherem Googeln gefunden:

In Melvyn Harbour wird ausführlich erläutert, warum MVC mit Datumsangaben so arbeitet und wie Sie diese bei Bedarf außer Kraft setzen können:

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

Bei der Suche nach dem zu analysierenden Wert sieht das Framework in einer bestimmten Reihenfolge aus:

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

Kulturbewusst werden jedoch nur die letzten sein. Aus Sicht der Lokalisierung gibt es dafür einen sehr guten Grund. Stellen Sie sich vor, ich habe eine Webanwendung geschrieben, die Fluginformationen von Fluggesellschaften enthält, die ich online veröffentliche. Ich suche Flüge an einem bestimmten Datum, 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 dann E-Mail diesen Link an meinen Kollegen in den USA. Der einzige Weg, auf dem wir garantieren können, dass wir beide dieselbe Seite mit Daten betrachten, ist, wenn InvariantCulture verwendet wird. Wenn ich hingegen 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 diese berücksichtigen, wenn sie vom Formular zurückkehren.

163
Sam Wessel

Ich würde Ihre Kulturen global setzen. ModelBinder holen 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

35
Peter Gfader

Ich habe das gleiche Problem mit der Bindung des kurzen Datumsformats an DateTime-Modelleigenschaften. Nachdem ich mir viele verschiedene Beispiele (nicht nur zu DateTime) angesehen habe, 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 zu gewährleisten, dass Routen usw. in der Global ASAX-Datei registriert sind, habe ich dem Ordner App_Start meines MVC4-Projekts mit dem Namen CustomModelBinderConfig eine neue systematische 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 Global ASASX Application_Start wie folgt auf:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Ein wichtiger Hinweis hier ist, dass, wenn Sie einen DateTime-Wert in ein Hiddenfield 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 von mir gewünschte Format "MM/TT/JJJJ HH: MM: TT: MM: TT: MM: JJJJ HH: MM: TT". Dies führte dazu, dass meine Modellvalidierung entweder fehlschlug oder das falsche Datum zurückgab (offensichtlich vertauschten sich die Werte für Tag und Monat).

Nach vielen Kopfkratzern und fehlgeschlagenen Versuchen bestand die Lösung darin, die Kulturinformationen für jede Anforderung im Global.ASAX festzulegen:

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 speichern, da es dem aktuellen Thread für die Sitzung zugewiesen wird. Wie Sie wissen, sind Webanwendungen statusfrei, sodass der Thread, der Ihre Anfrage zuvor bearbeitet hat, nicht derselbe Thread ist, der Ihre aktuelle Anfrage bearbeitet.

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

29
WernerVA

In MVC 3 wird es etwas anders sein.

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

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());
13
Dmitry

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

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

/ unternehmen/presse/mai% 2001% 202008

/ unternehmen/presse/2008-05-01

/ unternehmen/presse/01.05.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.

8
Simon_Weaver

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

<globalization uiCulture="auto" culture="auto" />
5
JeeShen Lee

Versuchen Sie, toISOString () zu verwenden. Es wird ein String im ISO8601-Format zurückgegeben.

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

            }
        }
    }
}
1
tobias
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
Teth

Ich habe CurrentCulture und CurrentUICulture für meinen benutzerdefinierten Basiscontroller festgelegt

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

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