14

Currently, I have a global filter called GlobalizationFilter that checks the route values, cookies and browser languages header to determine the correct culture settings for the request:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    // determine cultureInfo
    Thread.CurrentThread.CurrentCulture = cultureInfo;
    Thread.CurrentThread.CurrentUICulture = cultureInfo;
}

It all works, but the model binding process seems to occur before the global filters, and so the model binder doesn't take the culture settings into account.

This leads to problems with interpreting double values, DateTime values etc.

I could move the culture detection code to other locations, but I don't like any of my options:

  • Application's BeginRequest event. At this point of time the routing hasn't occurred, so I'll have to manually fish out the /en-US/ culture token from the URL. This in unacceptable.

  • Controller's Initialize() method. This will force me to write a base class for all my controllers, and inherit the existing controllers from it. I don't like this, but I'll opt for this solution if nothing better comes up.

Ideally, I want to find some way to inject my code between the "routing complete" and "model binding starts" events, but I found nothing in MSDN / Google on this.

Or maybe there's some other way to handle MVC3 globalization that I'm unaware of?

Thanks in advance for any contribution.

Zruty
  • 8,377
  • 1
  • 25
  • 31

1 Answers1

15

Extract out the code that determines the culture into a separate component/class. Then create a ModelBinder that derives from DefaultModelBinder that uses the class to set the culture before calling BindModel

public class CultureAwareModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        /* code that determines the culture */
        var cultureInfo = CultureHelper.GetCulture(controllerContext.HttpContext);

        //set current thread culture
        Thread.CurrentThread.CurrentCulture = cultureInfo;
        Thread.CurrentThread.CurrentUICulture = cultureInfo;

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

and then register it for the application (in Application_Start)

// register our own model binder as the default
ModelBinders.Binders.DefaultBinder = new CultureAwareModelBinder();
Russ Cam
  • 124,184
  • 33
  • 204
  • 266
  • since I'm going to get a custom model binder eventually anyway, it seems the best way. Thanks – Zruty Aug 26 '11 at 09:55
  • However, setting the request-wide culture in a model binder seems a bit counter-intuitive.. – Zruty Aug 26 '11 at 09:57
  • you could store the culture before hand, set the culture and bind the model and then restore the original culture. A custom model binder is simply one seam in the MVC architecture, you could implement it in other places if you're uncomfortable with the Single Responsibility Principle arguments. For example, you could define your own route handler that sets the culture. Depends on your driving tenets. If testability is important, don't put it somewhere that's going to be hard to test (like `Application_BeginRequest`) :) – Russ Cam Aug 26 '11 at 10:04
  • I'm OK with single responsibility, but a _model binder_ setting _culture_ seems strange to me. I guess I'll look up the route handler customization you mentioned – Zruty Aug 26 '11 at 10:19
  • +1, nothing better comes to my mind at this point, than implementing a culture-aware model binder – s.ermakovich May 14 '12 at 10:50