ASP.NET MVC Action Filter for Localized Sites

Disclaimer: This is my first attempt at creating an ASP.NET MVC Action Filter and was developed using the ASP.NET MVC Release Candidate Refresh 1 release.  I doubt things change much on this topic before RTM.

Your mission: to make your site available to multiple cultures and languages by way of ASP.NET MVC localization, globalization, internationalization, or whatever term you prefer to use.

You’re familiar with cultures right?  It's the combination of language and country, usually identified in software using ISO culture codes.  The ISO culture code for English in the United States is "en-US", "en-CA" for English in Canada, "fr-CA" for French in Canada, and so on.

If you've got or want a site which supports multiple cultures using ASP.NET MVC localization you need to be familiar with setting CultureInfo values for the current thread and UI.  The .NET Framework uses this info to do a few key things.  First, if you're using resource files the framework knows which to pick up because the resource file name specifies the culture.  Secondly the culture is used as a formatter argument for various methods such as ToString().  The culture needs to be set for each page request as it's based on the current thread.  For more info on this end of things check out Localization in ASP.NET 2.0 - I found it quite helpful.

Once you know which culture the user's experience should use you'll want to persist that somehow.  Maybe it's in a cookie, the session, embedded in the URL, etc.  In ASP.NET web forms you might include this detection in a base page or the global.asax for each page load. 

ASP.NET MVC provides another nifty trick for handling things on each action call - Action Filters.  I thought it'd be interesting to figure out some ways of doing internationalization in ASP.NET MVC so I came up with an ASP.NET MVC action filter to include on a controller class for setting the culture for each action call.  Currently it supports keeping the culture in a cookie, the querystring, session state, and the URL path.

Using the filter is simple.  Assuming the SetCultureAttribute class is included in your project you decorate a controller class with it.  You must set the CultureStore  (type of CultureLocation) to indicate where the culture code comes from, and then one or two other properties to provide more hints at getting to the culture code.  Here's an example for storing the culture code in the query string.  In this example the URL would be expected to include a param called "culture" such as in this URL: "http://localhost/?culture=en-GB".

[HandleError]
[SetCulture(CultureStore = SetCultureAttribute.CultureLocation.QueryString, QueryStringParamName = "culture")]
public class HomeController : Controller
{ ... actions ... }

If stored in session you'd modify the SetCulture properties to something similar to:

[SetCulture(CultureStore = SetCultureAttribute.CultureLocation.Session, SessionParamName = "culture")]

If stored in a cookie you'd modify the SetCulture properties to something similar to:

[SetCulture(CultureStore = SetCultureAttribute.CultureLocation.Cookie, CookieName = "culture")]

If stored in the URL path your actions will need to expect language and country parameters and your route needs to account for the same.  For my implementation I modified my default route and assumed the "en-US" culture if nothing else was specified.  This code expects a URL formatted like "http://localhost/en/us/home/about".  There will be some kinks to work out with this one:

routes.MapRoute(
    "Default",                                             
// Route name
    "{language}/{country}/{controller}/{action}/{id}",                           // URL with parameters

    new { controller = "Home", action = "Index", id = "", language = "en", country = "US" }  // Parameter defaults
);

Using this updated route you'd modify the SetCulture properties to:

[SetCulture(CultureStore = SetCultureAttribute.CultureLocation.URL, CountryActionParamName = "country", LanguageActionParamName = "language")]

... etc.

Assuming the culture code is found then the thread and UI culture will be automatically set in this fashion:

CultureInfo culture = new CultureInfo (cultureCode);
System.Threading.Thread.CurrentThread.CurrentCulture = culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = culture;

Click here to get the full SetCultureAttribute class.  Don't forget to change the namespace to match your own.

Have you any experience with localized sites in ASP.NET MVC?  How did you approach this topic?

kick it on DotNetKicks.com
Bookmark and Share

Related posts

Comments

February 24. 2009 11:02 PM

accord 2010

Nice coding man. I'll try later.

accord 2010

February 25. 2009 03:25 AM

trendbender

good artcle, thx

trendbender

February 26. 2009 04:41 AM

Celik

Well, pretty nice localization can be done using this. Thanks for the article

Celik

March 7. 2009 06:42 PM

pingback

Pingback from weblogs.asp.net

Interesting Finds: 2009-03-08 - Bolik

weblogs.asp.net

April 13. 2009 08:34 PM

Andre

Hi.
I was googling about MVC and localization and found very few posts online.
I tried to implement you code (with storing localization in URL) and id did not work.
What I did:
1) Created a blank MVC website
2) Created Folder "Common" inside the "Controllers" folder.
3) Copied your SetCultureAttribute class into "Controllers/Common" folder.
4) Changed the namespace of the SetCultureAttribute class to MyApp.Controllers.Common
5) Decorated the HomeController and Account controllers with - [SetCulture(CultureStore = SetCultureAttribute.CultureLocation.URL, CountryActionParamName = "country", LanguageActionParamName = "language")]
6) Refferenced the namespace in each controller "using MyApp.Controllers.Common"
7) Changed the global.asax.cs to have this code (instead of default one)
routes.MapRoute(
"Default", // Route name
"{language}/{country}/{controller}/{action}/{id}", // URL with params
new { controller = "Home", action = "Index", id = "", language = "en", country = "CA" } // Parameter defaults
);

Rebuilt the application without errors/warnings and got the following error at runtime:
The given key was not present in the dictionary.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.

Source Error:


Line 147: if (CultureStore == CultureLocation.URL)
Line 148: {
Line 149: if (filterContext.ActionParameters[LanguageActionParamName] != null && filterContext.ActionParameters[CountryActionParamName] != null
Line 150: && filterContext.ActionParameters[LanguageActionParamName].ToString() != string.Empty && filterContext.ActionParameters[CountryActionParamName].ToString() != string.Empty
Line 151: )


Source File: C:\Inetpub\wwwroot\IP\IP\Controllers\Common\SetCultureAttribute.cs Line: 149


Sorry for being a noob - a am new to MVC. Do you know what it might be? I understand that some parameter is missing... I tried to run the application and change the URL for different countries languages such as http://localhost:1366/en/ca/home/about
and http://localhost:1366/en/us/home/about. Nothing seems to work.

Andre

April 13. 2009 09:18 PM

Andre

Its me again, I just tried to use query string variable to pass the calture information and it is working...
So, I suppose the error that I have in my above post has to do iether with global.asax.cs routes.MapRoute() function or the way I decorate the Controllers.

Can't figure it out - followed your example, but no luck...

Andre

April 14. 2009 12:03 PM

Andre

Hey, its me again!
I figured it out!

In global.asax.cs I had another .MapRoute(...) method that was called before the method that you defined in your exampled which caused the 2 parameters (country and language) to be dropped and never passed to ActionExecutingContext param of the public void OnActionExecuting().

All good now! Nice post!
Thank you very much!

Best regards,
Andriy.

Andre

April 17. 2009 01:39 AM

Rajan R.G

Hi,
Very nice article. But I got the same error "The given key was not present in the dictionary" when i use CultureLocation.URL. Looks like ActionParameters in filterContext is 0. I am using ASP.NET MVC 1.0 RTM. This is new blank application and i have only one route defined. Any idea for this error?
Rajan R.G

Rajan R.G

April 25. 2009 07:08 PM

BinaryDigits

Hello,
The code does not work if URL is selected as a Culture Location.
Exception: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.

The variables for the language and country never come through and this line fails:
if(filterContext.ActionParameters[LanguageActionParamName] != null &&...

Can you post a zip file with a project source which configured to use URL? I can't figure it out but for some reason the parameters are never passed...

BinaryDigits

April 25. 2009 08:24 PM

BinaryDigits

Hello everyone,
One very important thing to mention for the Localization based on URL:
MAKE SURE that your controller's action acepts 2 parameters for language and country.
You don't have to do anything with them, but they must be in your action method signature. For some reason it will not work otherwise. So, this will work:
public class HomeController : Controller
{
public ActionResult Index(string language, string country)
{
return View();
}
}

Best of luck to you all!

BinaryDigits

May 15. 2009 02:43 AM

graphics comments

thanks for this usefull informations..
now i find what i want to know, thanks ..

graphics comments

May 17. 2009 07:26 AM

Ryan

I'm a noob with mvc... I need to retrieve language and country for both static content (i.e., from resx files) and from model methods (i.e., GetArticles(language, country)... Can you please provide a simple way to understand how to achieve this?

Thanks a lot...

Ryan

February 15. 2010 07:44 AM

pingback

Pingback from twitter.com

Twitter / Suksan Interaxsa: #asp.net #mvc action filte ...

twitter.com

May 20. 2010 01:19 PM

pingback

Pingback from 163.tgrconversions.com

Micra Replacement Nissan 370z Geneva Motor Show, Test Nissan Micra 2008

163.tgrconversions.com

Comments are closed