Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Auto-Culture detection related issues in ASP.NET


:P
On this page:

I've been mostly playing around with new Auto-Culture switching in ASP.NET 2.0 and it seems that there are a coulpe of issues I've run into that I can't seem to work around using this mechanism.

 

I’ve posted about ways to switch cultures in code previously and that’s been working fine, but now that ASP.NET includes some page level functionality it might seem like a good idea to let ASP.NET do this for me.

 

But I’m running into some issues. One is the CurrencySymbol. Auto-culture switches also switch the currency symbol which in most situations is not what you want. While I want to show German content and number and date formatting I don’t want to show pricing in Euros.

 

With my old code I created a new CultureInfo object and then assigned it to the thread. On that object I could happily override the Currency symbol:

 

/// <summary>

/// Sets a user's Locale based on the browser's Locale setting. If no setting

/// is provided the default Locale is used.

/// </summary>

public static void SetUserLocale(string CurrencySymbol,bool SetUiCulture)

{

    HttpRequest Request = HttpContext.Current.Request;

    if (Request.UserLanguages == null)

        return;

 

    string Lang = Request.UserLanguages[0];

    if (Lang != null)

    {

        // *** Problems with Turkish Locale and upper/lower case

        // *** DataRow/DataTable indexes

        if (Lang.StartsWith("tr"))

            return;

 

        if (Lang.Length < 3)

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

        try

        {

            System.Globalization.CultureInfo Culture = new System.Globalization.CultureInfo(Lang);

            System.Threading.Thread.CurrentThread.CurrentCulture = Culture;

 

            if (!string.IsNullOrEmpty(CurrencySymbol))

                System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencySymbol =

                  CurrencySymbol;

 

            if (SetUiCulture)

                System.Threading.Thread.CurrentThread.CurrentUICulture = Culture;

        }

        catch

        { ;}

    }

}

But if I do the following in an Asp.NET page:

 

protected override void InitializeCulture()

{

   System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencySymbol = "$";

   base.InitializeCulture();

 }

 

I get:

Instance is read-only.

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.InvalidOperationException: Instance is read-only.

Source Error:

 
 
Line 24:     {
 
Line 25: 
 
Line 26:         System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencySymbol = "$"; 
 
Line 27:         base.InitializeCulture();
 
Line 28:     }

 

So it looks if I want to do this I can go back to assigning my own culture with:

 

protected override void InitializeCulture()

{

 

        wwWebUtils.SetUserLocale("$");

        base.InitializeCulture();

}

 

This works, but of course this defeats the whole purpose of using the ASP.NET auto culture detection in the first place.

 

Is there an easier way?

 

Caching Problems

I’ve also run into a problem with caching which even with using a VaryByCustom for culture doesn’t seem to do the right thing. If you use cultures and caching is active you have to be careful so that you don’t cache culture specific information. So I figured you can use:

 

<%@ OutputCache VaryByCustom="Culture" Duration="60" VaryByParam="none" %>                       

 

And then set up this code in global.asax:

 

public override string GetVaryByCustomString(HttpContext Context, string Custom)   

{

    if (Custom == "Culture")

    {

        return System.Globalization.CultureInfo.CurrentCulture.Name;

    }

   

    return base.GetVaryByCustomString(Context,Custom);

}

 

But this doesn’t actually work when Auto-Culture switching is enabled in a page. Xctually I’m not surprised. By the time ASP.NET is setting the locale for page level auto-culture detection, the cache is long done and has already returned its output it seems and that's exactly the behavior I see. If I run two browsers, one in English one in German, hit the page in English then switch to the German browser the German browser gets the English page.

 

If I switch back to my original SetUserLocale code in Application.BeginRequest the caching works correctly:

 

void Application_BeginRequest(object sender, EventArgs e)

{

    Westwind.Globalization.Tools.wwWebUtils.SetUserLocale("$");

}

 

Since this happens before the cache processing occurs the check for the VaryByCustom actually has an effect.

 

What am I missing here? Both of these issues seem like a glaring holes in the Auto-Culture switching in ASP.NET’s page mechanism.

Posted in ASP.NET  Localization  

The Voices of Reason


 

Maurice
October 30, 2006

# re: Auto-Culture detection related issues in ASP.NET

Hi Rick,

You need to check if the culture is ReadOnly and if so create a Clone(). The Clone is readwrite and will let you set properties.

Something like:
if (culture.IsReadOnly)
culture = (CultureInfo)culture.Clone();
culture.DateTimeFormat.ShortDatePattern = "dd-MM-yyyy";

Marc Brooks
October 30, 2006

# re: Auto-Culture detection related issues in ASP.NET

You should Clone a culture before mucking with it, then you can store that modified Culture against the CurrentThread. Also for OutputCache to work properly with cultures, you really need to override GetVaryByCustomString... here's my Global.asax.cs
        static string s_DefaultTheme = ConfigurationManager.AppSettings["defaultTheme"];
        static string s_DefaultMaster = ConfigurationManager.AppSettings["defaultMaster"];

        public override string GetVaryByCustomString(HttpContext ctx, string customstring)
        {
            string browser = ctx.Request.Browser.Type;
            string culture = Thread.CurrentThread.CurrentCulture.Name;
            string theme = s_DefaultTheme;
            string master = s_DefaultMaster;

            //NOTE: Profile object will be null the first time this is called, when ASP.NET tries to see if the 
            // page is cached. So, to properly use Profile object with page caching we need to write code
            // to access the Profile database directly, or use some other mechanism to find out user preferences early
            // on, such as an HTTP cookie  

            if (ctx != null && ctx.Profile != null)
            {
                theme = (string)ctx.Profile.GetPropertyValue("Theme");
                master = (string)ctx.Profile.GetPropertyValue("Master");
            }

            StringBuilder customVaryBy = new StringBuilder();
            StringBuilder defaultVaryBy = new StringBuilder();

            foreach (string key in customstring.Split('+'))
            {
                switch (key.ToLowerInvariant())
                {
                    case "browser":
                        customVaryBy.Append(browser ?? string.Empty).Append('+');
                        break;
                    case "culture":
                        customVaryBy.Append(culture ?? string.Empty).Append('+');
                        break;
                    case "theme":
                        customVaryBy.Append(theme ?? string.Empty).Append('+');
                        break;
                    case "master":
                        customVaryBy.Append(master ?? string.Empty).Append('+');
                        break;
                    default:
                        // build up a list of things we don't understand to pass down to the base...
                        defaultVaryBy.Append(key).Append('+');
                        break;
                }
            }

            if (defaultVaryBy.Length > 1)
                defaultVaryBy.Length--; // drop trailing '+'

            return customVaryBy + base.GetVaryByCustomString(ctx, defaultVaryBy.ToString());
        }

Rick Strahl
October 30, 2006

# re: Auto-Culture detection related issues in ASP.NET

Eh Marc - I'm already implementing VaryByCustomString and it is working if the culture is set in Application.BeginRequest, but not when auto-culture switching is enabled in the page.

Rick Strahl
November 05, 2006

# re: Auto-Culture detection related issues in ASP.NET

So, to switch the culture's currency symbol you can do the following in Page code:

    protected override void InitializeCulture()
    {
        base.InitializeCulture();
        
       Thread.CurrentThread.CurrentCulture  = CultureInfo.CurrentCulture.Clone() as CultureInfo;
       Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencySymbol = "$";
    }


But the more I think about this, this is just not a good place to do this. So now we're essentially creating instances twice - once ASP.NET does the culture switch for the page, then we clone and reassign.

It seems to me it's just ALOT cleaner to handle culture switching manually in Application_BeginRequest and do it once there UNLESS you do profile or user based language assignments in which case you generally won't have the chance to do it until page code fires.

Mozahid Islam
March 31, 2009

# re: Auto-Culture detection related issues in ASP.NET

I do have same type of problem in turkish culture (.Net 2.0) where We are reading the column name from a table as
cboSampleStatus.DataTextField = dsSampleLookupValues.Tables[0].Columns["LANGUAGETERMDESC"].ToString().
But at run time it is not able to read its column name. But if we read as dsSampleLookupValues.Tables[4].Columns["EQUipMENTTYPEID"].ToString() then it is able to read.

same as in this case cboSampleCondition.DataValueField = dsSampleLookupValues.Tables[1].Columns["ACTIONSTATUSID"]

But it works for dsSampleLookupValues.Tables[1].Columns["ACTioNSTATUSID"]

Before that tryied by setting the default(en-US) or invarient culture or tr-TR, but it didn't work for me..
This problem persists with only with tr-TR not with any other culture like pt-BR,fr-FR chinese etc... can some one help me.

Mozahid Islam
March 31, 2009

# re: Auto-Culture detection related issues in ASP.NET

Sorry i did a mistake... The first statement is not cboSampleStatus.DataTextField = dsSampleLookupValues.Tables[0].Columns["LANGUAGETERMDESC"].ToString().
but
cboSampleStatus.DataTextField = dsSampleLookupValues.Tables[0].Columns["EQUIPMENTTYPEID"].ToString().

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024