Enlace MVC DateTime con formato de fecha incorrecto

132

Asp.net-MVC ahora permite la vinculación implícita de objetos DateTime. Tengo una acción en la línea de

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

Esto convierte con éxito una cadena de una llamada ajax en DateTime. Sin embargo, usamos el formato de fecha dd / MM / aaaa; MVC se está convirtiendo a MM / dd / aaaa. Por ejemplo, enviar una llamada a la acción con una cadena '09 / 02/2009 'da como resultado una fecha y hora de '02 / 09/2009 00:00:00', o el 2 de septiembre en nuestra configuración local.

No quiero rodar mi propio modelo de carpeta por el bien de un formato de fecha. Pero parece innecesario tener que cambiar la acción para aceptar una cadena y luego usar DateTime.Parse si MVC es capaz de hacer esto por mí.

¿Hay alguna forma de alterar el formato de fecha utilizado en la carpeta del modelo predeterminado para DateTime? ¿No debería el archivador de modelo predeterminado usar la configuración de localización de todos modos?

Sam Wessel
fuente
Oye ... Solo cambia el formato de fecha del sistema: DD / MM / aaaa a MM / DD / aaaa y listo. También tengo el mismo problema, lo resolví cambiando el formato de fecha del sistema.
banny
@banny si la aplicación es global y podría no tener el mismo formato de fecha y hora, ¿cómo podría hacer esto? , no debes ir y cambiar el formato de fecha y hora de cada uno ..
Ravi Mehta
Ninguna de estas respuestas me está ayudando. El formulario debe ser localizado. Algunos usuarios pueden tener la fecha de una manera, otros de otra manera. Configurar algo en web.config. o global.asax no va a ayudar. Seguiré buscando una mejor respuesta, pero una forma sería tratar la fecha como una cadena hasta que vuelva a c #.
astrosteve

Respuestas:

164

Acabo de encontrar la respuesta a esto con un google más exhaustivo:

Melvyn Harbour tiene una explicación detallada de por qué MVC trabaja con las fechas de la manera en que lo hace, y cómo puede anular esto si es necesario:

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

Al buscar el valor para analizar, el marco se ve en un orden específico, a saber:

  1. RouteData (no se muestra arriba)
  2. Cadena de consulta URI
  3. Formulario de solicitud

Sin embargo, solo el último de ellos será consciente de la cultura. Hay una muy buena razón para esto, desde una perspectiva de localización. Imagine que he escrito una aplicación web que muestra la información de vuelos de una aerolínea que publico en línea. Busco vuelos en una fecha determinada haciendo clic en un enlace para ese día (quizás algo así como http://www.melsflighttimes.com/Flights/2008-11-21 ), y luego quiero enviar ese enlace por correo electrónico a mi colega en los Estados Unidos. La única forma en que podríamos garantizar que ambos estaremos viendo la misma página de datos es si se usa InvariantCulture. Por el contrario, si estoy usando un formulario para reservar mi vuelo, todo está sucediendo en un ciclo apretado. Los datos pueden respetar CurrentCulture cuando se escribe en el formulario, por lo que deben respetarlo al regresar del formulario.

Sam Wessel
fuente
Lo haré Esa funcionalidad está desactivada durante 48 horas después de publicar la pregunta.
Sam Wessel
43
Estoy totalmente en desacuerdo con que técnicamente esto es correcto. El archivador modelo SIEMPRE debe comportarse igual con POST y GET. Si la cultura invariante es el camino a seguir para GET, hágalo también para POST. Cambiar el comportamiento dependiendo del verbo http no tiene sentido.
Bart Calixto
Tengo una pregunta, nuestro sitio web alojado en otro país, necesita MM/dd/yyyyformato, de lo contrario, muestra un error de validación The field BeginDate must be a date., ¿cómo puedo hacer que el servidor acepte el dd/MM/yyyyformato?
shaijut
El parámetro de URL no debe ser ambiguo, como usar el formato estándar ISO. Entonces la configuración cultural no importaría.
nforss
esto proporciona un enlace que explica la causa, pero en realidad no proporciona una solución más fácil para este problema (como publicar una cadena de fecha y hora ISO del script), de esa manera siempre funciona y no tenemos que preocuparnos por establecer una cultura específica en el servidor o asegúrese de que el formato de fecha y hora sea idéntico entre el servidor y el cliente.
Desesperado
36

Globalmente establecería sus culturas. ¡ModelBinder lo recoge!

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

O simplemente cambia esto para esta página.
Pero globalmente en web.config creo que es mejor

Peter Gfader
fuente
27
No para mi. La fecha sigue siendo nula si paso el 23/10/2010.
GONeale
Creo que es la solución más fácil. Para mí, cambia el formato en todo Date.ToString (). Creo que también funcionará con el enlace, pero no lo
comprobé
1
Tengo el formato de fecha de mis servidores de desarrollo establecido en dd / MM / aaaa. El modelbinder utilizó el formato MM / dd / aaaa. Establecer el formato en web.config en dd / MM / aaaa ahora obliga al modelbinder a usar el formato europeo. En mi opinión, debería usar la configuración de fecha del servidor. De todos modos resolviste mi problema.
Karel
Eso funcionó perfectamente para mí ... de alguna manera se sintió extraño ya que sé que mi servidor de aplicaciones está en el Reino Unido ejecutando un sistema operativo del Reino Unido ...: /
Tallmaris
Funcionó perfectamente en MVC4 que se ejecuta en sitios web de Azure
Matty Bear
31

He estado teniendo el mismo problema con el formato de fecha corta vinculante a las propiedades del modelo DateTime. Después de mirar muchos ejemplos diferentes (no solo con respecto a DateTime), reuní lo siguiente:

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

Para mantener la forma en que las rutas, etc., se registran en el archivo ASAX global, también agregué una nueva clase sintética a la carpeta App_Start de mi proyecto MVC4 llamada 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());
        }
    }
}

Entonces simplemente llamo al static RegisterCustomModelBinders estático desde mi Global ASASX Application_Start de esta manera:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Una nota importante aquí es que si escribe un valor DateTime en un campo oculto como este:

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

Lo hice y el valor real en la página estaba en el formato "MM / dd / aaaa hh: mm: ss tt" en lugar de "dd / MM / aaaa hh: mm: ss tt" como quería. Esto hizo que la validación de mi modelo fallara o devolviera la fecha incorrecta (obviamente intercambiando los valores de día y mes).

Después de un montón de rascarse la cabeza e intentos fallidos, la solución fue establecer la información cultural para cada solicitud haciendo esto en 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;
}

No funcionará si lo pega en Application_Start o incluso Session_Start, ya que eso lo asigna al hilo actual de la sesión. Como bien sabe, las aplicaciones web no tienen estado, por lo que el hilo que atendió su solicitud anteriormente no es el mismo hilo que atiende su solicitud actual, por lo tanto, su información cultural se ha ido al gran GC en el cielo digital.

Gracias 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

WernerVA
fuente
13

Va a ser un poco diferente en MVC 3.

Supongamos que tenemos un controlador y una vista con el método Get

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

Deberíamos agregar 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
}

y el comando en Application_Start () de Global.asax

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
Dmitry
fuente
Este es un punto de partida decente, pero no se implementa correctamente IModelBinder, particularmente con respecto a la validación. Además, solo funciona si el nombre de DateTimees dateTime .
Sam
2
Además, descubrí que eso DateTime?solo funciona si agrega otra llamada a ModelBinders.Binders.Addwith typeof(DateTime?).
Sam
8

También vale la pena señalar que, incluso sin crear su propio modelo de carpeta, se pueden analizar múltiples formatos diferentes.

Por ejemplo, en los Estados Unidos, todas las siguientes cadenas son equivalentes y se vinculan automáticamente al mismo valor de DateTime:

/ empresa / prensa / mayo% 2001% 202008

/ empresa / prensa / 2008-05-01

/ empresa / prensa / 05-01-2008

Recomiendo encarecidamente usar aaaa-mm-dd porque es mucho más portátil. Realmente no desea lidiar con el manejo de múltiples formatos localizados. Si alguien reserva un vuelo el 1 de mayo en lugar del 5 de enero, ¡tendrá grandes problemas!

NB: No estoy muy claro si aaaa-mm-dd se analiza universalmente en todas las culturas, por lo que tal vez alguien que sepa pueda agregar un comentario.

Simon_Weaver
fuente
3
Como nadie dice que aaaa-MM-dd no es universal, supongo que sí.
ciervo
En mi opinión, esto es mejor que usar un modelo de carpeta. .datepicker ("opción", "dateFormat", "aa-mm-dd") o simplemente configúrelo en los valores predeterminados.
Bart Calixto
6

Intenta usar toISOString (). Devuelve una cadena en formato ISO8601.

Método 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étodo 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
fuente
5

Configuré la siguiente configuración en mi MVC4 y funciona de maravilla

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
fuente
3
Esto funcionó para mí también. Tenga en cuenta que el elemento de globalización se encuentra en Configuración> Sistema.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
fuente
Esto no funciona dd-MM-aaaa todavía no se reconoce
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
fuente
1
Debería enriquecer su respuesta agregando algunas explicaciones.
Alexandre Lavoie
Desde la memoria, esto no es coherente con la forma en que funcionan las carpetas del modelo incorporado, por lo que podría no tener el mismo comportamiento secundario, como retener el valor escrito para la validación.
Sam
1

Configuré CurrentCulturey CurrentUICulturemi controlador base personalizado

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

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