Associazione MVC DateTime con formato data errato


132

Asp.net-MVC ora consente l'associazione implicita di oggetti DateTime. Ho un'azione sulla falsariga di

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

Ciò converte correttamente una stringa da una chiamata Ajax in un DateTime. Tuttavia, utilizziamo il formato data gg / MM / aaaa; MVC sta convertendo in MM / gg / aaaa. Ad esempio, l'invio di una chiamata all'azione con una stringa '09 / 02/2009 'genera un DateTime di '02 / 09/2009 00:00:00' o il 2 settembre nelle nostre impostazioni locali.

Non voglio creare il mio modello di raccoglitore per il gusto di un formato data. Ma sembra inutile dover modificare l'azione per accettare una stringa e quindi utilizzare DateTime.Parse se MVC è in grado di farlo per me.

Esiste un modo per modificare il formato della data utilizzato nel raccoglitore modello predefinito per DateTime? Il raccoglitore di modelli predefinito non dovrebbe comunque utilizzare le impostazioni di localizzazione?


Ehi .. Basta cambiare il formato della data del sistema - GG / MM / aaaa in MM / GG / aaaa e l'ho fatto .. Ho anche lo stesso problema, l'ho risolto cambiando il formato della data di sistema.
Banny,

@banny se l'applicazione è globale e potrebbe non avere tutti lo stesso formato di data e ora, come puoi farlo? , non supponi di andare a cambiare il formato di ogni ora e data.
Ravi Mehta,

Nessuna di queste risposte mi sta aiutando. Il modulo deve essere localizzato. Alcuni utenti potrebbero avere la data in un modo, altri nell'altro. Configurare qualcosa nel web.config. o global.asax non aiuterà. Continuerò a cercare una risposta migliore, ma un modo sarebbe solo quello di gestire la data come una stringa fino a quando non torno a c #.
Astrosteve,

Risposte:


164

Ho appena trovato la risposta a questo con alcuni googling più esaustivi:

Melvyn Harbour ha una spiegazione approfondita del perché MVC funziona con le date nel modo in cui funziona e come è possibile ignorarlo se necessario:

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

Quando si cerca il valore da analizzare, il framework cerca in un ordine specifico, ovvero:

  1. RouteData (non mostrato sopra)
  2. Stringa di query URI
  3. Modulo di richiesta

Tuttavia, solo l'ultimo di questi sarà consapevole della cultura. C'è un'ottima ragione per questo, dal punto di vista della localizzazione. Immagina di aver scritto un'applicazione web che mostra le informazioni sui voli delle compagnie aeree che pubblico online. Cerco i voli in una determinata data facendo clic su un link per quel giorno (forse qualcosa come http://www.melsflighttimes.com/Flights/2008-11-21 ), e quindi voglio inviare via e-mail quel link al mio collega in gli Stati Uniti. L'unico modo in cui potremmo garantire che entrambi guarderemo la stessa pagina di dati è se si utilizza InvariantCulture. Al contrario, se sto usando un modulo per prenotare il mio volo, tutto sta accadendo in un ciclo ristretto. I dati possono rispettare CurrentCulture quando sono scritti nel modulo e quindi devono rispettarli quando tornano dal modulo.


Andrà bene. Tale funzionalità è disabilitata per 48 ore dopo la pubblicazione della domanda.
Sam Wessel,

43
Non sarò assolutamente d'accordo sul fatto che tecnicamente questo sia corretto. Il raccoglitore modello dovrebbe comportarsi SEMPRE lo stesso con POST e GET. Se la cultura invariante è la strada da percorrere per OTTENERE, fallo anche per il POST. Modificare il comportamento in base al verbo http non ha senso.
Bart Calixto,

ho una domanda, il nostro sito Web è ospitato in un altro paese, ha bisogno di un MM/dd/yyyyformato altrimenti mostra un errore di convalida The field BeginDate must be a date., come posso fare per accettare il dd/MM/yyyyformato?
shaijut,

Il parametro URL dovrebbe essere inequivocabile, come usare la formattazione standard ISO. Quindi le impostazioni della cultura non avrebbero importanza.
nforss

questo fornisce un link che spiega la causa ma in realtà non fornisce alcuna soluzione più semplice a questo problema (ad esempio pubblicando una stringa datetime ISO dallo script), in questo modo funziona sempre e non dobbiamo preoccuparci di impostare una cultura specifica sul server o assicurarsi che il formato datetime sia identico tra server e client.
Senza speranza

36

Definirei globalmente le tue culture. ModelBinder raccoglilo!

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

O semplicemente lo cambi per questa pagina.
Ma a livello globale in web.config penso sia meglio


27
Non per me. La data viene comunque annullata se passo il 23/10/2010.
GONeale,

Penso che sia la soluzione più semplice. Per me cambia formato in tutto Date.ToString (). Penso che funzionerà anche con l'associazione, ma non ho controllato, mi dispiace :-(
msa.im

1
Ho il dataformat dei miei server di sviluppo impostato su gg / MM / aaaa Il modelbinder ha usato il formato MM / gg / aaaa. L'impostazione del formato in web.config su gg / MM / aaaa ora impone al rilegatore di modelli di utilizzare il formato europeo. Secondo me, però, dovrebbe usare le impostazioni della data del server. Comunque hai risolto il mio problema.
Karel,

Ha funzionato perfettamente per me ... in qualche modo mi è sembrato strano anche se so che il mio server delle applicazioni è nel Regno Unito con un sistema operativo del Regno Unito ...: /
Tallmaris

Ha funzionato perfettamente in MVC4 in esecuzione su siti Web di Azure
Matty Bear il

31

Ho avuto lo stesso problema con il formato data breve associato alle proprietà del modello DateTime. Dopo aver visto molti esempi diversi (non solo relativi a DateTime) ho messo insieme il seguente:

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

Per mantenere il modo in cui le rotte ecc. Sono registrate nel file ASAX globale, ho anche aggiunto una nuova classe sytatic alla cartella App_Start del mio progetto MVC4 denominata 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());
        }
    }
}

Quindi chiamo semplicemente RegisterCustomModelBinders statico dal mio Global ASASX Application_Start in questo modo:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Una nota importante qui è che se scrivi un valore DateTime in un campo nascosto come questo:

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

L'ho fatto e il valore effettivo sulla pagina era nel formato "MM / gg / aaaa hh: mm: ss tt" invece di "gg / MM / aaaa hh: mm: ss tt" come volevo. Ciò ha causato il fallimento della convalida del mio modello o la restituzione della data errata (ovviamente scambiando i valori di giorno e mese).

Dopo un sacco di grattacapi e tentativi falliti, la soluzione era quella di impostare le informazioni sulla cultura per ogni richiesta facendo questo nel 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;
}

Non funzionerà se lo attacchi in Application_Start o anche in Session_Start poiché lo assegna al thread corrente per la sessione. Come ben sapete, le applicazioni Web sono apolidi, quindi il thread che ha soddisfatto la richiesta in precedenza è lo stesso thread che serve la richiesta corrente, quindi le informazioni sulla cultura sono andate nel grande GC nel cielo digitale.

Grazie a: 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


13

Sarà leggermente diverso in MVC 3.

Supponiamo di avere un controller e una vista con il metodo Get

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

Dovremmo aggiungere 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
}

e il comando in Application_Start () di Global.asax

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

Questo è un buon punto di partenza, ma non si implementa correttamente IModelBinder, in particolare per quanto riguarda la convalida. Inoltre, funziona solo se il nome di DateTimeè dateTime .
Sam,

2
Inoltre, ho scoperto che DateTime?s solo il lavoro se si aggiunge un'altra chiamata a ModelBinders.Binders.Addcon typeof(DateTime?).
Sam,

8

Vale anche la pena notare che anche senza creare il proprio raccoglitore modello è possibile analizzare più formati diversi.

Ad esempio, negli Stati Uniti tutte le seguenti stringhe sono equivalenti e vengono automaticamente associate allo stesso valore DateTime:

/ Società / stampa / maggio% 2001% 202008

/ Società / stampa / 2008-05-01

/ Società / stampa / 05-01-2008

Consiglio vivamente di usare yyyy-mm-dd perché è molto più portatile. Non vuoi davvero occuparti della gestione di più formati localizzati. Se qualcuno prenota un volo il 1 ° maggio invece del 5 gennaio avrai grossi problemi!

NB: Non sono chiaramente esatto se yyyy-mm-dd è analizzato universalmente in tutte le culture, quindi forse qualcuno che sa può aggiungere un commento.


3
Dal momento che nessuno dice che yyyy-MM-dd non è universale, immagino che lo sia.
Deerchao,

questo secondo me è meglio che usare un raccoglitore di modelli. .datepicker ("opzione", "dateFormat", "yy-mm-dd") o semplicemente impostarlo nelle impostazioni predefinite.
Bart Calixto,

6

Prova a usare toISOString (). Restituisce una stringa in formato ISO8601.

OTTIENI il metodo

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

Metodo POST

javascript

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

c #

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


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

            }
        }
    }
}

Questo non funziona gg-MM-aaaa non è ancora riconosciuto
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
Dovresti arricchire la tua risposta aggiungendo alcune spiegazioni.
Alexandre Lavoie,

Dalla memoria, questo non è coerente con il modo in cui funzionano i raccoglitori di modelli incorporati, quindi potrebbe non avere lo stesso comportamento secondario come il mantenimento del valore digitato per la convalida.
Sam,

1

Ho impostato CurrentCulturee il CurrentUICulturemio controller di base personalizzato

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

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.