ResouceManager有时会错误地加载ASP.Net MVC资源文件

时间:2013-07-15 10:43:31

标签: c# asp.net-mvc asp.net-mvc-3 asp.net-mvc-4

概述

我们有一个跨国网站,为其服务的各个国家/地区提供本地化内容。这种本地化是使用标准的.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.mysite.fr等。

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

在重新启动应用程序之前,它会继续执行此操作。它可能会再次重新启动提供错误的内容。我们必须继续重新启动,直到它提供正确的内容,此时它将保持稳定。

分析

我们已经能够在开发PC上本地重现这一点,方法是在启动期间向应用程序发送请求(使用Fiddler)。该网站显示了英国版网站上某些资源文件的德语内容。

在我们的代码中检查了明显的罪魁祸首(HTTP模块正确设置了CurrentUICulture并在整个请求处理过程中保持正确),我们开始查看资源管理器。

在应用程序以不正确的状态启动时,我们检查了ResourceManager类上_resourceSets属性的内容。 这是一个以ISO文化代码为主的字典。 检查en-GB的内容,我们发现它确实包含了德语版资源文件中的资源字符串。

有时,当网站在接收请求时启动时,ResourceManager类正在为文化加载错误的资源文件,或者它在字典中对文件进行了错误的分类。

有没有其他人遇到过这种行为,是否有人知道有任何变通办法?

感谢。

5 个答案:

答案 0 :(得分:5)

这听起来好像你的处理程序中存在线程安全问题。当您修改线程当前区域性时,您正在为可能正在处理多个请求的当前线程修改它。生成响应时,另一个请求可能会改变当前的线程语言,使所有响应都使用相同的语言。

我可以从一开始就提出一些建议:

  1. 确保您的处理程序不可重用。我假设你已经实现了IHttpHandler,为IsReusable属性返回false,因为多个线程应该同时命中它,并且每个请求都会创建一个新实例。
  2. 不要使用处理程序...处理程序不是这样的理想解决方案,设置线程文化的首选位置是Application_AcquireRequestState,它将针对每个请求正确触发而不重叠。
  3. 改为使用路由处理程序:http://adamyan.blogspot.com/2010/07/addition-to-aspnet-mvc-localization.html
  4. 如果没有看到处理程序,你会谈到剩下的只能推测为什么响应正在共享线程文化。问题可能很容易出现在您正在使用的字典中,这本身就不是线程安全的。

答案 1 :(得分:2)

我们最近遇到了同样的问题。这似乎是ResourceManager(或其帮助程序代码)中的竞争条件。 我在https://bitbucket.org/onyxmaster/resmanrc放了一个复制品。 另外,我在https://connect.microsoft.com/VisualStudio/feedback/details/806505/上的MS Connect网站上提交了一个错误。

P.S。我不确定这是否算作答案,因为我知道没有解决办法,但至少现在有一个复制品和错误报告。

答案 2 :(得分:1)

对于遇到此问题的其他人,我从来没有解决此问题的根本原因,但确实找到了解决方法。 当应用程序启动时,我有一个例程按顺序遍历每个资源文件,并从每个受支持语言的每个文件中请求资源。 以单线程方式以这种方式“触摸”每个文件似乎允许每次都正确加载所有资源。

答案 3 :(得分:0)

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

要检查的另一件事是缓存,如果您使用[Cache] attrbute或缓存资源管理器。

答案 4 :(得分: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")

此外,您可以使用type safe enum作为资源类型名称的资源类型,并将资源管理器与文化信息一起包装到私有类中,并将其保存在并发包中。