web-dev-qa-db-fra.com

Liaison DateTime MVC avec format de date incorrect

Asp.net-MVC autorise désormais la liaison implicite des objets DateTime. J'ai une action dans le sens de

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

Cela convertit avec succès une chaîne d'un appel ajax en un DateTime. Cependant, nous utilisons le format de date jj/mm/aaaa; MVC est en train de convertir en MM/jj/aaaa. Par exemple, l'envoi d'un appel à l'action avec la chaîne '09/02/2009 'entraîne un DateTime de '02/09/2009 00:00:00' ou le 2 septembre dans nos paramètres locaux.

Je ne veux pas lancer mon propre classeur de modèles pour un format de date. Mais il semble inutile de devoir modifier l'action pour accepter une chaîne, puis utiliser DateTime.Parse si MVC est capable de le faire pour moi.

Existe-t-il un moyen de modifier le format de date utilisé dans le classeur de modèles par défaut pour DateTime? Le classeur de modèles par défaut ne doit-il pas quand même utiliser vos paramètres de localisation?

127
Sam Wessel

Je viens de trouver la réponse à cela avec des recherches plus exhaustives sur Google:

Melvyn Harbour explique en détail pourquoi MVC utilise les dates de cette manière et comment vous pouvez le remplacer si nécessaire:

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

Lors de la recherche de la valeur à analyser, le cadre cherche dans un ordre spécifique, à savoir:

  1. RouteData (non montré ci-dessus)
  2. Chaîne de requête URI
  3. Formulaire de demande

Cependant, seuls les derniers seront conscients de la culture. Il y a une très bonne raison à cela, du point de vue de la localisation. Imaginez que j'ai écrit une application Web contenant des informations sur les vols des compagnies aériennes que je publie en ligne. Je cherche des vols à une certaine date en cliquant sur un lien correspondant à la journée (peut-être quelque chose comme http://www.melsflighttimes.com/Flights/2008-11-21 ), puis je veux envoyez ce lien par courrier électronique à mon collègue américain. La seule façon de garantir que nous examinerons tous les deux la même page de données est d'utiliser InvariantCulture. En revanche, si j'utilise un formulaire pour réserver mon vol, tout se passe dans un cycle serré. Les données peuvent respecter la CurrentCulture lorsqu’elles sont écrites dans le formulaire et doivent donc être respectées lors du retour du formulaire.

163
Sam Wessel

Je définirais globalement vos cultures. ModelBinder ramasser ça!

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

Ou vous changez simplement ceci pour cette page.
Mais globalement, dans web.config, je pense que c'est mieux

35
Peter Gfader

J'ai eu le même problème avec la liaison de format de date courte aux propriétés de modèle DateTime. Après avoir examiné de nombreux exemples différents (pas seulement concernant DateTime), j’ai rassemblé ce qui suit:

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

Pour rester avec la façon dont les routes, etc., sont enregistrées dans le fichier Global ASAX, j'ai également ajouté une nouvelle classe sytatic au dossier App_Start de mon projet MVC4 nommé 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());
        }
    }
}

J'appelle alors juste les RegisterCustomModelBinders statiques de mon ASASX Global Application_Start comme suit:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Une remarque importante ici est que si vous écrivez une valeur DateTime dans un champ caché comme ceci:

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

Je l'ai fait et la valeur réelle sur la page était au format "MM/jj/aaaa hh: mm: ss tt" au lieu de "jj/MM/aaaa hh: mm: ss tt" comme je le souhaitais. Cela a entraîné l'échec de la validation de mon modèle ou son renvoi à une date incorrecte (en échangeant évidemment les valeurs de jour et de mois).

Après de nombreuses tentatives infructueuses et des tentatives infructueuses, la solution consistait à définir les informations de culture pour chaque demande en procédant ainsi dans 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;
}

Cela ne fonctionnera pas si vous le collez dans Application_Start ou même Session_Start, car cela l'assigne au thread actuel de la session. Comme vous le savez bien, les applications Web sont sans état, de sorte que le fil qui traitait précédemment votre demande est le même que celui qui traite votre demande actuelle, de sorte que vos informations sur la culture ont été transmises au grand GC dans le ciel numérique.

Merci à: 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

29
WernerVA

Cela va être légèrement différent dans MVC 3.

Supposons que nous ayons un contrôleur et une vue avec la méthode Get

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

Nous devrions ajouter 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
}

et la commande dans Application_Start () de Global.asax

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

Il convient également de noter que même sans créer votre propre classeur de modèles, plusieurs formats différents peuvent être analysés.

Par exemple, aux États-Unis, toutes les chaînes suivantes sont équivalentes et sont automatiquement liées à la valeur identique DateTime:

/ société/presse/mai% 2001% 202008

/ société/presse/2008-05-01

/ société/presse/05-01-2008

Je suggérerais fortement d'utiliser aaaa-mm-jj car c'est beaucoup plus portable. Vous ne voulez vraiment pas vous occuper de la gestion de plusieurs formats localisés. Si quelqu'un réserve un vol le 1er mai au lieu du 5 janvier, vous aurez de gros problèmes!

NB: Je ne sais pas exactement si aaaa-mm-jj est universellement analysé dans toutes les cultures, alors peut-être que quelqu'un qui le sait peut ajouter un commentaire.

8
Simon_Weaver

Je règle la configuration ci-dessous sur mon MVC4 et cela fonctionne comme un charme

<globalization uiCulture="auto" culture="auto" />
5
JeeShen Lee

Essayez d'utiliser toISOString (). Il retourne une chaîne au format ISO8601.

Méthode 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);
}

Méthode POST

javascript

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

c #

[HttpPost]
public JsonResult Do(DateTime date)
{
     return Json(date.ToString());
}
5
rnofenko
  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);
                    }
                }

            }
        }
    }
}
1
tobias
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
Teth

J'ai défini CurrentCulture et CurrentUICulture mon contrôleur de base personnalisé

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

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }
0
Korayem