« Hotel Rwanda | Main | Keynote thought »

I had blogged earlier about a bug in dasBlog that affected Turkish users. When a Turkish browser reported an HTTP Accept-Language header indicating Turkish as the preferred language, no blog posts would show up.  As fix, I suggested that users change their blog templates, but I knew that wasn't an appropriate fix.

For background, here's what an Accept-Language header looks like. This user would prefer Turkish, but would take English as their second choice.

GET /DasBlog/default.aspx HTTP/1.1
Accept-Language: tr,en-us;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; Tablet PC 1.7; .NET CLR 2.0.50215)

Why would the browser affect the underlying engine you say? Here's some background before I show you how I fixed the bug. Typically globalized ASP.NET applications set the current thread's Culture and UICulture to a specific culture based on the user's preferences. 

For example:

CultureInfo Turkey = CultureInfo.CreateSpecificCulture("tr");
Thread.CurrentThread.CurrentCulture = Turkey;
           
Thread.CurrentThread.CurrentUICulture = Turkey;

Why two properties? From the MSDN Documentation:

The CurrentCulture property's default value is the system's User Locale, which is set in the Regional Options control panel. The CurrentUICulture property's default value is the system's UI Language, which is the language of your system UI. On Windows 2000 and Windows XP MultiLanguage Edition, the CurrentUICulture defaults to the current user UI language settings.

The CurrentUICulture is used by the ResourceManager to look up resources like strings at run time. The CurrentCulture is set per-thread also, but is used for formatting dates, times, currencies and string manipulation methods.

Earlier I blogged that I suspected a problem with the Reflection method we were using to invoke Macros in dasBlog. We were passed in "items" from a dasBlog tempate and were looking for a property called "Items." We used a reflection method like this:

MemberInfo[] members = subexObject.GetType().FindMembers(

    MemberTypes.Field|MemberTypes.Method|MemberTypes.Property,

    BindingFlags.IgnoreCase|BindingFlags.Instance|BindingFlags.Public,

    new MemberFilter(this.IsMemberEligibleForMacroCall), subex.Trim() );

if ( members.Length == 0 )

{

    throw new MissingMemberException(subexObject.GetType().FullName,subex.Trim());

}

Notice that we're indicating via the BindingFlags.IgnoreCase flag that we want the FindMembers() method to handle the case-insensitivity issue for us. My initial thought was that they must be doing something wrong inside. I mentioned this to Michael Kaplan, but he said there was a big push to fix "Turkish-I" bugs in the 1.1 timeframe. However, he said he'd look at the problem and see if it would need to be fixed if I had a simple repro to prove it was still a problem in 2.0. At this point, I started thinking that it's probably NOT Microsoft, but as I started writing a small repro as a separate project, I was thinking, we (dasBlog) MUST be doing something wrong, otherwise how have the Turks been doing reflection all this time? They'd have run into this before. I googled and couldn't find any Turks suffering the slings and arrows of reflection.

I went back and looked at the code again with fresh eyes. Then I noticed (brain fart here):

MemberInfo[] members = subexObject.GetType().FindMembers(

    MemberTypes.Field|MemberTypes.Method|MemberTypes.Property,

    BindingFlags.IgnoreCase|BindingFlags.Instance|BindingFlags.Public,

    new MemberFilter(this.IsMemberEligibleForMacroCall), subex.Trim() );

if ( members.Length == 0 )

{

    throw new MissingMemberException(

        subexObject.GetType().FullName,subex.Trim());

}

I had been glossing over that. We are/were passing in a delegate for filtering. That method looked like this:

private bool IsMemberEligibleForMacroCall(MemberInfo m, object filterCriteria )

{

    return String.Compare(m.Name,(string)filterCriteria,true)==0;

}

Doh! That's a culture-sensitive string compare. We were comparing the MemberInfo that we got back from the reflection call with the string we knew we needed. Even though the Reflection call to FindMembers succeeded, we were filtering out our method with a bad compare.

Here's where the lesson comes in (and this had bitten me before, so I was kicking myself).

Scott's Rule Number 0x5F: Think about your string compares and their context. Make sure you've expressed your true intent correctly.

Here is the fixed method. It'll be in the next dasBlog point release and won't require anyone to change their templates.

private bool IsMemberEligibleForMacroCall(MemberInfo m, object filterCriteria )

{

    //This has to be case-insensitive and culture-invariant or

    // "Item" and "item" won't match if the current culture is Turk

    return String.Compare(m.Name,(string)filterCriteria, true, System.Globalization.CultureInfo.InvariantCulture) == 0;

}



Sunday, June 05, 2005 10:38:48 AM (Pacific Standard Time, UTC-08:00)
Now that's the type of thing FxCop could have helped with, right?
Sunday, June 05, 2005 11:43:40 AM (Pacific Standard Time, UTC-08:00)
Oh, he may be right -- there are FxCop rules to check this sort of thing.

Of course if you look at the info in the latest string guidelines, it points out that you may want to use something other than 'Invariant' here. :-)

We'll be talking here soon, I would imagine....
Sunday, June 05, 2005 7:02:36 PM (Pacific Standard Time, UTC-08:00)
Cool. Just one thing: is is possible to add some padding between the code and the border? Just curious. :P
Janus
Tuesday, June 07, 2005 12:05:30 AM (Pacific Standard Time, UTC-08:00)
Would the Type class's filter delegate Type.FilterNameIgnoreCase have done the trick for you?
mikeb
Wednesday, June 08, 2005 5:29:11 AM (Pacific Standard Time, UTC-08:00)
We experienced the same problem a few months back and ended up doing the same thing you were doing with CultureInfo.InvariantCulture.

There is a related KB article
PRB: Letter "i" Problems When You Use XML Encoding with Turkish Settings
http://support.microsoft.com/default.aspx?scid=kb;en-us;317505
xjin
Comments are closed.

Contact

Sponsors

Hosting By

Hot Topics

Tags

Calendar

<February 2010>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
28123456
78910111213

Archives

February, 2010 (8)
January, 2010 (13)
December, 2009 (13)
November, 2009 (7)
October, 2009 (19)
September, 2009 (11)
August, 2009 (12)
July, 2009 (21)
June, 2009 (26)
May, 2009 (16)
April, 2009 (13)
March, 2009 (17)
February, 2009 (17)
January, 2009 (18)
December, 2008 (32)
November, 2008 (17)
October, 2008 (22)
September, 2008 (16)
August, 2008 (14)
July, 2008 (25)
June, 2008 (19)
May, 2008 (17)
April, 2008 (17)
March, 2008 (26)
February, 2008 (21)
January, 2008 (28)
December, 2007 (19)
November, 2007 (17)
October, 2007 (31)
September, 2007 (39)
August, 2007 (37)
July, 2007 (43)
June, 2007 (37)
May, 2007 (32)
April, 2007 (38)
March, 2007 (29)
February, 2007 (46)
January, 2007 (31)
December, 2006 (27)
November, 2006 (31)
October, 2006 (32)
September, 2006 (39)
August, 2006 (34)
July, 2006 (40)
June, 2006 (18)
May, 2006 (31)
April, 2006 (34)
March, 2006 (30)
February, 2006 (38)
January, 2006 (44)
December, 2005 (19)
November, 2005 (34)
October, 2005 (24)
September, 2005 (37)
August, 2005 (20)
July, 2005 (24)
June, 2005 (33)
May, 2005 (16)
April, 2005 (22)
March, 2005 (34)
February, 2005 (15)
January, 2005 (37)
December, 2004 (28)
November, 2004 (30)
October, 2004 (34)
September, 2004 (22)
August, 2004 (34)
July, 2004 (18)
June, 2004 (64)
May, 2004 (49)
April, 2004 (21)
March, 2004 (29)
February, 2004 (29)
January, 2004 (36)
December, 2003 (25)
November, 2003 (24)
October, 2003 (59)
September, 2003 (42)
August, 2003 (24)
July, 2003 (44)
June, 2003 (29)
May, 2003 (21)
April, 2003 (30)
March, 2003 (27)
February, 2003 (47)
January, 2003 (50)
December, 2002 (31)
November, 2002 (38)
October, 2002 (44)
September, 2002 (15)
May, 2002 (2)
April, 2002 (4)

Google Ads