2009-02-09 2 views
119

Asp.net-MVC ora consente il binding implicito di oggetti DateTime. Ho un'azione sulla falsariga diMVC DateTime binding con formato data errato

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

Converte correttamente una stringa da una chiamata ajax in un DateTime. Tuttavia, usiamo 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 'ha come risultato un DateTime di '02/09/2009 00:00:00' o il 2 settembre nelle nostre impostazioni locali.

Non desidero eseguire il rolling del mio modello di raccoglitore per motivi di formato data. Ma sembra inutile dover cambiare l'azione per accettare una stringa e quindi utilizzare DateTime.Parse se MVC è in grado di farlo per me.

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

+0

Hey .. Basta cambiare la data di sistema Formato- GG/MM/yyyy a MM/DD/aaaa e fatto. Ho anche lo stesso problema, l'ho risolto modificando il formato della data del sistema. – banny

+0

@banny se l'applicazione è globale e potrebbe essere che nessuno ha lo stesso formato di data, come è possibile farlo? , non supponiamo di andare a cambiare ogni ora Data Formato .. –

+0

Nessuna di queste risposte mi sta aiutando. Il modulo deve essere localizzato. Alcuni utenti possono avere la data in un modo, altri nell'altro modo. Impostazione di qualcosa nel web.config. o global.asax non aiuterà. Continuerò a cercare una risposta migliore, ma un modo sarebbe solo quello di trattare la data come una stringa finché non torno a C#. – astrosteve

risposta

151

ho appena trovato la risposta a questa con un po 'googling più esaustivo:

Melvyn Harbour ha una spiegazione approfondita del perché MVC lavora con date il modo in cui lo fa, e come si può ignorare questo se necessario:

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

Quando si cerca il valore da analizzare, il quadro si presenta in un ordine specifico e cioè:

  1. RouteData (non mostrate sopra)
  2. URI stringa di query
  3. Richiesta forma

solo l'ultimo di questi saranno cultura però consapevoli. C'è una buona 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. Ricerco voli in una certa data facendo clic su un link per quel giorno (forse qualcosa come http://www.melsflighttimes.com/Flights/2008-11-21), e poi voglio inviare via email quel link al mio collega negli Stati Uniti. L'unico modo in cui possiamo garantire che entrambi stiamo guardando 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 rispettarlo quando torna dal modulo.

+0

Lo farà. Quella funzionalità è disabilitata per 48 ore dopo aver postato la domanda. –

+37

Sarò fortemente in disaccordo sul fatto che tecnicamente sia corretto. Il modello legatore dovrebbe SEMPRE comportarsi allo stesso modo con POST e GET. Se la cultura invariante è la strada da percorrere per GET, fallo anche per POST. Cambiare il comportamento in base al verbo http non ha senso. –

+1

+1 ha votato per i chiarimenti sul problema. –

8

Vale anche la pena notare che anche senza creare il proprio modello di raccoglitore possono essere analizzabili più formati diversi.

Per esempio, in Stati Uniti tutte le seguenti stringhe sono equivalenti e automaticamente ottenere legato alla lo stesso valore DateTime:

/società/stampa/maggio% 2001% 202008

/società/stampa/2008-05-01

/società/stampa/05-01-2008

sarei forte Suggerisco di usare yyyy-mm-dd perché è molto più portatile.Davvero non vuoi occuparti della gestione di più formati localizzati. Se qualcuno prenota un volo il 1 ° maggio invece del 5 gennaio avrai grandi problemi!

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

+3

Dato che nessuno dice che yyyy-MM-dd non è universale, immagino lo sia. – deerchao

+0

questo è secondo me meglio di un legante modello. .datepicker ("option", "dateFormat", "yy-mm-dd") o semplicemente impostarlo nei valori predefiniti. –

34

Definirei globalmente le tue culture. ModelBinder lo raccoglie!

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

Oppure è sufficiente modificare questo per questa pagina.
Ma a livello globale nel web.config penso che sia meglio

+26

Non ha fatto per me. La data risulta ancora nulla se entro il 23/10/2010. – GONeale

+0

Penso che sia la soluzione più semplice. Per me cambia formato in tutte le Date.ToString(). Penso che funzionerà anche con il binding, ma non ho controllato, mi dispiace :-( – msa7

+1

Ho il mio server di date dateformat impostato su gg/MM/aaaa Il modelbinder ha utilizzato il formato MM/gg/aaaa Impostazione del formato nel Web .config a dd/MM/yyyy ora obbliga il modelbinder ad usare il formato europeo A mio parere dovrebbe comunque usare le impostazioni della data del server In ogni caso hai risolto il mio problema – Karel

13

E sarà leggermente diverso in MVC 3.

Supponiamo di avere un controller e una vista con 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()); 
+0

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

+2

Inoltre, ho trovato che 'DateTime?' Funziona solo se si aggiunge un'altra chiamata a 'ModelBinders.Binders.Add' con' typeof (DateTime?) '. – Sam

5

Ho impostato la configurazione di sotto sul mio MVC4 e funziona come un fascino

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

Questo ha funzionato anche per me. Si noti che l'elemento di globalizzazione va in Configurazione> System.Web. http://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); 
        } 
       } 

      } 
     } 
    } 
} 
+0

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 rendere la tua risposta più ricca aggiungendo alcune spiegazioni. –

+0

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

27

Ho avuto lo stesso problema con formato di data breve legame DateTime proprietà modello . Dopo aver guardato molti esempi diversi (non solo per quanto riguarda DateTime) ho messo insieme il follwing:

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 con il modo in cui le rotte, ecc sono regiseterd nel file globale ASAX Ho anche aggiunto una nuova classe sytatic al App_Start cartella del mio progetto denominato MVC4 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()); 
     } 
    } 
} 

ho poi basta chiamare i RegisterCustomModelBinders statiche dal mio globale ASASX Application_Start come questo:

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

    CustomModelBindersConfig.RegisterCustomModelBinders(); 
} 

Una nota importante è che se si scrive un valore DateTime ad un HiddenField come questo:

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

ho fatto e il valore effettivo sulla pagina era nel formato "MM hh/gg/aaaa: mm: ss tt "invece di" dd/MM/yyyy hh: mm: ss tt "come volevo. Ciò ha causato il fallimento della mia convalida del modello o il ripristino della data sbagliata (ovviamente invertendo i valori del giorno e del mese).

Dopo un sacco di testa di graffiare e falliti tentativi la soluzione era quella di impostare le informazioni di coltura per ogni richiesta in questo modo 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 ti infili in Application_Start o anche Session_Start poiché lo assegna al thread corrente per la sessione. Come ben sai, le applicazioni web sono prive di stato, quindi il thread che ha servito la tua richiesta in precedenza non è lo stesso thread che serve la tua richiesta corrente, quindi le informazioni sulla tua cultura sono andate al grande GC nel cielo digitale.

ringraziamenti vanno 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

3

tenta di utilizzare toISOString(). Restituisce la stringa nel formato ISO8601.

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

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

ho impostato CurrentCulture e CurrentUICulture il mio 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"); 
    }