Pasta de modelo DateTime personalizada no Asp.net MVC

Eu gostaria de escrever meu próprio fichário de modelo para o tipo DateTime . Primeiro de tudo, gostaria de escrever um novo atributo que eu possa append à minha propriedade de modelo como:

 [DateTimeFormat("dMyyyy")] public DateTime Birth { get; set,} 

Esta é a parte fácil. Mas a parte do fichário é um pouco mais difícil. Eu gostaria de adicionar um novo modelo de fichário para o tipo DateTime . Eu também posso

  • implementar interface IModelBinder e escrever meu próprio método BindModel()
  • herda de DefaultModelBinder e sobrescreve o método BindModel()

Meu modelo tem uma propriedade como visto acima ( Birth ). Portanto, quando o modelo tenta associar dados de solicitação a essa propriedade, o BindModel(controllerContext, bindingContext) do binder do meu modelo é chamado. Tudo bem, mas. Como faço para obter atributos de propriedade de controller / bindingContext, para analisar minha data corretamente ? Como posso chegar ao PropertyDesciptor da propriedade Birth ?

Editar

Devido à separação de interesses, minha class de modelo é definida em um assembly que não faz (e não deveria) fazer referência ao assembly System.Web.MVC. Definir atributos de binding personalizada (semelhante ao exemplo de Scott Hanselman ) é um não-ir aqui.

   

Eu não acho que você deve colocar atributos específicos do local em um modelo.

Duas outras soluções possíveis para esse problema são:

  • Faça com que as suas páginas transliterem as datas do formato específico da localidade para um formato genérico como aaaa-mm-dd em JavaScript. (Funciona, mas requer JavaScript.)
  • Escreva um fichário de modelo que considere a cultura da interface do usuário atual ao analisar as datas.

Para responder a sua pergunta real, a maneira de obter atributos personalizados (para MVC 2) é escrever um AssociatedMetadataProvider .

você pode alterar o fichário de modelo padrão para usar a cultura de usuário usando IModelBinder

 public class DateTimeBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value); return value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture); } } public class NullableDateTimeBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value); return value == null ? null : value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture); } } 

E no Global.Asax, adicione o seguinte ao Application_Start ():

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

Leia mais neste excelente blog que descreve por que a equipe do framework Mvc implementou um Culture padrão para todos os usuários.

Eu tive esse problema muito grande e depois de horas de tentativas e falhas, consegui uma solução de trabalho como você pediu.

Primeiro de tudo, desde ter um fichário em apenas uma propriedade não é possibile yuo tem que implementar um ModelBinder completo. Como você não deseja que a binding seja toda a propriedade única, mas somente aquela da qual você cuida, você pode herdar de DefaultModelBinder e vincular a única propriedade:

 public class DateFiexedCultureModelBinder : DefaultModelBinder { protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) { if (propertyDescriptor.PropertyType == typeof(DateTime?)) { try { var model = bindingContext.Model; PropertyInfo property = model.GetType().GetProperty(propertyDescriptor.Name); var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name); if (value != null) { System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("it-CH"); var date = DateTime.Parse(value.AttemptedValue, cultureinfo); property.SetValue(model, date, null); } } catch { //If something wrong, validation should take care } } else { base.BindProperty(controllerContext, bindingContext, propertyDescriptor); } } } 

No meu exemplo, estou analisando a data com uma cultura fiexed, mas o que você quer fazer é possível. Você deve criar um CustomAttribute (como DateTimeFormatAttribute) e colocá-lo sobre sua propriedade:

 [DateTimeFormat("dMyyyy")] public DateTime Birth { get; set,} 

Agora, no método BindProperty, em vez de procurar uma propriedade DateTime, você pode procurar uma propriedade com DateTimeFormatAttribute, pegar o formato especificado no construtor e, em seguida, analisar a data com DateTime.ParseExact

Espero que isso ajude, levei muito tempo para vir com essa solução. Na verdade, foi fácil ter essa solução uma vez que eu sabia como pesquisá-la 🙁

Você pode implementar um fichário de DateTime personalizado como esse, mas é preciso tomar cuidado com a cultura e o valor assumidos da solicitação real do cliente. Você pode obter uma data como mm / dd / aaaa em en-US e deseja converter na cultura de sistemas en-GB (o que seria como dd / mm / aaaa) ou uma cultura invariável, como fazemos, então você tem que analisá-lo antes e usando a fachada estática Converter para alterá-lo em seu comportamento.

  public class DateTimeModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var valueResult = bindingContext.ValueProvider .GetValue(bindingContext.ModelName); var modelState = new ModelState {Value = valueResult}; var resDateTime = new DateTime(); if (valueResult == null) return null; if ((bindingContext.ModelType == typeof(DateTime)|| bindingContext.ModelType == typeof(DateTime?))) { if (bindingContext.ModelName != "Version") { try { resDateTime = Convert.ToDateTime( DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture, DateTimeStyles.AdjustToUniversal).ToUniversalTime(), CultureInfo.InvariantCulture); } catch (Exception e) { modelState.Errors.Add(EnterpriseLibraryHelper.HandleDataLayerException(e)); } } else { resDateTime = Convert.ToDateTime( DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture), CultureInfo.InvariantCulture); } } bindingContext.ModelState.Add(bindingContext.ModelName, modelState); return resDateTime; } } 

De qualquer forma, cultura dependente DateTime análise em um aplicativo sem estado pode por uma crueldade … Especialmente quando você trabalha com JSON em javascript clientside e para trás.