43

For multilingual ASP.NET MVC 3 web application, I am determining the Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture on the controller factory as follows:

public class MyControllerFactory : DefaultControllerFactory {

    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) {

        //Get the {language} parameter in the RouteData
        string UILanguage;
        if (requestContext.RouteData.Values["language"] == null)
            UILanguage = "tr";
        else
            UILanguage = requestContext.RouteData.Values["language"].ToString();

        //Get the culture info of the language code
        CultureInfo culture = CultureInfo.CreateSpecificCulture(UILanguage);
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;

        return base.GetControllerInstance(requestContext, controllerType);
    }

}

The above code is nearly a year old now! So, I open for suggestions.

And I register this on the Global.asax file like:

ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());

This is working good but I am not sure if it is the best practice and best place to do this type of action.

I haven't dug into the main role of ControllerFactory and I am unable to compare it against ActionFilterAttribute.

What do you think about the best place to do this type of action?

tugberk
  • 57,477
  • 67
  • 243
  • 335
  • 3
    I have yet to find a good reason to change the CurrentCulture. Although it makes date and time formatting work automatically I think it's overreaching to change the culture just for that (e.g. Do you want the thread culture to be X when opening a SQL connection to a server that uses culture Y? I would rather leave the CurrentCulture alone and use the user culture explicitly when I need to format dates and numbers. (CurrentUICulture is OK in my opinion, though.) – Hector Correa Nov 22 '11 at 14:34
  • @HectorCorrea Thanks hector. actually I never thought about that and best part is that you are right. I am changing it only for resource files. do you think CurrentUICulture would be enough for that? – tugberk Nov 22 '11 at 14:40
  • 2
    Yes, the CurrentUICulture is enough for the resources. [A funny thing is that I was bitten by using the CurrentCulture earlier this year when switching the culture to Turkish because an issue with upper case "I" and lower case "i". Very similar to what's described in this link. I wonder what your take is on that since you seem to deal with Turkish language. http://www.west-wind.com/weblog/posts/2005/May/23/DataRows-String-Indexes-and-case-sensitivity-with-Turkish-Locale ] – Hector Correa Nov 22 '11 at 15:18
  • @HectorCorrea hmm, yes. Even if I only play with the UICulture, does it happen? – tugberk Nov 22 '11 at 15:41
  • 1
    No, it works OK with the UICulture. – Hector Correa Nov 22 '11 at 16:15
  • 2
    I know this is old but I have to disagree. CurrentCulture is made to be manipulated. It's the writes to the database et al that need to be set explicitly (e.g. with InvariantCulture). That's where relying on CurrentCulture can get you in trouble. – Rich Oct 18 '13 at 20:54

5 Answers5

57

I used a global ActionFilter for this, but recently I realized, that setting the current culture in the OnActionExecuting method is too late in some cases. For example, when model after POST request comes to the controller, ASP.NET MVC creates a metadata for model. It occurs before any actions get executed. As a result, DisplayName attribute values, and other Data Annotations stuff are handled using the default culture at this point.

Eventually I've moved setting the current culture to the custom IControllerActivator implementation, and it works like a charm. I suppose it's almost the same from the request lifecycle perspective to host this logic in the custom controller factory, like you have today. It's much more reliable, than usage of global ActionFilter.

CultureAwareControllerActivator.cs:

public class CultureAwareControllerActivator: IControllerActivator
{
    public IController Create(RequestContext requestContext, Type controllerType)
    {
        //Get the {language} parameter in the RouteData
        string language = requestContext.RouteData.Values["language"] == null ?
            "tr" : requestContext.RouteData.Values["language"].ToString();

        //Get the culture info of the language code
        CultureInfo culture = CultureInfo.GetCultureInfo(language);
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;

        return DependencyResolver.Current.GetService(controllerType) as IController;
    }
}

Global.asax.cs:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        ...
        ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CultureAwareControllerActivator()));
    }
}
s.ermakovich
  • 2,641
  • 2
  • 26
  • 24
  • @shanabus Unfortunately I don't have this code any more, but I've attempted to reconstruct it and updated the answer with code snippets. I'm not sure that registration of the `CultureAwareControllerActivator` is done right, but it works. – s.ermakovich Jan 16 '14 at 14:28
  • 1
    At this point, `RouteData` does not yet contain any action parameters. Still useful when the culture settings are based on a fixed part of the URL (hostname, or the first part of the url path) – marapet Mar 18 '14 at 15:55
  • Why wouldn't you want to do this behavior in the onRequestBegin event? Seems to me that that would hit all possible cases. – Agile Jedi Nov 20 '14 at 16:02
  • @AgileJedi `OnBeginRequest` doesn't give you any information regarding the request (route data, etc.). It's just a signal of new request creation. So, abilities for culture selection are very limited in this case. – s.ermakovich Nov 21 '14 at 08:44
  • 2
    ActionFilters are indeed *too late in some cases* (if not all) mainly because Model Binding (where your strings are going to be converted to numbers and dates and all) occur *before* Action Filters. – Jeff Jun 17 '16 at 12:51
7

I know an anser has already been selected. The option we used was to just Initialize the thread current culture in the OnBeginRequest event for the Application. This ensures the culture is discovered with every request

public void OnBeginRequest(object sender, EventArgs e)
{
   var culture = YourMethodForDiscoveringCulutreUsingCookie();
   System.Threading.Thread.CurrentThread.CurrentCulture = culture;
   System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
}
Agile Jedi
  • 2,922
  • 2
  • 18
  • 17
  • We have option to set the culture in BeginRequest method. We can also set the state and culture specific information in AcquireRequestState Method.Both the BeginRequest and AcquireRequestState methods are executed for every request.BeginRequest excute first followed by AcquireRequestState. – ssmsnet Nov 13 '17 at 10:24
4

An alternative place to put this would be to put that code in the OnActionExecuting method of a custom ActionFilter, which can be registered in the GlobalFilters collection:

http://weblogs.asp.net/gunnarpeipman/archive/2010/08/15/asp-net-mvc-3-global-action-filters.aspx

Paul Stovell
  • 32,377
  • 16
  • 80
  • 108
  • thanks! This was an option which I considered as well. I am mostly looking for a best practice here. What would be the pros / cons of doing this action with ActionFilterAttribute? – tugberk Nov 22 '11 at 12:22
  • 1
    +1, that's the way to do it. Here is a sample attribute that I've made: https://github.com/jgauffin/griffin.mvccontrib/blob/master/source/Griffin.MvcContrib/Localization/LocalizedAttribute.cs – jgauffin Nov 22 '11 at 16:01
  • @jgauffin putting a CookieName property is a good idea. You made it unchangeable there though. it is good to have this if the attribute is in a seperate project. – tugberk Nov 22 '11 at 16:24
  • @jgauffin I figured that there is a downside of localizing the app through an ActionFilter. If you cache your action and the cache is still valid, the ActionFilter is never executed. Best way of doing the localization is setting a new controller factory. – tugberk Nov 27 '11 at 10:46
  • @tugberk: No. You need to specify that the cache should varybyparam. (Use the `OutputCache` attribute + global.asax/`GetVaryByCustomString` – jgauffin Nov 27 '11 at 11:43
  • @jgauffin is it really bad to implement this by setting a new controller factory? – tugberk Nov 27 '11 at 11:56
3

Instead of overriding OnActionExecuting you can override Initialize here like this

protected override void Initialize(RequestContext requestContext)
{
        string culture = null;
        var request = requestContext.HttpContext.Request;
        string cultureName = null;

        // Attempt to read the culture cookie from Request
        HttpCookie cultureCookie = request.Cookies["_culture"];
        if (cultureCookie != null)
            cultureName = cultureCookie.Value;
        else
            cultureName = request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages

        // Validate culture name
        cultureName = CultureHelper.GetValidCulture(cultureName); // This is safe

        if (request.QueryString.AllKeys.Contains("culture"))
        {
            culture = request.QueryString["culture"];
        }
        else
        {
            culture = cultureName;
        }

        Uitlity.CurrentUICulture = culture;

        base.Initialize(requestContext);
    }
Hitendra
  • 329
  • 2
  • 17
  • Thanks for sharing. What does the Utility class look like for setting the CurrentUICulture? – shanabus Jan 14 '14 at 22:08
  • Utility class will have property as defined below `code` public static string CurrentUICulture { get { return Thread.CurrentThread.CurrentUICulture.Name; } set { Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(CultureHelper.GetValidCulture(value)); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(CultureHelper.GetValidCulture(value)); } } – Hitendra Jan 16 '14 at 10:02
0

If you don't use ControllerActivator, can use BaseController class and inhеrid from it.

public class BaseController : Controller
{
    public BaseController()
    {
          //Set CurrentCulture and CurrentUICulture of the thread
    }
}

public class HomeController: BaseController    
{
    [HttpGet]
    public ActionResult Index()
    {
        //..............................................
    }
}
Maximilian Ast
  • 3,369
  • 12
  • 36
  • 47
Delyan
  • 1
  • 1