password - Carpeta de modelo personalizada de DateTime en Asp.net MVC
forms asp net core (4)
Me gustaría escribir mi propia carpeta de modelo para el tipo DateTime
. En primer lugar, me gustaría escribir un nuevo atributo que pueda adjuntar a la propiedad de mi modelo, como:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
Esta es la parte facil. Pero la parte de encuadernación es un poco más difícil. Me gustaría agregar una nueva carpeta de modelo para el tipo DateTime
. Yo puedo cualquiera
- implementar la interfaz
IModelBinder
y escribir mi propio métodoBindModel()
- heredar de
DefaultModelBinder
y anular el métodoBindModel()
Mi modelo tiene una propiedad como se ve arriba ( Birth
). Por lo tanto, cuando el modelo intenta vincular datos de solicitud a esta propiedad, se BindModel(controllerContext, bindingContext)
el BindModel(controllerContext, bindingContext)
mi modelo de BindModel(controllerContext, bindingContext)
. Todo bien, pero. ¿Cómo obtengo los atributos de propiedad de controller / bindingContext para analizar mi fecha correctamente ? ¿Cómo puedo llegar al PropertyDesciptor
de la propiedad Birth
?
Editar
Debido a la separación de las preocupaciones, mi clase de modelo se define en un ensamblaje que no hace referencia (y no debería) al ensamblado System.Web.MVC. Establecer atributos de enlace personalizado (similar al ejemplo de Scott Hanselman ) es un no-ir aquí.
No creo que deba poner atributos específicos de locale en un modelo.
Otras dos posibles soluciones a este problema son:
- Haga que sus páginas transliteren las fechas del formato específico de la configuración regional a un formato genérico como aaaa-mm-dd en JavaScript. (Funciona, pero requiere JavaScript.)
- Escriba una carpeta modelo que tenga en cuenta la cultura actual de la interfaz de usuario al analizar las fechas.
Para responder a su pregunta real, la forma de obtener atributos personalizados (para MVC 2) es escribir un AssociatedMetadataProvider .
Podría implementar una Carpeta de fecha y hora personalizada como tal, pero debe tener cuidado con la cultura y el valor asumidos a partir de la solicitud real del cliente. Puede obtener una fecha como mm / dd / aaaa en en-US y quiere que se convierta en la cultura del sistema en-GB (que sería como dd / mm / aaaa) o una cultura invariable, como lo hacemos nosotros, entonces usted tiene que analizarlo antes y usar la fachada estática Convertir para cambiarlo en su comportamiento.
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 todos modos, la cultura dependend El análisis de DateTime en una aplicación sin estado puede ser cruel ... Especialmente cuando trabajas con JSON en javascript hacia el cliente y hacia atrás.
Tuve este gran problema yo mismo y después de horas de prueba y error obtuve una solución de trabajo como la que me pediste.
En primer lugar, dado que tener una carpeta en una propiedad no es posible, debe implementar una ModelBinder completa. Dado que no desea enlazar todas las propiedades individuales, sino solo la que le importa, puede heredar de DefaultModelBinder y luego vincular la propiedad individual:
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);
}
}
}
En mi ejemplo, estoy analizando la fecha con una cultura fiexed, pero lo que quieres hacer es posible. Debe crear un CustomAttribute (como DateTimeFormatAttribute) y ponerlo sobre su propiedad:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
Ahora, en el método BindProperty, en lugar de buscar una propiedad DateTime, puede buscar una propiedad con DateTimeFormatAttribute, tomar el formato que especificó en el constructor y luego analizar la fecha con DateTime.ParseExact
Espero que esto ayude, me tomó mucho tiempo llegar con esta solución. En realidad, era fácil tener esta solución una vez que sabía cómo buscarla :(
puede cambiar la carpeta de modelo predeterminada para usar la cultura de usuario utilizando 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);
}
}
Y en Global.Asax agregue lo siguiente a Application_Start ():
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());
Lea más en este excelente blog que describe por qué el equipo de marco de Mvc implementó una Cultura predeterminada para todos los usuarios.