Liaison MVC DateTime avec un format de date incorrect

132

Asp.net-MVC permet désormais la liaison implicite d'objets DateTime. J'ai une action du genre

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 convertit en MM / jj / aaaa. Par exemple, la soumission d'un appel à l'action avec une chaîne «09/02/2009» entraîne un DateHeure de «02/09/2009 00:00:00», ou le 2 septembre dans nos paramètres locaux.

Je ne veux pas rouler mon propre classeur de modèle pour un format de date. Mais il semble inutile de changer l'action pour accepter une chaîne, puis d'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èle par défaut pour DateTime? Le classeur de modèles par défaut ne devrait-il pas utiliser vos paramètres de localisation de toute façon?

Sam Wessel
la source
Hé .. Changez simplement le format de date de votre système - JJ / MM / aaaa en MM / JJ / aaaa et faites-le .. J'ai aussi le même problème, je l'ai résolu en changeant le format de date du système.
banny
@banny si l'application est globale et peut-être que tout le monde n'a pas le même format de date et d'heure, comment pouvez-vous faire cela? , vous ne supposez pas aller changer tous les formats de date et d'heure ..
Ravi Mehta
Aucune de ces réponses ne m'aide. Le formulaire doit être localisé. Certains utilisateurs peuvent avoir la date dans un sens, d'autres dans l'autre. Configurer quelque chose dans le web.config. ou global.asax ne va pas vous aider. Je vais continuer à chercher une meilleure réponse, mais une façon serait simplement de traiter la date sous forme de chaîne jusqu'à ce que je revienne à c #.
astrosteve

Réponses:

164

Je viens de trouver la réponse à cela avec un googling plus exhaustif:

Melvyn Harbour explique en détail pourquoi MVC fonctionne avec les dates comme il le fait et comment vous pouvez remplacer cela si nécessaire:

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

Lorsque vous recherchez la valeur à analyser, le framework regarde dans un ordre spécifique, à savoir:

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

Cependant, seul le dernier d'entre eux sera conscient 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 affichant des informations sur les vols des compagnies aériennes que je publie en ligne. Je recherche des vols à une certaine date en cliquant sur un lien pour ce jour (peut-être quelque chose comme http://www.melsflighttimes.com/Flights/2008-11-21 ), puis je veux envoyer ce lien par e-mail à mon collègue dans les Etats Unis. La seule façon de garantir que nous regarderons 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 la respecter lorsqu'elles reviennent du formulaire.

Sam Wessel
la source
Ça ira. Cette fonctionnalité est désactivée pendant 48 heures après la publication de la question.
Sam Wessel
43
Je ne suis pas du tout d'accord que techniquement c'est correct. Le classeur de modèles doit TOUJOURS se comporter de la même manière avec POST et GET. Si la culture invariante est la voie à suivre pour GET, faites-la également pour POST. Changer le comportement en fonction du verbe http n'a pas de sens.
Bart Calixto
J'ai une question, notre site Web est hébergé dans un autre pays, il a besoin d'un MM/dd/yyyyformat sinon il affiche une erreur de validation The field BeginDate must be a date., comment puis-je faire en sorte que le serveur accepte le dd/MM/yyyyformat?
shaijut
Le paramètre URL doit être sans ambiguïté, comme l'utilisation d'un formatage standard ISO. Ensuite, les paramètres de culture n'auraient pas d'importance.
nforss
cela donne un lien expliquant la cause mais ne fournit en fait aucune solution plus simple à ce problème (par exemple en publiant une chaîne de date / heure ISO à partir du script), cela fonctionne toujours et nous n'avons pas à nous soucier de définir une culture spécifique sur le serveur ou assurez-vous que le format datetime est identique entre le serveur et le client.
Désespéré
36

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

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

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

Peter Gfader
la source
27
Pas pour moi. La date est toujours considérée comme nulle si je passe le 23/10/2010.
GONeale
Je pense que c'est la solution la plus simple. Pour moi, cela change de format dans tous les Date.ToString (). Je pense que cela fonctionnera aussi avec la liaison, mais je n'ai pas vérifié, désolé :-(
msa.im
1
Le format de date de mes serveurs de développement est défini sur jj / MM / aaaa Le classeur de modèles utilisait le format MM / jj / aaaa. La définition du format dans web.config sur jj / MM / aaaa force désormais le classeur de modèles à utiliser le format européen. À mon avis, il devrait cependant utiliser les paramètres de date du serveur. Quoi qu'il en soit, vous avez résolu mon problème.
Karel
Cela a parfaitement fonctionné pour moi ... en quelque sorte, cela me semblait étrange puisque je sais que mon serveur d'applications est au Royaume-Uni et exécute un système d'exploitation britannique ...: /
Tallmaris
Fonctionne parfaitement dans MVC4 fonctionnant sur les sites Web Azure
Matty Bear
31

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

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 conserver la façon dont les routes, etc. sont enregistrées dans le fichier Global ASAX, j'ai également ajouté une nouvelle classe sytatique 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 ensuite simplement les RegisterCustomModelBinders statiques de mon Global ASASX Application_Start comme ceci:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Une note importante ici est que si vous écrivez une valeur DateTime dans un champ caché comme celui-ci:

@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 voulais. Cela a fait échouer la validation de mon modèle ou renvoyer la mauvaise date (en échangeant évidemment les valeurs du jour et du mois).

Après beaucoup de grattage et de tentatives infructueuses, la solution consistait à définir les informations de culture pour chaque demande en procédant ainsi dans le 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 de discussion qui a traité votre demande précédemment n'est pas le même fil de service que votre demande actuelle, par conséquent, vos informations de culture sont allées 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

WernerVA
la source
13

Ce sera 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());
Dmitry
la source
C'est un bon point de départ, mais il n'est pas correctement implémenté IModelBinder, en particulier en ce qui concerne la validation. En outre, cela ne fonctionne que si le nom de DateTimeest dateTime .
Sam
2
De plus, j'ai trouvé que cela DateTime?ne fonctionne que si vous ajoutez un autre appel à ModelBinders.Binders.Addavec typeof(DateTime?).
Sam
8

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

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

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

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

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

Je suggère fortement d'utiliser aaaa-mm-jj car il est beaucoup plus portable. Vous ne voulez vraiment pas gérer la gestion de plusieurs formats localisés. Si quelqu'un réserve un vol le 1er mai au lieu du 5 janvier, vous allez avoir 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 sait peut ajouter un commentaire.

Simon_Weaver
la source
3
Puisque personne ne dit que aaaa-MM-jj n'est pas universel, je suppose que oui.
deerchao
c'est à mon avis mieux que d'utiliser un modèle de reliure. .datepicker ("option", "dateFormat", "aa-mm-jj") ou définissez-le simplement dans les valeurs par défaut.
Bart Calixto
6

Essayez d'utiliser toISOString (). Il renvoie 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());
}
rnofenko
la source
5

J'ai défini la configuration ci-dessous sur mon MVC4 et cela fonctionne comme un charme

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
la source
3
Cela a fonctionné pour moi aussi. Notez que l'élément de globalisation va sous Configuration> System.Web. 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);
                    }
                }

            }
        }
    }
}
tobias
la source
Cela ne marche pas. jj-MM-aaaa n'est toujours pas reconnu
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;
}
Teth
la source
1
Vous devriez enrichir votre réponse en ajoutant quelques explications.
Alexandre Lavoie
De mémoire, cela n'est pas cohérent avec le fonctionnement des classeurs de modèle intégrés, il se peut donc qu'il ne présente pas le même comportement secondaire, tel que la conservation de la valeur typée pour la validation.
Sam
1

J'ai défini CurrentCultureet CurrentUICulturemon 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");
    }
Korayem
la source