Date not binding with model for specific culture

1.6k views Asked by At

I am trying to bind date in a model for two cultures (en-US and fr-FR) when the culture is selected en-US. User selects the Item language for creating an entry. For example : Add news in en-US | add news in fr-FR

Model:

[Column(TypeName = "datetime2")]
public DateTime CreatedOn { get; set; }

View:

@Html.TextBox("CreatedOn", Model.CreatedOn, new { @class = "form-control", @id = "datepicker" , @type="text"})

Script for datepicker and parsing:

$(document).ready(function () {
        $('#datetimepicker2').datetimepicker({
            locale: '@Model.CultureInfoCode',
            format: 'L'
        }).data("DateTimePicker").date();
        //$('#datetimepicker2').moment().format("MM/dd/YYYY");
    });

$(document).ready(function () {
    $.validator.methods.date = function (value, element) {
        moment.locale('@Model.CultureInfoCode'); // CultureInfoCode is the culture of item being entered. 
        return moment(value);
        //parseDate(value, null, "@Model.CultureInfoCode");
           // return this.optional(element) || Globalize.parseDate(value) !== null;
        }
});

Parsing date seems to work fine also. Model binds all values including date if the culture selected for whole website(en-US ) matches the culture of Item being entered (i.e Add news in English (en-US)). But when I add news in French(fr-FR) and enter date as 26/01/0001, it binds all fields except date and gives value in model as 1/1/0001 and returns in same view displaying error message:

**The value '26/01/0001' is not valid for 'CreatedOn' .**

model.Validate is also false in controller.

Please help. Thanks

3

There are 3 answers

0
Alexander Polyankin On BEST ANSWER

Default model binder in asp.net mvc is aware of date localization issues. To make it parse date in specified culture, you need to set CurrentCulture before action method is called. I am aware of few possible ways to do this.

Globalization element

Globalization element can automatically set CurrentCulture to user's OS default. Web.config:

<globalization culture="auto" uiCulture="auto" />

So if user comes from en-US localized OS, date will be parsed in en-US format, from fr-FR localized OS - in fr-FR format, and so on.

Action filters

If you need to set CurrentCulture manually, you can use action filters to do this. More information can be found in this answer. You can use this approach if culture code is specified in url.

Define route:

routes.MapRoute(
    "DefaultLocalized",
    "{culture}/{controller}/{action}/{id}",
    new { culture = "en-US", controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Define action filter:

public class InternationalizationAttribute : ActionFilterAttribute 
{
    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    {
        string culture = (string)filterContext.RouteData.Values["culture"] ?? "en-US";

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
    }
}

Use it per action/controller as attribute or add this to global filters collection in Application_Start():

GlobalFilters.Filters.Add(new InternationalizationAttribute());

Model binders

Another way is to re implement model binder for DateTime type. More information here. For example, you have culture field in form. Simplest model binder for this:

public class DateTimeBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var provider = bindingContext.ValueProvider;
        var cultureValue = provider.GetValue("culture");
        var dateValue = provider.GetValue(bindingContext.ModelName);

        var cultureInfo = CultureInfo.CurrentCulture;
        if (cultureValue != null)
            cultureInfo = CultureInfo.GetCultureInfo(cultureValue.AttemptedValue);

        return dateValue.ConvertTo(typeof(DateTime), cultureInfo));
    }
}

Add binder to collection in Application_Start():

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
0
kpr On

Thanks ranquild, It solved the problem. Though I picked the culture from Request object like below. Because I needed the culture of Item being entered by user not set by globalization.

   public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                string cultureName = controllerContext.HttpContext.Request.Form["CultureInfoCode"];
                var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
                System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo(cultureName);
                return value.ConvertTo(typeof(DateTime), cultureinfo);
            }
0
spadelives On

In our case, we were setting a default date on the view in JQuery, which was causing the binding fail for some reason. We removed the JQuery that set the date and set the default date in the viewmodel from the controller instead to resolve the problem.