MVC, Datetime y la localización regional

Descubrí hace poco que existe un tema muy frecuente y poco esperable por parte de MVC y las localizaciones. Para los que manejamos las fechas con un formato diferente del de Estados Unidos, nos suele aparecer el error "The field [date] has format invalid" cuando ingresamos un día del mes superior a 12 y se desea .
Esto se debe a un problema del model binder que tiene por defecto MVC y se debe reemplazar por uno personalizado para que se pueda tener en cuenta el formato de la hora correspondiente.
Veamos un ejemplo que he probado y funciona. Incluso hace uso del atributo [DisplayFormat] para poder utilizar el formato de fecha que más nos convenga.

Model:

public class MyViewModel
{
    [DisplayName("date of birth")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime? Birth { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel
        {
            Birth = DateTime.Now
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

@model MyViewModel

@using (Html.BeginForm())
{
    @Html.LabelFor(x => x.Birth)
    @Html.EditorFor(x => x.Birth)
    @Html.ValidationMessageFor(x => x.Birth)
    <button type="submit">OK</button>
}

Model Binder personalizado:

Hasta aquí sólo hemos hecho lo de costumbre en MVC. Para poder resolver nuestro problema, deberemos escribir nuestro propio Model Binder y así reemplazar el que utiliza MVC por defecto.
public class MyDateTimeModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (!string.IsNullOrEmpty(displayFormat) && value != null)
        {
            DateTime date;
            displayFormat = displayFormat.Replace("{0:", string.Empty).Replace("}", string.Empty);
            // use the format specified in the DisplayFormat attribute to parse the date
            if (DateTime.TryParseExact(value.AttemptedValue, displayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
            {
                return date;
            }
            else
            {
                bindingContext.ModelState.AddModelError(
                    bindingContext.ModelName,
                    string.Format("{0} is an invalid date format", value.AttemptedValue)
                );
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }
}

Registración del Model Binder:

Para que MVC utilice nuestro Model Binder, debemos registrarlo. Para ello, agregaremos la siguiente línea en el método Application_Start.
ModelBinders.Binders.Add(typeof(DateTime?), new MyDateTimeModelBinder());
Ya no importa la cultura en la que se encuentre el servidor o el cliente, se utilizará el formato especificado en el atributo DisplayFormat para parsear la fecha.
La solución hasta aquí propuesta fue hallada en StackOverflow.

Bonus:

Hasta aquí, esta solución resuelve el problema para los navegadores: Firefox, Opera e Internet Explorer. Pero persiste para Chrome y Safari, ya que estos no usan la localización regional para instanciar el objeto Datetime de jQuery, lo que ocasiona el mismo error pero del lado del cliente.
Para solucionar este último escollo, también es necesario modificar el archivo jQuery.validate.js.
La función debe quedar así:
// http://docs.jquery.com/Plugins/Validation/Methods/date
date: function(value, element) {
   return this.optional(element) || !/Invalid|NaN/.test(new Date($.browser.webkit ? d[1] + "/" + d[0] + "/" + d[2] : value));
},
Y no se debe olvidar modificar el archivo minificado tabién (jQuery.validate.min.js):
date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date($.browser.webkit?d[1]+"/"+d[0]+"/"+d[2]:a))}

0 comentarios:

Publicar un comentario

Muchas gracias por leer el post y comentarlo.

 
Copyright 2009 Programación SOLIDa
BloggerTheme by BloggerThemes | Design by 9thsphere