CultureInfo issue with Modelbinding double in asp.net-mvc(2)
Asked Answered
D

2

6

In my Jquery script I post two doubles using the browser's CultureInfo (en-UK) that uses the .as a fraction separator. My MVC app is running on a server with locale nl-BE using the , as a fraction separator.

[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GetGridCell(double longitude, double latitude)
{
    var cell = new GridCellViewModel { X = (int)Math.Round(longitude, 0), Y = (int)Math.Round(latitude, 0) };
    return Json(cell);
}

The modelbinding fails because of the parsing issue.

I think it would be best to have my javascript set to en-UK and the same for the modelbinding in my MVC app. But I don't know how to do either.
Any suggestions?

Davon answered 2/7, 2010 at 19:39 Comment(0)
C
8

I'm not sure how far localisation goes with the default model binder (DefaultModelBinder), but you can easily create a binder yourself that can handle the culture specific parsing of the data, e.g, create a new class, let's call it the DoubleModelBinder, copypasta the following:

public class DoubleModelBinder : IModelBinder
{
    /// <summary>
    /// Binds the value to the model.
    /// </summary>
    /// <param name="controllerContext">The current controller context.</param>
    /// <param name="bindingContext">The binding context.</param>
    /// <returns>The new model.</returns>
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var culture = GetUserCulture(controllerContext);

        string value = bindingContext.ValueProvider
                           .GetValue(bindingContext.ModelName)
                           .ConvertTo(typeof(string)) as string;

        double result = 0;
        double.TryParse(value, NumberStyles.Any, culture, out result);

        return result;
    }

    /// <summary>
    /// Gets the culture used for formatting, based on the user's input language.
    /// </summary>
    /// <param name="context">The controller context.</param>
    /// <returns>An instance of <see cref="CultureInfo" />.</returns>
    public CultureInfo GetUserCulture(ControllerContext context)
    {
        var request = context.HttpContext.Request;
        if (request.UserLanguages == null || request.UserLanguages.Length == 0)
            return CultureInfo.CurrentUICulture;

        return new CultureInfo(request.UserLanguages[0]);
    }
}

Now, what we are doing here, is establishing our own language-aware double-parser. When we implement the IModelBinder interface, we need to create a BindModel method. This is where the meat of the it is done, but before we can parse anything, we need to get an IFormatProvider based on the browser's language. So, we use the GetUserCulture method to try and ready the browser's language. If we can't revert to the current culture.

When we have that, we are then in a position to parse the value. We first grab it from the ValueProvider (which is really a composite of many value providers, e.g. from the Form collection, Request collection, etc.), and then we parse it using the discovered IFormatProvider, which is the CultureInfo we now have.

Once you've done that, it's pretty trivial to add it to the model binder collection;

ModelBinder.Binders[typeof(Double)] = new DoubleModelBinder();

Try that and see if that helps.

Cioffi answered 4/7, 2010 at 18:38 Comment(3)
That worked perfectly. But now I'm wondering how this is done in the default one and why it is different in any way?Davon
Most likely because the DefaultModelBinder is a best-fit-for-all binder, I don't think it was really designed to do anything more complex then binding simple values to models.Cioffi
I wouldn't call doubles complex really, but I see your point. Anyway it seems resolved and I hope it won't come and bite me in the back later. Thx :)Davon
D
1

ModelBinding uses CurrentCulture to parse values. Thats understandable, because the user might enter a date or decimal into a textbox and the value would be parsed correctly.

But I still think most developers see it the way you see it: They want that all values are parsed by using the same culture no matter what language the user uses. They want to display values in the format of the user but enter values in a neutral format (InvariantCulture).

Thats why I set the CurrentCulture in Application.BeginRequest to CultureInfo.InvariantCulture. By that all Binding uses the invariant Culture. If you later want to use Ressources or format values in the language of the browser you have to switch back to the language of the user, by setting CurrentCulture to the language of the user again. I do this in an action filter.

EDIT:

The OP corrected me in that only Form submissions are culture aware: thats true. See the source for ValueProviderDictionary:PopulateDictionary where it is documented:

   We use this order of precedence to populate the dictionary:
   1. Request form submission (should be culture-aware)
   2. Values from the RouteData (could be from the typed-in URL or from the route's default values)
   3. URI query string
Domett answered 4/7, 2010 at 19:6 Comment(2)
Get requests are in InvariantCulture by default. It's only POST requests that are culture aware. But somehow it's not working with doubles.. I tried your idea, but for some reason I'm trying to figure out now, the BeginRequest is never triggered on ajax callsDavon
I edited my post. My Ajax calls go through BeginRequest. I have no idea why it is not called in your application. Caching? Call a different domain then you think you do?Domett

© 2022 - 2024 — McMap. All rights reserved.