Associação MVC DateTime com formato de data incorreto

132

O Asp.net-MVC agora permite a ligação implícita de objetos DateTime. Eu tenho uma ação ao longo das linhas de

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

Isso converte com êxito uma sequência de caracteres de uma chamada ajax em um DateTime. No entanto, usamos o formato de data dd / MM / aaaa; O MVC está convertendo para MM / dd / aaaa. Por exemplo, ao enviar uma chamada para a ação com a string '09 / 02/2009 'resulta em um DateTime de '02 / 09/2009 00:00:00' ou 2 de setembro em nossas configurações locais.

Eu não quero rolar meu próprio fichário de modelo por causa de um formato de data. Mas parece desnecessário alterar a ação para aceitar uma string e usar DateTime.Parse se o MVC for capaz de fazer isso por mim.

Existe alguma maneira de alterar o formato da data usado no fichário do modelo padrão para DateTime? O fichário de modelo padrão não deveria usar suas configurações de localização?

Sam Wessel
fonte
Ei .. Basta alterar o formato da data do sistema - DD / MM / aaaa para MM / DD / aaaa e pronto .. Também tenho o mesmo problema, resolvi-o alterando o formato da data do sistema.
quer
@banny se o aplicativo é global e pode ser que todos não tenham o mesmo formato de data e hora, como você pode fazer isso? , você não deve mudar cada data e hora Formato ..
Ravi Mehta
Nenhuma dessas respostas está me ajudando. O formulário precisa ser localizado. Alguns usuários podem ter a data de uma maneira, outros, de outra maneira. Configurando algo no web.config. ou global.asax não vai ajudar. Continuarei procurando uma resposta melhor, mas uma maneira seria lidar com a data como uma sequência até voltar ao c #.
astrosteve

Respostas:

164

Acabei de encontrar a resposta para isso com um pouco mais de pesquisa exaustiva:

Melvyn Harbour tem uma explicação completa do motivo pelo qual o MVC trabalha com datas do jeito que funciona e como você pode substituí-lo, se necessário:

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

Ao procurar o valor para analisar, a estrutura procura em uma ordem específica, a saber:

  1. RouteData (não mostrado acima)
  2. Cadeia de consulta do URI
  3. Formulário de solicitação

Somente o último deles estará ciente da cultura, no entanto. Há uma boa razão para isso, da perspectiva da localização. Imagine que eu escrevi um aplicativo da Web mostrando informações de voos de companhias aéreas que publico on-line. Pesquise voos em uma determinada data clicando em um link para esse dia (talvez algo como http://www.melsflighttimes.com/Flights/2008-11-21 ) e, em seguida, deseje enviar por e-mail esse link para meu colega em os EUA. A única maneira de garantirmos que estaremos olhando para a mesma página de dados é se o InvariantCulture for usado. Por outro lado, se estou usando um formulário para reservar meu voo, tudo está acontecendo em um ciclo apertado. Os dados podem respeitar o CurrentCulture quando ele é gravado no formulário e, portanto, precisam respeitá-lo ao retornar do formulário.

Sam Wessel
fonte
Vai fazer. Essa funcionalidade é desativada por 48 horas após a postagem da pergunta.
21720 Sam Wessel
43
Discordo totalmente que tecnicamente isso está correto. O fichário do modelo SEMPRE deve se comportar da mesma forma com POST e GET. Se a cultura invariável é o caminho a seguir para o GET, faça-o também para o POST. Alterar o comportamento dependendo do verbo http não faz sentido.
22713 Bart Bartixix
eu tenho uma pergunta, nosso site está hospedado em outro país, ele precisa de MM/dd/yyyyformato; caso contrário, mostra erro de validação. The field BeginDate must be a date.Como posso fazer com que o sever aceite o dd/MM/yyyyformato?
shaijut
O parâmetro URL deve ser inequívoco, como usar a formatação padrão ISO. Então as configurações de cultura não importam.
nforss
isso fornece um link para explicar a causa, mas na verdade não fornece nenhuma solução mais fácil para esse problema (como a publicação de uma seqüência de data e hora ISO do script), dessa maneira sempre funciona e não precisamos nos preocupar em definir uma cultura específica no servidor ou verifique se o formato de data e hora é idêntico entre servidor e cliente.
Hopeless
36

Eu definiria globalmente suas culturas. ModelBinder pegue isso!

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

Ou você apenas altera isso nesta página.
Mas globalmente no web.config acho melhor

Peter Gfader
fonte
27
Não para mim. A data ainda é nula se eu passar em 23/10/2010.
GONeale
Eu acho que é a solução mais fácil. Para mim, mudar o formato em todos os Date.ToString (). Eu acho que ele vai funcionar com ligação também, mas não verifiquei, desculpe :-(
msa.im 12/12/12
1
Tenho o dataformat de meus servidores de desenvolvimento definido como dd / MM / aaaa. O modelbinder usou o formato MM / dd / aaaa. Definir o formato no web.config como dd / MM / aaaa agora força o modelbinder a usar o formato europeu. Na minha opinião, ele deve usar as configurações de data do servidor. Enfim, você resolveu meu problema.
22412 Karel
Isso funcionou perfeitamente para mim ... de alguma forma me senti embora estranho desde que eu sei que o meu servidor de aplicação é no Reino Unido executando um sistema operacional UK ...: /
Tallmaris
Funcionou perfeitamente no MVC4 correndo em sites Azure
Matty Urso
31

Estou tendo o mesmo problema com a ligação de formato de data curta às propriedades do modelo DateTime. Depois de analisar muitos exemplos diferentes (não apenas sobre o DateTime), montei o seguinte:

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 manter a maneira como as rotas etc. são registradas no arquivo Global ASAX, também adicionei uma nova classe sintática à pasta App_Start do meu projeto MVC4 chamado 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());
        }
    }
}

Depois, chamo o RegisterCustomModelBinders estático do meu Global ASASX Application_Start assim:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Uma observação importante aqui é que, se você escrever um valor DateTime em um campo oculto como este:

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

Eu fiz isso e o valor real na página estava no formato "MM / dd / aaaa hh: mm: ss tt" em vez de "dd / MM / aaaa hh: mm: ss tt" como eu queria. Isso fez com que a validação do meu modelo falhasse ou retornasse a data errada (obviamente trocando os valores de dia e mês).

Depois de muito esforço e tentativas fracassadas, a solução foi definir as informações de cultura para cada solicitação, fazendo isso no 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;
}

Não funcionará se você o colar no Application_Start ou mesmo no Session_Start, pois isso o atribui ao thread atual da sessão. Como você bem sabe, os aplicativos da Web são sem estado, portanto, o encadeamento que atendeu sua solicitação anteriormente não é o mesmo encadeamento que atende à sua solicitação atual, portanto, suas informações de cultura foram para o grande GC no céu digital.

Agradecimentos: 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
fonte
13

Vai ser um pouco diferente no MVC 3.

Suponha que tenhamos um controlador e uma visão com o método Get

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

Devemos adicionar o 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
}

e o comando em Application_Start () do Global.asax

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
Dmitry
fonte
Este é um ponto de partida decente, mas não é implementado corretamente IModelBinder, principalmente em relação à validação. Além disso, ele só funciona se o nome do DateTimefor dateTime .
Sam
2
Além disso, descobri que isso DateTime?só funciona se você adicionar outra chamada a ModelBinders.Binders.Addwith typeof(DateTime?).
Sam
8

Também é importante notar que, mesmo sem criar seu próprio fichário de modelo, vários formatos diferentes podem ser analisados.

Por exemplo, nos EUA, todas as seguintes strings são equivalentes e são automaticamente vinculadas ao mesmo valor DateTime:

/ company / press / may% 2001% 202008

/ company / press / 01-05-2008

/ company / press / 05-01-2008

Eu sugiro usar o aaaa-mm-dd, porque é muito mais portátil. Você realmente não quer lidar com o manuseio de vários formatos localizados. Se alguém reservar um voo em 1º de maio, em vez de 5 de janeiro, você terá grandes problemas!

NB: Não estou claro exatamente se aaaa-mm-dd é universalmente analisado em todas as culturas, então talvez alguém que conheça possa adicionar um comentário.

Simon_Weaver
fonte
3
Como ninguém diz que aaaa-MM-dd não é universal, acho que sim.
deerchao
na minha opinião, isso é melhor do que usar um fichário de modelo. .datepicker ("opção", "dateFormat", "aa-mm-dd") ou apenas defina-o nos padrões.
22413 Bart Bartixix
6

Tente usar toISOString (). Retorna string no 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
fonte
5

Defino a configuração abaixo no meu MVC4 e funciona como um encanto

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
fonte
3
Isso funcionou para mim também. Observe que o elemento globalization está em Configuration> System.Web. msdn.microsoft.com/pt-br/library/hy4kkhe0%28v=vs.85%29.aspx
Jowen 14/01/15
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
fonte
Isso não funciona. dd-MM-aaaa ainda não é reconhecido
jao 22/10
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
fonte
1
Você deve enriquecer sua resposta adicionando algumas explicações.
Alexandre Lavoie
A partir da memória, isso não é consistente com a maneira como os binders de modelo internos funcionam, portanto, pode não ter o mesmo comportamento secundário, como reter o valor digitado para validação.
Sam
1

Defino CurrentCulturee CurrentUICulturemeu 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
fonte