web-dev-qa-db-fra.com

Problème de culture datetime ASP.NET MVC lors de la transmission de la valeur au contrôleur

Comment puis-je dire à mon contrôleur/modèle à quel type de culture il doit s’attendre pour l’analyse d’une date/heure?

J'utilisais une partie de ce post pour implémenter jquery datepicker dans mon application mvc.

Lorsque je soumets la date "perdue dans la traduction", je n'utilise pas le format US pour la date. Par conséquent, lorsqu'il est envoyé à mon contrôleur, il devient tout simplement nul.

J'ai un formulaire où l'utilisateur choisit une date:

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

J'ai créé un modèle de modification pour cela, pour implémenter le datequicker de jquery:

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

Je crée ensuite les widgets Datepicker comme ceci.

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

Tout cela fonctionne bien.

Voici où les problèmes commencent, voici mon contrôleur:

[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.
}

C’est la raison pour laquelle je voudrais dire en quelque sorte à mon contrôleur quelle culture il devrait s’attendre? Est-ce que mon modèle est faux? puis-je en quelque sorte lui dire quelle culture utiliser, comme avec les attributs d'annotation de données?

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

Edit: Ce lien explique mon problème et une très bonne solution pour le résoudre également. Merci à gdoron

18
Jim Wolff

Vous pouvez créer une extension de classeur pour gérer la date au format de culture.

Ceci est un exemple que j'ai écrit pour traiter le même problème avec le type décimal, j'espère que vous aurez l'idée

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

Mettre à jour

Pour l'utiliser, déclarez simplement le classeur dans Global.asax comme ceci

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

Ensuite, lorsque le modelbinder devra effectuer certains travaux, il saura automatiquement quoi faire. Par exemple, il s’agit d’une action avec un modèle contenant des propriétés de type décimal. Je ne fais simplement rien

[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);
}
12
Iridio

vous pouvez modifier le classeur de modèle par défaut pour utiliser la culture utilisateur à l'aide de 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;    
       }
   }

Et dans le Global.Asax, écrivez:

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

Plus d’informations sur cet excellent blog qui décrivent pourquoi l’équipe de framework Mvc a implémenté une culture par défaut pour tous les utilisateurs.

20
gdoron

Ce problème se pose parce que vous utilisez la méthode GET sur votre formulaire. Le fournisseur de valeurs QueryString dans MVC utilise toujours le format de date Invariant/US. Voir: Liaison MVC DateTime avec un format de date incorrect

Il y a trois solutions:

  1. Changez votre méthode en POST.
  2. Comme quelqu'un d'autre le dit, changez le format de la date en ISO 8601 "aaaa-mm-jj" avant la soumission.
  3. Utilisez un classeur personnalisé pour toujours traiter les dates de chaîne de requête en Go. Si vous faites cela, vous devez vous assurer que toutes les dates sont sous cette forme:

    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;
        }
    }
    }
    
10
Rob Kent

Lorsque vous soumettez une date, vous devriez toujours essayer de la soumettre au format "aaaa-MM-jj". Cela lui permettra de devenir indépendant de la culture.

J'ai normalement un champ caché qui maintient la date dans ce format. C'est relativement simple avec le sélecteur de date de jQuery UI.

3
Digbyswift

Pourquoi ne pas simplement inspecter la culture des données et les convertir en tant que telles? Cette approche simple m'a permis d'utiliser des dates fortement typées dans les modèles, d'afficher des liens d'action et de modifier des champs dans les paramètres régionaux souhaités, sans avoir à me soucier du tout de le relier dans un DateTime fortement typé:

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);
    }
}
1
Jeff Dunlop

cela a fait le tour pour moi

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