在ASP.NET 1.1 中实现全球化的应用

关于ASP.NET全球化设计的背景以及相关知识点请参看:http://www.microsoft.com/china/msdn/archives/library/dnaspp/html/aspnet-globalarchi.asp

本文通过一个Web应用中的全球化的应用实践来说明如何在ASP.NET 1.1中利用Resource文件来实现Web应用的多国语言的切换。

一. 资源文件与程序集

关于.NET的资源,没有区域性特定的资源文件打包在主程序集内(AssemblyName.dll),而有区域性特定的资源文件打包在附属程序集内(AssemblyName.resources.dll)附属程序集:针对应用程序支持的每个文化环境都将生成一个附属程序集。每个文化环境都有一个未指定区域的中性文化环境,例如,“en”是表示中性英语文化环境的 ISO 代码,而“es”是表示中性西班牙语环境的代码。提供中性文化环境的翻译时,它将作为该文化环境的默认资源(如果您没有为每个可能的区域提供特定的资源)。特定文化环境同时包含了语言和区域。例如,“en-CA”表示英语(加拿大),“en-US”表示英语(美国),“es-EC”表示西班牙语(厄瓜多尔),“es-SP”表示西班牙语(西班牙)。而在部署的时候,附属程序集部署到根据此文化环境的 ISO 代码命名的子目录下(比如:bin/zh-cn/AssemblyName.resources.dll)。

在ASP.NET 1.1 中实现全球化的应用

利用这一点,我们为每个页面(WebForm.aspx)创建一个特定区域的.resx文件。将其中的描述性文字统统写到这个特定区域的.resx文件中(Training0010.zh-cn.resx)。这样区域相关的不同语言的字符串就从aspx中剥离出去。如果应用需要显示别的语言版本时,只要追加这个语言的resx文件即可。

在ASP.NET 1.1 中实现全球化的应用

为了区别不同的语言包,将不同区域限定的资源文件放在以区域命名的文件夹下。需要注意的是:资源文件的目录结构将会影响附属程序集中MAINFEST对于资源文件的描述,而嵌入主程序集的资源文件不会受目录结构影响。可以通过.NET提供的ildasm.exe来查看附属程序集中的资源文件的MAINFEST,这个资源名是从附属程序集中获取资源文件的关键。(如果你在提取资源时候抛出找不到资源文件的错误时,也可以利用ildasm.exe来查看你程序中所写的资源名是否和嵌入程序集中的资源名是否吻合)

特别需要注意的是:当文件夹名中有“-”,嵌入附属程序集之后会被改为“_”;当文件夹名以数字开头命名,嵌入附属程序集之后会在前面追加“_”。如果大家有兴趣可以多试试,“所见非所得”也让不少程序员头痛。

在ASP.NET 1.1 中实现全球化的应用

二. 资源文件与区域属性

我们知道可以通过web.config中的<globalization></globalization>culture属性来配置整个应用程序的文化区域。相当于为每个WebForm中的@Page指令加上culture属性。为了使应用程序可以显示不同的语言,所以首先要将requestresponse的编码方式都设置为UTF-8

<globalization

requestEncoding="utf-8"

responseEncoding="utf-8"

culture="zh-cn"

/>

如上的culture属性将会影响到每个页面的System.Threading.Thread.CurrentThread.CurrentCultureInfo

而区域的属性将会影响到日期表示格式,货币表示等方面。在实现全球化时,该属性需要在运行时被修改。因此,在设置web.config不用理会这个属性就可以了,因为在后面会动态的修改它。

如:

<globalization

requestEncoding="utf-8"

responseEncoding="utf-8"

/>

嵌入的资源在每个程序集清单中被标识为 assemblyname.resourcename.culture.resources,所以,为了切换不同区域之间的资源文件,我们还要利用到System.Globalization.CultureInfo这个类。

利用不同的ISO代码(zh-cn, ja-jp)来构造不同语言版本的CultureInfo

_AppCultureInfo = CultureInfo.CreateSpecificCulture(strCulture);

并且设置当前的ThreadCulture对象:

System.Threading.Thread.CurrentThread.CurrentCulture = _AppCultureInfo;

然后通过这个CultureInfo对象,获取相关语言的资源(rmResourceManager的实例)

string strValue = rm.GetString(key, _AppCultureInfo);

_AppCultureInfo为“zh-cn”时,rm.GetString(key, _AppCultureInfo)方法就会从 .zh-cn.resx的资源文件中查找key并返回其value

三. 运行时获取相关页面的资源文件

为了提高应用程序的开发效率,每个页面都对应有一个区域特性的资源文件。并且基于以此原则设计一个静态类,提供一个统一的方法用于每个页面显示文字的字符串的获取:

using System;

using System.Web.UI;

using System.Reflection;

using System.Globalization;

using System.Resources;

using System.Configuration;

namespace DeptLibrary

{

public class UIResManager

{

private static string _Namespace = "DeptLibrary.Resources";

private static CultureInfo _AppCultureInfo = null;

public static CultureInfo AppCultureInfo

{

get

{

return _AppCultureInfo;

}

set

{

_AppCultureInfo = value;

}

}

static UIResManager()

{

string strCulture = ConfigurationSettings.AppSettings["DefaultCulture"];

if( strCulture == "" )

{

_AppCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;

}

else

{

_AppCultureInfo = CultureInfo.CreateSpecificCulture(strCulture);

_Namespace = _Namespace + "." + strCulture.Replace("-", "_") + ".";

}

//_CultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;

}

public static string GetString(string key, object obj)

{

string strValue = "";

string resourceName = "";

string replaceName = "";

try

{

if(obj is System.Web.UI.Page)

replaceName = "_aspx";

else if(obj is System.Web.UI.UserControl)

replaceName = "_ascx";

else

replaceName = "";

resourceName = obj.GetType().Name.Replace(replaceName, "");

ResourceManager rm =

new ResourceManager(_Namespace + resourceName, obj.GetType().BaseType.Assembly);

System.Threading.Thread.CurrentThread.CurrentCulture = _AppCultureInfo;

strValue = rm.GetString(key, _AppCultureInfo);

}

catch(Exception ex)

{

strValue = "";

}

return strValue;

}

}

如何在运行获得当前页面对应的资源文件?前面谈到了,首先规定:以页面的文件名来命名对应的资源文件(如:Training0010.aspx对应着Trainning0010.resx),可以看到上面的静态方法GetString,第二个参数是object对象,使用传入页面当前对象(this),也就是说该方法根据传入页面的实例来获得其页面的名字。ASP.NET中的Codebehind模型使得实际运行时页面的实例是继承于Codebehind的类,也就是对应.cs文件中定义的类,运行时该页面被编译为ASP命名空间下,页面名_aspx(如果是用户控件则是:用户控件名_ascx)。这些运行时编译的类都放在临时的程序集内,从该程序集并不能访问到资源文件,所以需要使用它们的基类的程序集:

ResourceManager rm =

new ResourceManager(_Namespace + resourceName, obj.GetType().BaseType.Assembly);

如下图所示,我们必须明白在实际运行时,page对象并不是我们定义的Codebehind类的实例而其派生类的实例。

<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="[email protected]@[email protected]@[email protected]@[email protected]@5xe" filled="f" stroked="f"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 296.25pt; HEIGHT: 295.5pt" type="#_x0000_t75" alt=""><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif" o:href="ms-help://MS.MSDNQTR.2003FEB.2052/vbcon/html/vbderivedpagebaseclasstowebformoutput.gif"></imagedata></shape>

在ASP.NET 1.1 中实现全球化的应用

四. 实例

首先在Web.config中追加默认区域的设置,以保证初始启动的页面加载默认的语言环境。

<appSettings>

<add key="DefaultCulture" value="zh-cn" />

</appSettings>

当要切换语言时,通过修改UIResManager的属性AppCultureInfo并重新加载页面就可以了。

下面的代码片断说明如何调用UIResManager来进行页面表示项目的初始化:

private void InitializePageLabel()

{

this.lblSummary.Text = UIResManager.GetString("lblSummary", this);

// Table1

this.lblTab1Date.Text = UIResManager.GetString("lblDate", this);

this.lblTab1Teacher.Text = UIResManager.GetString("lblTeacher", this);

this.lblTab1Topic.Text = UIResManager.GetString("lblTopic", this);

this.lblTab1Classroom.Text = UIResManager.GetString("lblClassroom", this);

this.lblTab1Domain.Text = UIResManager.GetString("lblDomain", this);

// Table2

this.lblTab2Date.Text = UIResManager.GetString("lblDate", this);

this.lblTab2Teacher.Text = UIResManager.GetString("lblTeacher", this);

this.lblTab2Domain.Text = UIResManager.GetString("lblDomain", this);

this.lblTab2Topic.Text = UIResManager.GetString("lblTopic", this);

this.lblTab2Download.Text = UIResManager.GetString("lblDownload", this);

this.lblTab2Rate.Text = UIResManager.GetString("lblRate", this);

}

由一个DropDownList来控制语言的切换。

private void DropDownList2_SelectedIndexChanged(object sender, System.EventArgs e)

{

UIResManager.AppCultureInfo = System.Globalization.CultureInfo.CreateSpecificCulture(this.DropDownList2.SelectedValue);

this.lblDepartment.Text = UIResManager.GetString("lblDepartment", this);

}