资源管理器有时会错误地加载ASP.Net MVC资源文件
本文关键字:Net ASP MVC 资源 源文件 加载 错误 资源管理器 | 更新日期: 2023-09-27 18:22:17
概述
我们有一个跨国网站,为其服务的各个国家提供本地化内容。此本地化是使用标准.Net资源文件实现的。
当我们的web应用程序在生产环境中启动或在负载下回收时,有时它会显示特定国家/地区的错误资源。例如,英国网站可能会显示法语内容。
这种情况一直持续到应用程序重新启动。
详细信息
生产环境是Windows Server 2012上的IIS 8。该应用程序是在ASP.Net MVC 4。
应用程序决定传入URL所服务的区域设置。因此www.mysite.com将是英国英语www.mysite.fr将是法语等
我们有一个IHttpModule的实现,它是通过Web.config注册的。在模块的Init方法中,它将处理程序附加到BeginRequest事件。在该方法中,将检查传入的URL,并将线程的CurrentUICulture设置为适当的值。en-GB代表www.mysite.com,fr-fr代表www.myste.fr等
这个系统在大多数情况下运行良好。但是,有时当应用程序在接收请求时启动时,它会始终为某些资源文件提供错误的内容。
它将继续执行此操作,直到应用程序重新启动。它可能会再次重新开始提供不正确的内容。我们必须不断重启,直到它提供正确的内容,届时它将保持稳定。
分析
我们已经能够通过在启动期间向应用程序抛出请求(使用Fiddler),在开发PC上本地复制这一点。该网站显示了英国版网站上一些资源文件的德语内容。
在检查了代码中明显的罪魁祸首(CurrentUICulture由HTTP模块正确设置,并在整个请求处理过程中保持正确)后,我们开始查看资源管理器。
由于应用程序在这种不正确的状态下启动,我们检查了ResourceManager类上_resourceSets属性的内容。这是一本基于ISO文化代码的词典。通过检查en-GB的内容,我们发现它实际上包含了德国版本的资源文件中的资源字符串
有时,当站点在接收请求时启动时,ResourceManager类似乎为区域性加载了错误的资源文件,或者在其Dictionary中对文件进行了错误的分类。
其他人经历过这种行为吗?有人知道有什么解决办法吗?
谢谢。
这听起来像是处理程序中存在线程安全问题。当您修改线程当前区域性时,您正在为可能处理多个请求的当前线程修改它。当生成响应时,另一个请求可能会更改线程当前语言,使所有响应都使用相同的语言。
我有一些建议你可以从开始
- 确保您的处理程序不可重复使用。我假设您已经实现了IHttpHandler,IsReusable属性返回false,因为多个线程应该同时命中它,并且每个请求将创建一个新实例
- 不要使用处理程序。。。处理程序不是此类问题的理想解决方案。设置线程区域性的首选位置是Application_AcquireRequestState,它将为每个请求正确激发,而不会重叠
- 请改用路由处理程序:http://adamyan.blogspot.com/2010/07/addition-to-aspnet-mvc-localization.html
在没有看到处理程序的情况下,您谈论的其余部分只能是猜测为什么响应共享线程文化。问题很容易就出在你正在使用的字典里,它本身就不是线程安全的。
我们最近遇到了同样的问题。这似乎是ResourceManager(或其帮助程序代码)中的竞争条件。我在https://bitbucket.org/onyxmaster/resmanrc.此外,我在MS Connect网站上提交了一个错误,位于https://connect.microsoft.com/VisualStudio/feedback/details/806505/.
附言:我不确定这是否算是一个答案,因为我不知道有什么变通办法,但至少现在有一个repro和一个bug报告。
对于其他遇到此问题的人,我从未找到解决此问题的根本原因的方法,但确实找到了解决方法。当应用程序启动时,我有一个例程依次遍历每个资源文件,并用每种支持的语言从每个文件中请求资源。以这种方式以单线程方式"触摸"每个文件似乎可以让所有资源每次都能正确加载。
您使用异步MVC操作吗?如果是,则在等待调用时(ConfigureAwait设置为false),处理线程在进行中会被重用。"返回"线程(在等待的调用之后执行代码的线程)不同,可能会丢失之前设置的所有属性。ConfigureAwait必须设置为false以防止死锁,因此没有立即的解决方案。
如果使用[Cache]属性或缓存资源管理器,则需要检查的另一件事是缓存。
我从其他资源中读到了这个答案。并且希望按照如下方式包装ResourceManager,这样新的ResourceManager()就不会在加载的多线程调用下运行:
public sealed class LocalizationHandler
{
[ThreadStatic]
private static ResourceManager _manager
private readonly ConcurrentBag<ResourceManager>
_localizationIdentityCollection = new ConcurrentBag<ResourceManager>();
private LocalizationHandler(){}
public static LocalizationHandler Load(ResourceType source)
{
switch (source)
{
case typeA:
//check if resource exist in the concurrent bag or create a new one
_manager = getManager();
break;
}
return this;
}
public string Get(string key)
{
return _manager.Get(key)
}
}
然后你可以打这样的电话:
LocalizationHandler.Load(ResourceType.TypeA).Get("your resource Key String")
此外,您可以对资源类型使用类型安全枚举,对资源名称使用name,并将资源管理器和区域性信息封装到一个私有类中,然后将其保存在并发包中。