Friday, November 05, 2010

An ASP.NET HttpModule to set the current culture to the user’s locale

Update: sandord, in the comments, has pointed out that you can achieve the same result with a simple configuration setting in Web.config:

<globalization culture="auto" uiculture="auto" enableClientBasedCulture="”true”" />

My web application TardisBank (a pocket money bank account for kids) needs to show users the currency symbol for their location. So UK users should see ‘£’, but US users should see ‘$’. I’d thought that I would have to ask the user to choose a currency symbol when they signed up, but Dylan Beattie had a much better idea:

dylan_twitter_recommends_locale_from_request

A quick Google turned up this excellent article by Rick Strahl, ‘detecting and setting the current locale on the current asp.net web request’.

Using Rick’s code I quickly created an ASP.NET module:

using System;
using System.Globalization;
using System.Threading;
using System.Web;

namespace Suteki.TardisBank.Mvc
{
    public class UserLocaleModule : IHttpModule
    {
        public void Init(HttpApplication httpApplication)
        {
            httpApplication.BeginRequest += (sender, eventArgs) =>
            {
                var app = sender as HttpApplication;
                if (app == null)
                {
                    throw new ApplicationException("Sender is null or not an HttpApplication");
                }
                var request = app.Context.Request;
                if (request.UserLanguages == null || request.UserLanguages.Length == 0) return;

                var language = request.UserLanguages[0];
                if (language == null) return;

                try
                {
                    Thread.CurrentThread.CurrentCulture = new CultureInfo(language);
                }
                catch
                {}
            };
        }

        public void Dispose()
        {
            
        }
    }
}

Which I configured in TardisBank’s web.config:

<modules runAllManagedModulesForAllRequests="true">
  <add name="UserLocale" type="Suteki.TardisBank.Mvc.UserLocaleModule, Suteki.TardisBank" />
</modules>

And now I’ve got currency symbols :)

tardisbank_currency_gbp

And if I change my browser’s language settings:

tardisbank_change_language_settings

I now get the $ symbol instead:

tardisbank_currency_usd

Thanks Dylan & Rick! It’s a very neat solution.

10 comments:

  1. I don't think you need this bit...

    language = language + "-" + language.ToUpper();

    You can create a CultureInfo just fine from a two letter language code e.g.

    new CultureInfo("fr").DisplayName.Dump();

    Plus you may run into an issue where using the language code as the region code results in an invalid culture.

    ReplyDelete
  2. Thanks Derek, I did some more tests and you are right. I wonder why Rick had this in his original code?

    Anyway I've updated the post.

    ReplyDelete
  3. Anonymous7:33 am

    Don;t you need to convert amounts to the new currency too?

    Dimitris

    ReplyDelete
  4. Anonymous8:54 am

    How is this any different from adding this to the web.config file?

    ReplyDelete
  5. Anonymous8:55 am

    Sorry, the XML didn't show up. Here it is:

    <globalization culture="auto" uiCulture="auto"/>

    ReplyDelete
  6. Anonymous10:25 am

    I'm with Dimitris. The currency symbol is useless as you need to convert the value. Currency type and ammount have a relationship as a pound/dollar/euro are not the same.

    ReplyDelete
  7. Hi Anonymous, Dimitris,

    Yes, you are right _if_ I was transferring money from one user's account to another. I'm ignoring the case where parents and children could be in different countries of course.

    Of course if you are writing a serious financial application, then you need a better way to record and convert currencies.

    ReplyDelete
  8. Hi sandord,

    Thanks for pointing that out to me, I had no idea that that setting even existed :p

    Yes, it does look like that would do exactly the same thing.

    I'm updating the post to point this out.

    ReplyDelete
  9. Another minor optimization is use the creator method CultureInfo.GetCultureInfo() to construct the culture, instead of creating a new one. The former gets the value from the pre-initialized tables.

    ReplyDelete

Note: only a member of this blog may post a comment.