Sunday, July 5, 2009

Localization with MasterPages - The Final Cut

I'll try to be short and to the point. You may skip all the following text description and go ahead to the code example.

Intro:
Assuming you want your ASP.NET website/web application to have a multi-lingual interface. You'll find multiple resources on the internet on how to use LocalResources files or GlobalResources files to store specific language's data.
Nice Video Tour for Localization.
A specific language resource would be loaded according to the user's browser language preferences.
But most probably you'll need to give the user the option to switch between languages on the fly without going to the browser's preferences....makes sense.

Questions:

1- Where to store user language preference?
Some suggest using a Session variable to store the language preference across the pages. But shouldn't you be storing this for future uses?
Some suggest using a Cookie, but this will be browser specific and/or a machine specific.
IMHO, it would be perfect if the site can remember a user preference regardless of the browser or machine he uses to sign in. That's why storing it in the database might sound like the best option, and I use ASP.NET Profile for that.

Btw, Profile usage is not as easy to use in a Web-Application project as it is in a WebSite project, as no custom class gets created to hold your profile properties defined in the web.config. Please check the code below to know how to manage Profile properties in a Web-Application.

2- How to switch it on the fly?
Simply by setting 2 properties: CurrentThread.CurrentUICulture & CurrentThread.CurrentCulture

3- But where to set them?
Basic answer is to override the InitializeCulture() method on each and every page in your application....not the smartest thing to do.
Another suggestion is to let all your pages inherit from a BasePage class which in turn inherits from the Page class, and there you override the InitializeCulture() method.
The problem is that InitializeCulture() method is never called on cached pages. Ref: InitializeCulture and caching don't mix

I'm using a MasterPage, can I do this in the code behind of the MasterPage.... The answer is a striking NO. The MasterPage doesn't inherit from the Page class, and it has no idea what InitializeCulture() is.

If we want a global location to set the culture, then why not using the Global.asax class to set the culture in one of the events declared there.
That's why many people who don't like to use the BasePage approach sugests using the
Application_BeginRequest() as a good location for this code.
Good point, only that the Profile won't be read at this point, yet... so what's now.
The answer is: Application_PreRequestHandlerExecute(). It's not there by default, you should add it yourself.

Code example (might have a room for refactoring):
Here I use a couple of buttons on the master page for switching the language on the fly, you may use whatever approach you want to use.

public partial class Main : System.Web.UI.MasterPage
{
protected void English_Click(object sender, EventArgs e)
{
SwitchCulture("en-Us");
}
protected void Arabic_Click(object sender, EventArgs e)
{
SwitchCulture("ar-Eg");
}
private void SwitchCulture(string culture)
{
CultureHelper.SaveCulture(culture);
Response.Redirect(Request.Url.AbsolutePath);
}
}

As said before thie event is not there by default when you add the Global class to your application.

public class Global : System.Web.HttpApplication
{
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
CultureHelper.SetCulture();
}
}

I created a class to encapsulate the culture manipulation.

public class CultureHelper
{
public static void SaveCulture(string culture)
{
HttpContext.Current.Profile.SetPropertyValue("Culture", culture);
}
public static void SetCulture()
{
var culture = GetCulture();
if(string.IsNullOrEmpty(culture))
return;
var cultureInfo = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = cultureInfo;
Thread.CurrentThread.CurrentCulture = cultureInfo;
}
private static string GetCulture()
{
var culture = string.Empty;
if (HttpContext.Current.Profile != null)
{
culture = (string) HttpContext.Current.Profile.GetPropertyValue("Culture");
}
return culture;
}

And don't forget the Profile property definition in the web.config

[system.web]
[anonymousIdentification enabled="true"/]
[profile]
[properties]
[add name="Culture" allowAnonymous="true" defaultValue="Auto" type="string"/]
[/properties]
[/profile]
...
[/system.web]

Tuesday, February 17, 2009

"Agile Simplified" is being showcased

I just received an e-mail from www.SlideShare.net saying my presentation Agile Simplified is currently being showcased on the 'Technology' page by their editorial team. It's likely to be there for the next 16-20 hours...

Most probably this happens for all new posted presentations, each might get showcased in its own category, and their so called editorial team might just be an electronic one :) I mean automatic selection.
So although it shouldn't mean any extra credit, it won't hurt to brag about it :) :)

Check my previous blog post about this presentation.

Saturday, February 14, 2009

An Introduction to Agile Software Development

Yesterday, I introduced the Agile way of Software Development to Egyptian University Students participating in Microsoft's Imagine Cup Local Competition.

The presentation took place at Microsoft Egypt Premises.

The slides are available here, where you can view it online (full screen if you want).
Also downloading is enabled.
It's worth mentioning that Imagine Cup World Finals will be held in Egypt.
Finally, I'd like to wish all the teams the best of luck, in the competition.