我假设您熟悉区域性 UI 和区域性设置、CultureInfo 对象、资源文件、ResourceManager 类和自动生成的强类型化资源类。并且,我还假设您基本了解 Microsoft? .NET Framework 是从默认程序集、还是从本地化卫星程序集加载资源的确定机制。如果您需要更多有关这些主题的背景知识,可先读一下我在 2006 年 5 月发表的 Basic Instincts(英文)专栏文章,然后再继续阅读本文。
控制页面级别的区域性设置利用 ASP.NET 2.0 可轻松地逐页更改区域性设置。您只需将 UICulture 和 Culture(区域性)属性添加到 .aspx 等类似页面内的 Page 指令即可。
<%@ Page UICulture="fr" Culture="fr-BE" %>
在页面生命周期的早期,一个以这些属性设置运行的 .aspx 页,将使用合适的 CultureInfo 对象初始化当前线程的 CurrentUICulture 属性和 CurrentCulture 属性。在测试期间,如果将以上所示属性添加到一个 .aspx 文件,然后再添加一个内置的 ASP.NET Web 控件(如 Calendar(日历)控件),您会立即看到一切运转正常,如图 1 所示。左侧的 Calendar(日历)控件已呈现在区域性设置为 en-US 的页面上,而右侧的 Calendar(日历)控件则呈现在区域性设置为 fr-BE 的页面上。
图 1 本地化的日历控件
然而,在大多数生产网站中,像这样将特定区域性设置硬编码到页面中是行不通的。而使用不同语言的用户将看到同一页面的不同的本地化内容。如果您向 UICulture 和 Culture(区域性)属性都分配一个“auto”值,则 ASP.NET 将基于每个请求自动为您初始化区域性设置。
<%@ Page UICulture="auto" Culture="auto" %>
ASP.NET 通过检查浏览器发送的 HTTP 标题来初始化这些设置。通过更改 Internet Options(Internet 选项)对话框中的语言首选项设置,您可以在 Internet Explorer? 中测试页面的不同本地化版本。
通常,您会想要站点中的所有页面都符合相同的区域性设置。您可以为 UICulture 和 Culture(区域性)属性分配一个站点范围的“auto”值,这样就不必分别为每个页面进行分配了。只需将以下元素添加到位于站点的根处的 web.config 文件中即可:
除了自动设置以外,您还可以为 ASP.NET 指定一个默认区域性(如果它无法找到 HTTP 标题来确定用户的首选区域性):
使用配置文件跟踪语言首选项
虽然自动设置的确会使事情变轻松,但它对于用户来说并不总是那么方便。例如,假设某位用户更愿意阅读英语版的技术类网站和法语版的商业类网站。当他在两个站点之间来回转换时,需要不断地更改浏览器设置,他会感到非常烦恼。该用户将非常喜欢能够让其选择语言首选项的网站。
要了解如何生成能够使用户更轻松地在不同语言间来回切换的 UI 和支持性实现,可以下载本月的代码示例 - 一个名为 LitwareWebApp 的 ASP.NET 2.0 网站。它的 UI 如图 2 所示。
图 2a 英语版本
图 2b 本地化版本
LitwareWebApp 站点使用 ASP.NET 2.0 中引入的新的配置文件功能,来跟踪用户的语言首选项。您可以添加一个名为 LanguagePreference 的基于字符串的配置文件属性,通过将以下元素添加到站点的 web.config 文件,来支持匿名识别:
LitwareWebApp 站点被设计为具有“母版页”内的标准布局,该“母版页”包含一个名为 lstLanguage 的 RadioButtonList 控件。请注意,该控件不仅显示友好的语言名称(如美国英语和比利时法语),而且还会使用 SelectedValue 属性跟踪真正的区域性名称(如 en-US 和 fr-BE)。当用户更改语言首选项时,lstLanguage 控件的 SelectedIndexChanged 事件会引发并执行以下代码来更新 LanguagePreference 配置文件属性:
如果调用 Response.Redirect,则会强制发生一次从浏览器到 Web 服务器的新往返程,从而在使用所需的语言首选项设置了配置文件属性后,重新启动页面的生命周期。
之后要处理的事情是以编程方式,于合适的时间在页面生命周期内调整区域性设置。在 ASP.NET 2.0 中执行此操作的正确方法是替换名为 InitializeCulture 的 Page 类。页面生命周期已被设计为,在页面本身或其任何子控件使用本地化资源进行任何工作前,始终调用 InitializeCulture.
示例站点的设计要求您向站点内的各页面添加一个替换的 InitializeCulture 实现。不幸的是,您无法在“母版页”级别上替换 InitializeCulture 方法,因为 MasterPage 类不会从 Page 类继承。另外,为网站内的各页面分别替换 InitializeCulture 方法非常冗长乏味。会导致许多重复实现,从而引发严重的维护问题。
一个用于初始化区域性设置的站点范围的更有效方法是,创建一个公共的 Page 派生基类,然后让所有 .aspx 页文件从该类继承,我在 LitwareWebApp 示例站点中就是这么做的。我的名为 LitwarePage(请参阅图 3)的 Page 派生基类是在名为 LitwarePage 的源文件中定义的,并且已被添加到 App_Code 目录,这样一来它就可以由 ASP.NET 自动编译并可用于当前网站中的其他代码。
创建了 Page 派生基类后,即可将 .aspx 页定义更新为从该类而不是从标准 Page 类派生。例如,您可以在 default.aspx.vb 内对局部类进行此类修改。
Partial Class _Default : Inherits LitwarePage
''*** 页面类定义位于此处
End Class
此时,我拥有一个可以跟踪用户语言首选项并可基于每个请求初始化区域性设置的网站。现在,我必须使用资源文件为 ASP.NET 2.0 网站本地化字符串文字,以便能够满足使用不同语言的用户的需求。
ASP.NET 2.0 中的资源文件
由于在默认情况下,Visual Studio? 2005 不使用项目来管理 ASP.NET 2.0 网站,所以将不会存在项目级别的资源文件,就像 Windows Forms 应用程序或类库 DLL 中那样。相反,您必须显式创建资源文件并将其添加到您的网站。而且,您还必须使用随 ASP.NET 2.0 引入的特殊文件夹:包含全局资源的资源文件应被添加到 App_GlobalResources 文件夹,而特定于某个文件的本地资源则应被添加到 App_LocalResources 文件夹。全局资源就是那些来自于页面及其他文件(如站点图)、可以在站点范围基础上使用的资源。支持本地资源的 ASP.NET 文件类型包含页(。aspx 文件)、母版页(。master 文件)和用户控件(。ascx 文件)。
不同于 ASP.NET 2.0 的另一点是,您不必像在开发国际化的 Windows Forms 应用程序时那样提前编译资源。相反,ASP.NET 运行时会准时将全局和本地资源文件编译到 DLL 中,就像 .aspx 文件那样。这是一种强大的功能,因为公司只需将 .resx 文件 XCOPY 到一个 Web 生产服务器上,即可为新语言添加本地化支持。
让我们在使用 Visual Studio 2005 的 ASP.NET 2.0 站点中,来完成一个创建和使用全局资源文件的示例。您可以先选择 Add New Item(添加新项)命令,然后再选择 Resource File(资源文件),来创建一个新的全局资源文件。
当您单击 Add(添加)按钮创建一个新的全局资源文件时,Visual Studio 2005 会用一个对话框对您进行提示,建议您将新的资源文件放置在 App_GlobalResources 目录内。单击 Yes(是)。如果您将其置于他处,则 ASP.NET 便不会将资源文件自动编译到 DLL 中。
在 ASP.NET 中使用资源文件与在 Windows Forms 应用程序中相同。首先创建一个资源文件,其字符串文字已本地化为默认区域性设置。在我们的示例网站中,有一个用于该目的的全局资源文件,该文件的名称为 Litware.resx,如图 4 所示。在您添加了所有采用默认区域性设置的指定字符串后,即可复制该资源文件并将其重命名,例如重命名为 Litware.fr.resx 以提供法语的本地化字符串。您还可以复制该法语资源文件并将其重命名为 Litware.fr-BE.resx,以维护已局部本地化为比利时法语的字符串。
图 4 本地化资源
在资源文件中添加和维护指定字符串非常容易,因为 Visual Studio 2005 提供了方便易用的资源编辑器,如图 5 所示。请记住,资源文件并不仅限于本地化字符串。您可以添加其他类型的资源,如图像文件、级联样式表和客户端 JavaScript 文件。
图 5 Visual Studio 2005 资源编辑器
现在,我们来创建从全局资源文件检索指定字符串的页面。这做起来非常容易,就像是在开发一个国际化的 Windows Forms 应用程序时,无需直接对 .NET 提供的 ResourceManager 类进行编程。因为 ASP.NET 和 Visual Studio 2005 可以在后台为每个全局资源文件生成一个强类型化资源类,并通过 IntelliSense? 使其变为可用。
可通过一个驻留在名为 Resources(资源)的顶级命名空间内的强类型化类,来访问您添加到全局资源文件中的指定字符串。它使用一行代码,将一个本地化的字符串分配给控件的属性值:
lblApplicationName.Text = Resources.Litware.ApplicationName
除了以编程的方式访问外,ASP.NET 2.0 还引入声明性语法,您可以使用它将指定的字符串绑定到页或控件的属性。该语法涉及使用美元符号 ($),其后紧跟资源命名空间、资源文件名和字符串名:
<%$ Resources:Litware, ApplicationName %>
例如,如果您想将名为 ApplicationName 的字符串绑定到 .aspx 页内一个标签的 Text 属性,您可以像这样编写标记:
Visual Studio 2005 还提供一个名为 Expression Builder 的方便易用的工具,如图 6 所示。该实用程序可帮助您生成将资源文件中指定字符串绑定到控件或页属性时所需的语法。在您用指定的字符串添加了一个或多个全局资源文件后,即可通过将 .aspx 页置于设计视图,并通过 Property(属性)表访问 Expressions 属性,来访问 Expression Builder.
图 6 Expression Builder
请注意,声明性资源绑定表达式不仅限于 .aspx 文件、。ascx 文件和 .master 文件。也可用于本地化 Web.sitemap 文件中定义的站点图中的字符串文字。图 7 显示来自 LitwareWebApp 网站中站点图的 XML,它用于本地化该站点导航菜单中显示的链接标题。
使用本地资源
本地资源文件包含用于站点内基于文件的单独项的资源,如页、母版页或用户控件。每个本地资源文件都必须正确命名并添加至 App_LocalResources 文件夹,以由 ASP.NET 进行编译。
本地资源文件的命名,应与它要为之提供资源的基于文件的项一致。例如,包含用于 AddCustomer.aspx 页的默认区域性资源的本地资源文件,应命名为 AddCustomer.aspx.resx.包含法语资源的本地资源文件应命名为 AddCustomer.aspx.fr.resx.
在您将指定的字符串添加到本地资源文件后,即可通过三种方式从页面或用户控件内访问它们。第一,您可以通过编程方式进行访问。第二,您可以使用显式语法,以声明的方式绑定到其上。第三,您可以使用隐式语法,以声明的方式绑定到其上。下面开始逐个探讨这些方法。
假设您已创建了一个本地资源文件,来本地化页面 AddCustomer.aspx 上显示的所有控件标题。要本地化该页提交按钮上显示的标题,您可以创建一个名为 btnSubmit.Text 的本地化字符串。在将该指定字符串添加到本地资源文件后,您即可通过以下调用 GetLocalResourceObject 方法和将返回值转换为字符串的代码来进行访问:
''*** AddCustomer.aspx.vb 内的代码
btnSubmit.Text = _
Me.GetLocalResourceObject("btnSubmit.Text").ToString()
此代码不如以前显示的代码好,以前显示的代码是从使用强类型化类的全局资源访问指定的字符串。本地资源文件没有相关联的强类型化类,所以您无法从 IntelliSense 中获益,而且必须在调用 GetLocalResourceObject 时,显式地转换基于对象的返回值。
如果您要使用显式声明性绑定语法,则其使用方式与使用全局资源时大致相同。唯一不同的是您使用本地资源时,可以忽略资源文件的名称:
Text="<%$ Resources:btnSubmit.Text %>" />
隐式声明性绑定语法是功能最强大的选项。首先将名为 meta:resourcekey 的特殊属性添加到控制标记,或添加到一个 ASP.NET 指令,如 Page、Master 或 Control.例如,如果您想通过 .aspx 文件中的 Button(按钮)控件来使用隐式声明性绑定语法,您可以像这样编写标记:
meta:resourcekey="btnSubmit" />
在您添加了 meta:resourcekey 属性后,就只剩一件事需要考虑了,即确保本地资源文件中的字符串具有正确的名称。在我的示例中,ASP.NET 会自动加载名为 btnSubmit.Text 的本地化字符串,并将其分配给名为 btnSubmit 的控件的 Text 属性。
关键在于,隐式绑定的基础是,创建的字符串应具有与 meta:resourcekey 属性定义的目标和属性的名称相匹配的名称。本示例中,由于 meta:resourcekey 是面向 btnSubmit 的,所以只需将更多指定的字符串添加到本地资源文件中,这样您不仅可以绑定到 Text,而且还可以绑定到其他几个属性值,如图 8 所示。
图 8 添加指定的字符串
请注意,Visual Studio 2005 可以在设计视图编辑器中打开页、用户控件或母版页时,在 Tools(工具)菜单中提供一个名为 Generate Local Resource(生成本地资源)的方便易用的命令。该命令可自动创建默认区域性的本地资源文件。还可在页面中添加 meta:resourcekey 属性,并在本地资源文件中创建相应的字符串值,来充当 meta:resourcekey 属性项目的目标。
最后,请注意:有一个名为 Localize(本地化)控件的 ASP.NET 2.0 新组件,可以使您本地化 .aspx 页上的任何元素。它提供一种不由其基类提供的设计时间功能:Literal(文字)控件;尤其是,Localize(本地化)控件提供了静态内容的设计时间编辑,以便您能够在页面设计模式下工作时查看默认值。
在 DLL 项目中嵌入资源
我将暂时撇开国际化和本地化主题,先讨论一种在类库 DLL 中使用嵌入资源的新 ASP.NET 技术。该技术允许您在 DLL 中嵌入图像文件、级联样式表文件和 JavaScript 文件,并通过 DLL 在托管 Web 服务器上加以提供。
请注意,该技术需要使用一个面向 ASP.NET 2.0 网站的类库 DLL.这一新功能是由 ASP.NET 团队特别添加的,目的是为服务器端的控件创建者提供一种更好的方式,使他们可以在分配自定义控件和 Web 部件的同时分配资源文件。不必将资源文件与 DLL 一起分配,也不必确保他们被复制到托管 Web 服务器上一个可访问的路径,资源文件现在可以在 DLL 内自行分配,并可通过运行时由 ASP.NET 生成的 URL 加以提供。
LitwareWebApp 网站包含一个名为 LitwareWebComponents 的类库 DLL 项目,该项目演示了这一技术。在该项目内,有一个名为 LitwareSlogan.png 的图像文件已被作为资源嵌入。您可以通过将文件的“生成操作”更改为“嵌入的资源”,将资源嵌入到一个程序集中,如图 9 所示。
图 9 嵌入资源
要提供对 DLL 内一个嵌入的资源文件的基于 Web 的访问,您必须添加一个名为 WebResource 的程序集级别的属性。当您添加 WebResource 属性时,必须包含资源文件的限定名及其 MIME 类型。在 Visual Basic? 类库 DLL 项目中,限定资源文件名包含项目名。
''*** 在 AssemblyInfo.vb 内
Imports System.Web.UI
"LitwareWebComponents.LitwareSlogan.png", "image/png")>
WebResource 属性允许您为 ASP.NET 运行时提供所需的元数据,以通过使用可在运行时生成的 URL,从 DLL 中检索资源文件。要从服务器端控件内的代码生成资源文件的 URL,您可以调用一个名为 GetWebResourceUrl 的方法,如图 10 所示。
这是使该技术得以运行的后台情况。一个对 GetWebResourceUrl 的调用生成一个指向名为 WebResource.axd 的内置 HTTP 处理程序的 URL.这个动态生成的 URL 还包含一个查询字符串,来识别目标 DLL 的名称和嵌入的资源文件。通过加载一个名为 AssemblyResourceLoader 的自定义 HttpHandler 类,ASP.NET 运行时可以响应 WebResource.axd 的请求。
当调用 AssemblyResourceLoader 类以从 DLL 加载资源文件时,它可以读取由 WebResource 属性提供的元数据。AssemblyResourceLoader 类已被实现以从 DLL 的图像中提取请求资源文件,并将其引流回调用程序。AssemblyResourceLoader 类甚至提供缓存算法,可在它被加载到前端 Web 宿主内存后,在多个请求中重复使用同一资源文件。
显示本地化图像
虽然使用嵌入的资源文件和 WebResource 属性会具有强大的功能,但是仍然存在一些明显的局限性。首先,您只能在面向 ASP.NET 2.0 网站的 DLL 项目内使用该技术。您无法在 ASP.NET 2.0 网站内直接使用该技术。第二,该技术实际上并不支持任何形式的本地化。如果您的网站具有诸如图形图像和级联样式表等已经本地化的资源文件,则您将不得不采用其他方法。
LitwareWebApp 网站显示一个名为 LitwareSlogan.png 的图形图像。该网站可依据当前用户更喜欢英语还是法语来显示不同版本的图像。尽管 ASP.NET 2.0 不直接支持本地化图像文件,但它也不需要过多的自定义代码来完成所需的效果。
您可以将本地化版本的图像文件添加到本地化版本的全局资源文件,并以此作为开始。例如,英语版本的 LitwareSlogan.png 已被添加到名为 Litware.resx 的全局资源文件,而法语版本的 LitwareSlogan.fr.png 则已添加到 Litware.fr.resx.这两个资源文件中的资源拥有一个相同的名称:LitwareSlogan.
当不同本地化版本的全局资源文件中含有本地化版本的图像文件时,您可以使用名为 LitwareSlogan.ashx 的自定义处理程序文件,基于用户的语言首选项来有条件地进行加载,如图 11 所示。
LitwareSlogan.ashx 中定义的自定义处理程序类可使用您以前在自定义 InitializeCulture 方法中看到的类似逻辑,在从全局资源文件中检索图像文件以前,初始化当前线程的 CurrentUICulture 设置。您可能会注意到,要加载正确的资源文件,您必须初始化当前线程的 CurrentUICulture 属性,但不必初始化 CurrentCulture 属性。
在该自定义处理程序正确初始化了 CurrentUICulture 设置之后,它即可通过 Litware.resx 的强类型化资源类来访问图像文件。然后,便只需将图像文件的数位编写到 HTTP 响应流。显示本地化图像的最后步骤是,将 LitwareSlogan.ashx URL 分配到站点内任何页面上一个图像控件的 ImageUrl 属性。
总结
ASP.NET 2.0 使国际化网站和资源变得更加容易。通过检查浏览器发送的 HTTP 标题,可在网站内轻松地初始化页面的区域性设置。而且,还可轻松地设计更加复杂的机制,使用户能够通过配置所需的语言首选项来个性化其体验。
请将您要提交给 Ted 的问题和意见发送至 instinct@microsoft.com.
Ted Pattison 身为作者兼培训人员的 Ted Pattison 最近成立了 Gorilla Training,该公司致力于提供有关 SharePoint 技术的、极具实力的开发人员培训。Ted 还在为 Microsoft Press 编写一本名为 Inside Windows SharePoint Services 3.0 的书。