ASP.Net MVC 资源文件有时会被 ResourceManager 错误加载。

28

概述

我们有一个跨国网站,为其服务的各个国家提供本地化内容。该本地化是使用标准的 .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 设置为适当的值。对于 www.mysite.com 是 en-GB,对于 www.mysite.fr 是 fr-FR 等等。

这个系统在大部分情况下运作良好。但是,有时当应用程序在接收请求时启动时,它会始终为某些资源文件提供错误的内容。

它会持续这样做,直到应用程序重新启动。它可能会再次开始提供不正确的内容。我们必须不断重启,直到它提供正确的内容,此后它将保持稳定。

分析

通过向应用程序投放请求(使用 Fiddler)来模拟本地机器上的问题,我们已经成功地复现了这个问题。该站点正在显示英国版本的某些资源文件的德语内容。

在检查 HTTP 模块是否正确设置了 CurrentUICulture 并且在处理请求过程中保持正确之后,我们开始查看资源管理器。

在应用程序处于这种不正确的状态时,我们检查了 ResourceManager 类上的 _resourceSets 属性的内容。 这是一个以 ISO 文化代码为键的字典。

检查 en-GB 内容时,我们发现它实际上包含了德语版本资源文件中的资源字符串。

似乎有时候,在接收请求时站点启动时,ResourceManager 类会为一个文化加载错误的资源文件或错误地将文件分类到其字典中。

是否有其他人遇到过这种行为,是否有任何解决方法?

谢谢。


你能否发布你的模块和处理程序的简化代码?模块不应该就足够了吗? - Nenad
我们遇到了相同或类似的问题。我们有两个项目(A和B),它们都在resx文件中具有特定于项目的文本。然后,我们有一个带有共享文本的卫星资源程序集。从项目特定的resx文件中获取的文本始终是正确的,但是来自卫星资源程序集的文本有时会在应用程序池重新启动后停留在英语状态。这意味着页面有时包含一些英文文本和一些瑞典文本。唯一“有用”的事情就是重复应用程序池,直到英语不再停滞。 - Carl Björknäs
将本地资源文件从App_GlobalResources移动到普通文件夹中,此后未能再现该问题。http://odetocode.com/Blogs/scott/archive/2009/07/16/resource-files-and-asp-net-mvc-projects.aspx - Carl Björknäs
5个回答

5
这听起来像是你在处理程序中存在线程安全问题。当你修改线程的当前文化时,你正在为可能正在处理多个请求的当前线程进行修改。当响应生成时,另一个请求可能已经改变了线程的当前语言,从而使所有响应具有相同的语言。
我有一些建议可以开始尝试:
1. 确保你的处理程序不可重用。我假设你已经实现了IHttpHandler,在IsReusable属性上返回false,因为多个线程应该同时访问它,并且每个请求将创建一个新实例。 2. 不要使用处理程序... 处理程序不是这种情况的理想解决方法,设置线程文化的首选位置是Application_AcquireRequestState,它将为每个请求正确触发,而不会重叠。 3. 使用路由处理程序代替:http://adamyan.blogspot.com/2010/07/addition-to-aspnet-mvc-localization.html

没有看到你所说的处理程序,其余的只能是关于为什么响应共享线程文化的猜测。问题也可能在于你使用的字典本身不是线程安全的。


1
谢谢回复,但我不认为这就是发生的事情。 如果在请求处理期间线程文化发生了变化,我会期望每次都有不同的结果。但是,如果应用程序在这种状态下启动,则对于每个请求,响应都保持相同;页面上的某些资源是德语,某些是英语。 我已经通过请求处理跟踪了文化,并且在整个过程中都是正确的。 我已经查看了.Net ResourceManager类的状态,它的内部状态是错误的。其针对文化的资源字典在en-GB的条目中具有德语资源。 - Stevo
这个问题是否与您遇到的问题相同?- http://stackoverflow.com/questions/5816773/currentthread-currentuiculture-is-set-correctly-but-seems-to-be-ignored-by-asp-n - BrutalDev
不,这些症状并不相同。如果我们的网站能够正确启动,本地化工作就很好。看起来在负载下启动时,资源管理器会加载错误的文化资源或错误地分类已加载的资源。 - Stevo
2
“负载过重”是可疑的,因为当多个请求更改当前UI线程文化时,资源管理器可能会在此时构建其内容不正确。您是否尝试在HttpContext事件之一中,在自己的处理程序之外执行此操作?在任何请求被处理之前,您还可以在Application_Start中预热资源管理器,从而在它被请求淹没之前正确设置它。 - BrutalDev
如果您正在使用自定义的HttpModule(如所指定),那么您肯定会遇到线程问题,因为每个请求都通过同一个模块,并且在设置线程文化时会存在竞争条件。http://forums.asp.net/t/900381.aspx/1。两个不同语言的请求将相互覆盖,并且都会响应最后设置的语言。当所有这些都发生时,静态资源管理器可能会陷入困境。只需尝试替代解决方案,有很多“更好”的选项可供选择,而不是使用HttpModule。 - BrutalDev
在应用程序启动时预热资源管理器是个好主意。关于你的下一个评论,有什么比使用HttpModule更好的选择吗?谢谢。 - Stevo

2

谢谢!这个问题困扰我很久了,很高兴找到了你的评论。 - Ilya Luzyanin
有关这个问题的任何消息吗?我们可能遇到了同样的问题。报告错误的网址无法使用... - Carl Björknäs
看起来微软已经删除了这个问题,但这并没有什么帮助,因为虽然他们承认了这个问题,但是没有提供修复估计或解决方法。对我们来说很容易解决,因为我们通过包装器访问资源,所以在资源访问周围添加锁定(在我们的情况下是HttpContext.GetGlobalResourceObject/GetLocalResourceObject)并不难。 - Aristarkh Zagorodnikov
@onyXMaster,你知道这个 bug 在 .NET 4.5.1、4.5.2 或 .NET 4.6 下是否仍然存在吗? - fstarnaud
@fstarnaud 我对我提供的重现进行了快速测试,将目标.NET FW更改为4.6.1后 - 没有任何变化,它仍然在共享(预期的,没有人保证它是线程安全的)和私有(真正的罪魁祸首在这里)情况下失败。 - Aristarkh Zagorodnikov
显示剩余2条评论

1

对于其他遇到这个问题的人,我从未得到解决这个问题根本原因的方法,但是找到了一个解决方法。当应用程序启动时,我编写了一个程序,按顺序逐个请求每个支持语言的每个资源文件中的资源。以这种方式单线程地“触摸”每个文件似乎可以使所有资源每次都正确加载。


0

你是否正在使用异步MVC操作?如果是,当你等待一个调用(ConfigureAwait设置为false)时,处理线程在进行中被重用。 "返回"线程(在等待调用后执行代码的线程)不同,可能会丢失之前设置的所有属性。必须将ConfigureAwait设置为false以防止死锁,因此没有立即解决方案。

另一件要检查的事情是缓存,如果你使用[Cache]属性或者缓存资源管理器。


我们目前没有使用异步操作,并已关闭所有缓存以帮助解决此问题。 - Stevo

0

我从其他资源中阅读了这个答案。我想将ResourceManager封装如下,以便在加载的多线程调用下不会出现新的RexourceManager()竞争:

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")

此外,您可以使用类型安全枚举来表示资源类型和名称,将资源管理器和区域信息封装到一个私有类中,并将其保存在并发包中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接