asp.net-mvc-3 - tutorial - razor mvc
Problema de cultura de fecha y hora de ASP.NET MVC al pasar el valor al controlador (6)
¿Cómo puedo decirle a mi controlador / modelo qué tipo de cultura debería esperar para analizar una fecha y hora?
Estaba usando parte de esta publicación para implementar jquery datepicker en mi aplicación mvc.
Cuando presento la fecha en la que se "pierde en la traducción", no estoy usando el formato de EE. UU. Para la fecha, por lo que cuando se envía a mi controlador simplemente se convierte en nulo.
Tengo un formulario donde el usuario elige una fecha:
@using (Html.BeginForm("List", "Meter", FormMethod.Get))
{
@Html.LabelFor(m => m.StartDate, "From:")
<div>@Html.EditorFor(m => m.StartDate)</div>
@Html.LabelFor(m => m.EndDate, "To:")
<div>@Html.EditorFor(m => m.EndDate)</div>
}
He creado una plantilla de edición para esto, para implementar el jquery datepicker:
@model DateTime
@Html.TextBox("", Model.ToString("dd-MM-yyyy"), new { @class = "date" })
Luego creo los widgets datepicker como este.
$(document).ready(function () {
$(''.date'').datepicker({ dateFormat: "dd-mm-yy" });
});
Todo esto funciona bien
Aquí es donde comienzan los problemas, este es mi controlador:
[HttpGet]
public ActionResult List(DateTime? startDate = null, DateTime? endDate = null)
{
//This is where startDate and endDate becomes null if the dates dont have the expected formatting.
}
¿Por eso me gustaría de alguna manera decirle a mi controlador qué cultura debería esperar? ¿Mi modelo está equivocado? ¿puedo decir de alguna manera qué cultura usar, como con los atributos de la anotación de datos?
public class MeterViewModel {
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
}
Editar: este enlace explica mi problema y también una muy buena solución. Gracias a gdoron
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);
var date = value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
return date;
}
}
Y en Global.Asax escribe:
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeBinder());
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.
Al enviar una fecha, siempre debe intentar enviarla en el formato "aaaa-MM-dd". Esto permitirá que se convierta en cultura independiente.
Normalmente tengo un campo oculto que mantiene la fecha en este formato. Esto es relativamente simple usando datepicker de jQuery UI.
eso hizo el truco para mí
<system.web>
<globalization enableClientBasedCulture="true" uiCulture="Auto" culture="Auto" />
</system.web>
¿Por qué no simplemente inspeccionar la cultura de los datos y convertirlos como tales? Este enfoque simple me permitió usar fechas fuertemente tipadas en modelos, mostrar enlaces de acción y editar campos en la configuración regional deseada y no tener que preocuparme por volver a vincularlo a un DateTime fuertemente tipado:
public class DateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return value.ConvertTo(typeof(DateTime), value.Culture);
}
}
Puede crear una extensión Binder para manejar la fecha en el formato cultural.
Esta es una muestra que escribí para manejar el mismo problema con el tipo Decimal, espero que entiendas la idea
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
Actualizar
Para usarlo, simplemente declare la carpeta en Global.asax como esta
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
//HERE you tell the framework how to handle decimal values
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
DependencyResolver.SetResolver(new ETAutofacDependencyResolver());
}
Luego, cuando el encuadernador tenga que trabajar, sabrá automáticamente qué hacer. Por ejemplo, esta es una acción con un modelo que contiene algunas propiedades de tipo decimal. Simplemente no hago nada
[HttpPost]
public ActionResult Edit(int id, MyViewModel viewModel)
{
if (ModelState.IsValid)
{
try
{
var model = new MyDomainModelEntity();
model.DecimalValue = viewModel.DecimalValue;
repository.Save(model);
return RedirectToAction("Index");
}
catch (RulesException ex)
{
ex.CopyTo(ModelState);
}
catch
{
ModelState.AddModelError("", "My generic error message");
}
}
return View(model);
}
Este problema surge porque estás usando el método GET en tu Formulario. El Proveedor de valor de QueryString en MVC siempre usa el formato de fecha Invariant / US. Ver: enlace MVC DateTime con formato de fecha incorrecta
Hay tres soluciones:
- Cambie su método a POST.
- Como dice otra persona, cambie el formato de fecha a ISO 8601 "aaaa-mm-dd" antes del envío.
Use una carpeta personalizada para tratar siempre las fechas de Cadena de consulta como GB. Si haces esto, debes asegurarte de que todas las fechas estén en esa forma:
public class UKDateTimeModelBinder : IModelBinder { private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); /// <summary> /// Fixes date parsing issue when using GET method. Modified from the answer given here: /// https://.com/questions/528545/mvc-datetime-binding-with-incorrect-date-format /// </summary> /// <param name="controllerContext">The controller context.</param> /// <param name="bindingContext">The binding context.</param> /// <returns> /// The converted bound value or null if the raw value is null or empty or cannot be parsed. /// </returns> public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var vpr = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (vpr == null) { return null; } var date = vpr.AttemptedValue; if (String.IsNullOrEmpty(date)) { return null; } logger.DebugFormat("Parsing bound date ''{0}'' as UK format.", date); // Set the ModelState to the first attempted value before we have converted the date. This is to ensure that the ModelState has // a value. When we have converted it, we will override it with a full universal date. bindingContext.ModelState.SetModelValue(bindingContext.ModelName, bindingContext.ValueProvider.GetValue(bindingContext.ModelName)); try { var realDate = DateTime.Parse(date, System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB")); // Now set the ModelState value to a full value so that it can always be parsed using InvarianCulture, which is the // default for QueryStringValueProvider. bindingContext.ModelState.SetModelValue(bindingContext.ModelName, new ValueProviderResult(date, realDate.ToString("yyyy-MM-dd hh:mm:ss"), System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB"))); return realDate; } catch (Exception) { logger.ErrorFormat("Error parsing bound date ''{0}'' as UK format.", date); bindingContext.ModelState.AddModelError(bindingContext.ModelName, String.Format("/"{0}/" is invalid.", bindingContext.ModelName)); return null; } } }