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.
早些时候,我曾写过一篇有关dasBlog中影响土耳其用户的错误的博客。 当土耳其语浏览器报告HTTP接受语言标头表示土耳其语为首选语言时,将不会显示博客文章。 作为修复,我建议用户更改其博客模板,但我知道这不是适当的修复。
For background, here's what an Accept-Language header looks like. This user would prefer Turkish, but would take English as their second choice.
对于背景,这是Accept-Language标头的样子。 该用户更喜欢土耳其语,但将英语作为第二选择。
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)GET /DasBlog/default.aspx HTTP / 1.1 接受语言: tr ,en-us; q = 0.5 接受编码:gzip,放气用户代理:Mozilla / 4.0(兼容; 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.
为什么浏览器会影响您说的基础引擎? 在向您展示如何修复该错误之前,这里有一些背景知识。 通常,全球化的ASP.NET应用程序根据用户的偏好将当前线程的文化和UICulture设置为特定的文化。
For example:
例如:
CultureInfo Turkey = CultureInfo.CreateSpecificCulture("tr");
Thread.CurrentThread.CurrentCulture = Turkey; Thread.CurrentThread.CurrentUICulture = Turkey;CultureInfo土耳其= CultureInfo.CreateSpecificCulture(“ tr”); Thread.CurrentThread.CurrentCulture =土耳其; Thread.CurrentThread.CurrentUICulture =土耳其;
Why two properties? From the MSDN Documentation:
为什么要有两个属性? 从MSDN文档中:
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.
CurrentCulture属性的默认值是系统的“用户区域设置”,该设置在“区域选项”控制面板中设置。 CurrentUICulture属性的默认值是系统的UI语言,这是系统UI的语言。 在Windows 2000和Windows XP MultiLanguage Edition上,CurrentUICulture默认为当前用户UI语言设置。
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.
ResourceUI使用CurrentUICulture在运行时查找诸如字符串之类的资源。 CurrentCulture也按线程设置,但用于格式化日期,时间,货币和字符串操作方法。
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:
早些时候,我在博客中写道,我怀疑我们用来在dasBlog中调用Macros的Reflection方法存在问题。 我们是从dasBlog模板中传入“项目”的,并正在寻找一个名为“ Items”的属性。 我们使用了这样的反射方法:
MemberInfo[] members = subexObject.GetType().FindMembers(
MemberInfo []成员= subexObject.GetType()。FindMembers(
MemberTypes.Field|MemberTypes.Method|MemberTypes.Property,
MemberTypes.Field | MemberTypes.Method | MemberTypes.Property,
BindingFlags.IgnoreCase|BindingFlags.Instance|BindingFlags.Public,
BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public,
new MemberFilter(this.IsMemberEligibleForMacroCall), subex.Trim() );
new MemberFilter( this .IsMemberEligibleForMacroCall),subex.Trim());
if ( members.Length == 0 )
如果(members.Length == 0)
{
{
throw new MissingMemberException(subexObject.GetType().FullName,subex.Trim());
抛出新的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.
注意,我们通过BindingFlags.IgnoreCase标志指示我们希望FindMembers()方法为我们处理不区分大小写的问题。 我最初的想法是他们必须在内部做错事。 我向迈克尔·卡普兰(Michael Kaplan)提到了这一点,但是他说,在1.1的时间范围内,大力推动了“ Turkish-I”错误的修复。 但是,他说他会研究这个问题,看看如果我有一个简单的repro来证明它在2.0中仍然是一个问题,是否需要修复它。 在这一点上,我开始认为可能不是Microsoft,但是当我开始编写一个小型repro作为一个单独的项目时,我在想,我们(dasBlog)一定做错了什么,否则土耳其人是怎么做的呢?时间? 他们以前会遇到这个问题。 我用谷歌搜索,没有找到任何遭受突击和反射之箭的土耳其人。
I went back and looked at the code again with fresh eyes. Then I noticed (brain fart here):
我回去,再次用新鲜的眼睛看了一下代码。 然后我注意到了(这里的脑放屁):
MemberInfo[] members = subexObject.GetType().FindMembers(
MemberInfo []成员= subexObject.GetType()。FindMembers(
MemberTypes.Field|MemberTypes.Method|MemberTypes.Property,
MemberTypes.Field | MemberTypes.Method | MemberTypes.Property,
BindingFlags.IgnoreCase|BindingFlags.Instance|BindingFlags.Public,
BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public,
new MemberFilter(this.IsMemberEligibleForMacroCall), subex.Trim() );
new MemberFilter( this .IsMemberEligibleForMacroCall),subex.Trim() );
if ( members.Length == 0 )
如果(members.Length == 0)
{
{
throw new MissingMemberException(
抛出新的MissingMemberException(
subexObject.GetType().FullName,subex.Trim());
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 )
私人布尔IsMemberEligibleForMacroCall(MemberInfo m, object filterCriteria)
{
{
return String.Compare(m.Name,(string)filterCriteria,true)==0;
返回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.
h! 这是对文化敏感的字符串比较。 我们正在将从反射调用中获得的MemberInfo与我们所需的字符串进行比较。 即使对FindMembers的Reflection调用成功,我们仍在通过比较不好的结果过滤掉了我们的方法。
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.
斯科特的规则编号0x5F:考虑一下您的字符串比较及其上下文。 确保正确表达了自己的真实意图。
Here is the fixed method. It'll be in the next dasBlog point release and won't require anyone to change their templates.
这是固定方法。 它将在下一个dasBlog点版本中发布,并且不需要任何人更改其模板。
private bool IsMemberEligibleForMacroCall(MemberInfo m, object filterCriteria )
私人布尔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
//如果当前的文化是土耳其语,则“ Item”和“ item”将不匹配
return String.Compare(m.Name,(string)filterCriteria, true, System.Globalization.CultureInfo.InvariantCulture) == 0;
返回String.Compare(m.Name,( string )filterCriteria, true ,System.Globalization.CultureInfo.InvariantCulture)== 0;
}
}