资源管理器有时会错误地加载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中对文件进行了错误的分类。

其他人经历过这种行为吗?有人知道有什么解决办法吗?

谢谢。

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

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

我有一些建议你可以从开始

  1. 确保您的处理程序不可重复使用。我假设您已经实现了IHttpHandler,IsReusable属性返回false,因为多个线程应该同时命中它,并且每个请求将创建一个新实例
  2. 不要使用处理程序。。。处理程序不是此类问题的理想解决方案。设置线程区域性的首选位置是Application_AcquireRequestState,它将为每个请求正确激发,而不会重叠
  3. 请改用路由处理程序: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,并将资源管理器和区域性信息封装到一个私有类中,然后将其保存在并发包中。