在开发国际化的ASP.NET应用程序时,资源文件的管理是一个重要的环节。本文将介绍一种模拟技术,它能够实现资源文件夹中任何资源的"自动"翻译,而无需创建和维护一系列自动翻译的resx文件。当当前文化与默认文化不同时,资源将被特定文化的标签所包围。
ASP.NET框架处理两种类型的资源:
全局资源可以在Web应用程序的任何级别使用。resx文件必须存储在应用程序根目录的App_GlobalResources中。
本地资源仅在页面级别工作。每个网页可以定义一个与字符串资源关联的resx文件。该resx文件必须存储在与页面及其名称相同层次结构的App_LocalResources中。
有关更多信息,请参阅MSDN上的。
资源处理是通过资源提供者进行的,它允许读取特定资源。资源提供者是由ResourceProviderFactory创建的,它提供了两种方法:
CreateGlobalResourceProvider返回全局资源的资源提供者,用于类键(即App_GlobalResources中的ResX文件名称)。
CreateLocalResourceProvider返回本地资源的资源提供者,用于虚拟路径(即网页的名称)。
将实现自己的工厂,以创建自定义资源提供者。
using System.Web.Compilation;
namespace MyProject.Resources {
public class CulturePrefixedResourceProviderFactory : ResourceProviderFactory {
public override IResourceProvider CreateGlobalResourceProvider(string classKey){
return new CulturePrefixedGlobalResXResourceProvider(classKey);
}
public override IResourceProvider CreateLocalResourceProvider(string virtualPath){
return new CulturePrefixedLocalResXResourceProvider(virtualPath);
}
}
}
资源提供者需要实现IResourceProvider接口,该接口定义了:
ResourceReader属性,返回一个允许资源读取的读取器
GetObject方法,根据其键和文化返回资源值
本地和全局资源提供者将覆盖AbstractResXResourceProvider类,它提供了一点抽象,实现了IResourceProvider.GetObject方法,创建了翻译模拟方面。
using System.Globalization;
using System.Resources;
using System.Web.Compilation;
namespace MyProject.Resources {
public abstract class AbstractResXResourceProvider : IResourceProvider {
protected AbstractResXResourceProvider() { }
protected abstract ResourceManager ResourceManager { get; }
public virtual object GetObject(string resourceKey, CultureInfo culture) {
if (ResourceManager == null) {
return null;
}
if (culture == null) {
culture = CultureInfo.CurrentUICulture;
}
object item = ResourceManager.GetObject(resourceKey, culture);
string strItem = item as string;
if (strItem != null && culture.TwoLetterISOLanguageName != "fr" && culture != CultureInfo.InvariantCulture) {
return string.Format(CultureInfo.InvariantCulture, "[{0}]{1}[/{0}]", culture.Name, strItem);
}
return item;
}
}
}
全局资源提供者扩展了AbstractResXResourceProvider,并实现了对位于全局资源专用程序集中的资源管理器的访问。这个可以通过BuildManager的AppResourcesAssembly属性来检索。
解决方案使用了反射,因为没有在.NET API中找到任何现有的公共入口点来执行此操作。
protected virtual Assembly AppResourcesAssembly {
get {
Type buildManagerType = typeof(BuildManager);
PropertyInfo property = buildManagerType.GetProperty("AppResourcesAssembly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField);
return (Assembly)property.GetValue(null, null);
}
}
实现的解决方案也使用了大量的反射,因为.NET API中缺乏执行此操作的入口点。
protected string VirtualPathFileName {
get {
PropertyInfo fileNameProperty = _virtualPathType.GetProperty("FileName");
return (string)fileNameProperty.GetValue(_virtualPathInstance, null);
}
}
protected object VirtualPathParent {
get {
PropertyInfo parentProperty = _virtualPathType.GetProperty("Parent");
return parentProperty.GetValue(_virtualPathInstance, null);
}
}
protected override ResourceManager ResourceManager {
get {
Assembly localResourceAssembly = LocalResourceAssembly;
if (localResourceAssembly == null) {
throw new InvalidOperationException("Unable to find the matching resource file.");
}
ResourceManager manager = new ResourceManager(VirtualPathFileName, localResourceAssembly);
manager.IgnoreCase = true;
return manager;
}
}
像往常一样,在ASP.NET中,web.config文件允许修改用于全球化的ResourceProviderFactoryType。
<configuration>
<system.web>
<globalization resourceProviderFactoryType="MyProject.Resources.CulturePrefixedResourceProviderFactory" culture="fr-FR" uiCulture="fr-FR" />
</system.web>
</configuration>
正如所看到的,为ASP.NET应用程序创建自定义资源提供者需要一些工作。在测试国际化应用程序时使用这个工具,以确保每个标签都在resx文件中正确填充。