Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the culture of all ValueProviderResult used in model binding to a given value?

It turns out that MVC's DefaultModelBinder uses different cultures in order to parse values (e.g. double, DateTime, etc.) for POST vs GET requests. Here is more info.

I see that this is controlled by the Culture property of ValueProviderResult objects, which are returned from IValueProvider.GetValue().

My question is: How can I globally make sure that this value is always CultureInfo.InvariantCulture.

I know I can implement custom value providers and do it this way.

I know I can implement custom model binders and do it this way.

I know I can set the culture in the thread, but unfortunately this is not an option in my case.

What I am looking for is a way to set it so that even the default model binder and the existing value providers are able to parse in culture invariant way, irrespective of what the thread culture is set to.

like image 598
Petar Ivanov Avatar asked Jul 17 '13 19:07

Petar Ivanov


1 Answers

As far as I know, there is no way that will match your criteria. You will have to do one of the things you know you can do (I'd say the most proper way would be a custom value provider).

Reason: All the default ValueProviders are hardcoded to use either CultureInfo.InvariantCulture or CultureInfo.CurrentCulture.

Here, specifically, is the way FormValueProvider does it:

internal FormValueProvider(
            ControllerContext controllerContext,
            IUnvalidatedRequestValues unvalidatedValues
         )
         : base(
              controllerContext.HttpContext.Request.Form, 
              unvalidatedValues.Form,
              CultureInfo.CurrentCulture // <--- Grrr, argh
         ) 
{
}

The culture isn't retrieved from anywhere else (i.e., the argument above isn't used as a default, but as the one culture to use).

Cultures of the different IValueProviders

For reference, these are the cultures for each of the default IValueProviders:

  • ChildActionValueProvider: InvariantCulture
  • FormValueProvider: CurrentCulture
  • JsonValueProvider: CurrentCulture
  • RouteDataValueProvider: InvariantCulture
  • QueryStringValueProvider: InvariantCulture
  • HttpFileCollectionValueProvider: InvariantCulture

Replacing the CurrentCulture IValueProviders

It isn't a huge task to replace FormValueProvider, since, as seen above, it just calls its base class' (NameValueCollectionValueProvider) constructor - which takes the desired culture as an argument.

The original implementation of FormValueProvider appears on the surface to be harder than it actually is, with references to internal classes and interfaces. But they're not needed in order to replace the provider - they're only there for unit testing.

You only need to call the base constructor (as mentioned above), passing two NameValueCollections that are easy to acquire: Request.Forms and the Forms property of Validation.Unvalidated(Request) (a static method). And set the third argument to the culture you want.

The FormValueProviderFactory is even more straightforward.

JsonValueProvider is a bit more involved - basically you'd have to copy the source of JsonValueProviderFactory to a new class and modify it - because although it allows overriding GetValueProvider(), that method mainly consists of calls to other private static methods.

EDIT (Petar Ivanov): This worked for me. In order to make it work it was not enough to add the custom factory to ValueProviderFactories.Factories, because this way it's is added after the FormValueProviderFactory. Instead, I had to replace the FormValueProviderFactory with the custom one.

like image 143
JimmiTh Avatar answered Nov 03 '22 04:11

JimmiTh