Associazione MVC DateTime con formato data errato

Asp.net-MVC ora consente il binding implicito di oggetti DateTime. Ho un’azione sulla falsariga di

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

Questo 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 voglio stampare il mio modello di legatura 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?

Ho appena trovato la risposta a questo con un googling più esaustivo:

Melvyn Harbour ha una spiegazione esauriente del perché MVC lavora con le date come fa e di come puoi sovrascriverlo 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 guarda in un ordine specifico e cioè:

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

Solo l’ultimo di questi sarà comunque consapevole della cultura. 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. Cerco i 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 in gli 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.

Definirei globalmente le tue culture. ModelBinder lo raccoglie!

    

O semplicemente lo cambi per questa pagina.
Ma globalmente in web.config penso sia meglio

Ho riscontrato lo stesso problema con il bind del formato di data breve per le proprietà del modello DateTime. Dopo aver esaminato molti esempi diversi (non solo riguardo a 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 il modo in cui i percorsi, ecc. Sono registrati nel file ASAX globale, ho aggiunto anche una nuova class sytatic alla cartella App_Start del mio progetto MVC4 denominato 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 il RegisterCustomModelBinders statico dal mio Global ASASX Application_Start come questo:

 protected void Application_Start() { /* bla blah bla the usual stuff and then */ CustomModelBindersConfig.RegisterCustomModelBinders(); } 

Una nota importante qui è che se scrivi un valore DateTime su 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 nella 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 mia convalida del modello o il ripristino della data errata (ovviamente invertendo i valori del giorno e del mese).

Dopo un sacco di grattacapo e tentativi falliti la soluzione era quella di impostare le informazioni sulla cultura per ogni richiesta facendo questo in 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é questo 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 passate al 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

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

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

Ad esempio negli Stati Uniti tutte le stringhe seguenti 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

Suggerisco caldamente 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.

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

  

Prova a usare toISOString (). Restituisce la stringa nel 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()); } 
  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); } } } } } } 
 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; } 

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