17

Come posso comunicare al mio controller/modello quale tipo di cultura deve aspettarsi per l'analisi di un datetime?Problema di cultura datetime di ASP.NET MVC quando si restituisce il valore al controllore

stavo usando alcuni dei this post per implementare jQuery DatePicker nella mia applicazione MVC.

Quando invio la data in cui viene "perso nella traduzione" non sto utilizzando il formato USA per la data, quindi quando viene inviato al mio controller diventa semplicemente nullo.

Ho un form in cui l'utente sceglie una data:

@using (Html.BeginForm("List", "Meter", FormMethod.Get)) 
{ 
    @Html.LabelFor(m => m.StartDate, "From:") 
    <div>@Html.EditorFor(m => m.StartDate)</div> 

    @Html.LabelFor(m => m.EndDate, "To:") 
    <div>@Html.EditorFor(m => m.EndDate)</div> 
} 

Ho fatto un modello di modifica per questo, per l'attuazione del DatePicker jQuery:

@model DateTime 
@Html.TextBox("", Model.ToString("dd-MM-yyyy"), new { @class = "date" }) 

Ho quindi creare il DatePicker widget come questo.

$(document).ready(function() { 
    $('.date').datepicker({ dateFormat: "dd-mm-yy" }); 
}); 

Tutto questo funziona correttamente.

Qui è dove iniziano i problemi, questo è il mio controllo:

[HttpGet] 
public ActionResult List(DateTime? startDate = null, DateTime? endDate = null) 
{ 
    //This is where startDate and endDate becomes null if the dates dont have the expected formatting. 
} 

Questo è il motivo per cui mi piacerebbe raccontare in qualche modo il mio controller ciò che la cultura si deve aspettare? Il mio modello è sbagliato? posso in qualche modo dirmi quale cultura usare, come con gli attributi di annotazione dei dati?

public class MeterViewModel { 
    [Required] 
    public DateTime StartDate { get; set; } 
    [Required] 
    public DateTime EndDate { get; set; } 
} 

Edit: this link spiega il mio problema e una soluzione molto buona ad esso pure. Grazie a gdoron

+1

Utilizzare un formato per tutti richieste. http://stackoverflow.com/a/28219557/960997 – rnofenko

+0

@fomaa Ora utilizzo datepicker con [altField] (http://api.jqueryui.com/datepicker/#option-altField) e [altFormat] (http://api.jqueryui.com/datepicker/#option-altFormat) opzioni per fornire un campo nascosto con una versione di invarianza della data della cultura (come ISO8601 come si menziona). Quindi, presentando quel campo, ritengo che questa sia una soluzione migliore. – FRoZeN

risposta

11

È possibile creare un'estensione Raccoglitore per gestire la data nel formato cultura.

Questo è un esempio che ho scritto per gestire lo stesso problema con il tipo decimale, la speranza si ottiene l'idea

public class DecimalModelBinder : IModelBinder 
{ 
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
    ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
    ModelState modelState = new ModelState { Value = valueResult }; 
    object actualValue = null; 
    try 
    { 
     actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture); 
    } 
    catch (FormatException e) 
    { 
     modelState.Errors.Add(e); 
    } 

    bindingContext.ModelState.Add(bindingContext.ModelName, modelState); 
    return actualValue; 
    } 
} 

Aggiornamento

Per utilizzarlo è sufficiente dichiarare il legante in Global.asax come questo

protected void Application_Start() 
{ 
    AreaRegistration.RegisterAllAreas(); 
    RegisterGlobalFilters(GlobalFilters.Filters); 
    RegisterRoutes(RouteTable.Routes); 

    //HERE you tell the framework how to handle decimal values 
    ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder()); 

    DependencyResolver.SetResolver(new ETAutofacDependencyResolver()); 
} 

Quindi quando il modellatore deve fare un po 'di lavoro, saprà automaticamente cosa fare. Ad esempio, questa è un'azione con un modello che contiene alcune proprietà del tipo decimale. Semplicemente non faccio nulla

[HttpPost] 
public ActionResult Edit(int id, MyViewModel viewModel) 
{ 
    if (ModelState.IsValid) 
    { 
    try 
    { 
     var model = new MyDomainModelEntity(); 
     model.DecimalValue = viewModel.DecimalValue; 
     repository.Save(model); 
     return RedirectToAction("Index"); 
    } 
    catch (RulesException ex) 
    { 
     ex.CopyTo(ModelState); 
    } 
    catch 
    { 
     ModelState.AddModelError("", "My generic error message"); 
    } 
    } 
    return View(model); 
} 
+0

potresti mostrare un esempio dal tuo codice dove lo usi? – FRoZeN

+0

Aggiornamento della risposta con un esempio. Spero che possa aiutarti meglio – Iridio

+0

grazie, mi aiuta molto :) – FRoZeN

3

Quando si invia una data, si dovrebbe sempre provare a inviarla nel formato "aaaa-MM-gg". Ciò consentirà che diventi indipendente dalla cultura.

Normalmente ho un campo nascosto che mantiene la data in questo formato. Questo è relativamente semplice usando datepicker dell'interfaccia utente di jQuery.

+0

@Dibbyswift: stavo pensando al campo nascosto, ma non ero sicuro che fosse la strada da percorrere, dal momento che non volevo campi nascosti non necessari. Ma ora con una seconda opinione potrei andare in quella direzione. – FRoZeN

+0

Il vantaggio di un campo nascosto è che è possibile avere un campo "display" visibile che consente agli utenti di fornire una data in un formato di facile utilizzo, mentre il campo nascosto mantiene semplicemente il valore nel formato richiesto. – Digbyswift

10

Questo problema si verifica perché stai utilizzando il metodo GET sul tuo modulo. QueryString Value Provider in MVC utilizza sempre il formato data Invariante/Stati Uniti.Vedi: MVC DateTime binding with incorrect date format

ci sono tre soluzioni:

  1. cambiare il metodo POST.
  2. Come dice qualcun altro, modificare il formato della data su ISO 8601 "aaaa-mm-gg" prima dell'invio.
  3. Utilizzare un raccoglitore personalizzato per trattare sempre le date della stringa di query come GB. Se si esegue questa operazione si deve fare in modo che siano tutte le date in quella forma:

    public class UKDateTimeModelBinder : IModelBinder 
    { 
    private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
    
    /// <summary> 
    /// Fixes date parsing issue when using GET method. Modified from the answer given here: 
    /// https://stackoverflow.com/questions/528545/mvc-datetime-binding-with-incorrect-date-format 
    /// </summary> 
    /// <param name="controllerContext">The controller context.</param> 
    /// <param name="bindingContext">The binding context.</param> 
    /// <returns> 
    /// The converted bound value or null if the raw value is null or empty or cannot be parsed. 
    /// </returns> 
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
        var vpr = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
    
        if (vpr == null) 
        { 
         return null; 
    
        } 
    
        var date = vpr.AttemptedValue; 
    
        if (String.IsNullOrEmpty(date)) 
        { 
         return null; 
        } 
    
        logger.DebugFormat("Parsing bound date '{0}' as UK format.", date); 
    
        // Set the ModelState to the first attempted value before we have converted the date. This is to ensure that the ModelState has 
        // a value. When we have converted it, we will override it with a full universal date. 
        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, bindingContext.ValueProvider.GetValue(bindingContext.ModelName)); 
    
        try 
        { 
         var realDate = DateTime.Parse(date, System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB")); 
    
         // Now set the ModelState value to a full value so that it can always be parsed using InvarianCulture, which is the 
         // default for QueryStringValueProvider. 
         bindingContext.ModelState.SetModelValue(bindingContext.ModelName, new ValueProviderResult(date, realDate.ToString("yyyy-MM-dd hh:mm:ss"), System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB"))); 
    
         return realDate; 
        } 
        catch (Exception) 
        { 
         logger.ErrorFormat("Error parsing bound date '{0}' as UK format.", date); 
    
         bindingContext.ModelState.AddModelError(bindingContext.ModelName, String.Format("\"{0}\" is invalid.", bindingContext.ModelName)); 
         return null; 
        } 
    } 
    } 
    
+0

+1 Questo è stato anche utile – FRoZeN

+0

+1, sono passato al post e sono stati accettati i valori. – Malkin

+0

Il formato di data ISO 8601 "aaaa-mm-gg" ha funzionato per me. Stavo lottando con un modulo bilingue e questo è un buon compromesso. Grazie. – vbocan

18

è possibile modificare il legante modello predefinito da utilizzare la cultura dell'utente utilizzando IModelBinder

public class DateTimeBinder : IModelBinder 
    { 
     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
     { 
      var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
      var date = value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture); 

      return date;  
     } 
    } 

E in Global.asax scrittura:

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

saperne di più su this excellent blog che descrivono il motivo per cui MVC per squadra quadro implementato un def ault Cultura a tutti gli utenti.

+3

+1 post di blog molto bello che spiega esattamente il mio problema. – FRoZeN

+0

Genera un'eccezione se è una data non valida. – mikek3332002

+0

Ho provato questo e non ha funzionato per GET, ad es. da ActionLinks. –

0

che ha fatto il trucco per me

<system.web>  
     <globalization enableClientBasedCulture="true" uiCulture="Auto" culture="Auto" /> 
    </system.web> 
+0

Non sono sicuro che la memoria funzioni, ma funziona se usi post, ma non ottieni. segui il link in una delle risposte chiamate: MVC DateTime binding con formato data errato – FRoZeN

1

Perché non è sufficiente esaminare la cultura dei dati e convertirlo in quanto tale? Questo semplice approccio mi ha permesso di utilizzare date fortemente tipizzati nei modelli, link spettacolo d'azione e modificare i campi del locale desiderata e non hanno ad agitarsi affatto vincolante di nuovo in un DateTime fortemente tipizzato:

public class DateTimeBinder : IModelBinder 
{ 
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     return value.ConvertTo(typeof(DateTime), value.Culture); 
    } 
} 
Problemi correlati