和ASP.NET AJAX应用程序环游地球
2008-04-17 10:46 Valens 阅读(414) 评论(0) 编辑 收藏 举报By Guy Smith-Ferrier
本文讨论:
|
本文使用了以下技术: ASP.NET AJAX |
目录
事实上,ASP.NET 2.0 为 Web 应用程序提供了出色的本地化模型,而且在 Microsoft® .NET Framework 中也包括了大量的全球化支持。但是,这种支持仅限于服务器端代码。ASP.NET AJAX 应用程序包括大量的客户端代码,而标准的 ASP.NET 本地化模型并不包括这些内容。在这里,我将对 ASP.NET AJAX 应用程序的本地化和全球化模型进行介绍,并演示如何本地化 JavaScript 资源和增加有限的全球化支持。并且我假设您对 ASP.NET 和 ASP.NET AJAX 以及国际化 ASP.NET 2.0 应用程序都有基本的了解。
基于程序集的本地化模型
Microsoft ASP.NET AJAX 框架包括两个可用于本地化 JavaScript 资源的模型:一个是基于程序集的,另一个是基于文件的。我们首先来了解基于程序集的模型。
基于程序集的模型将使用您在任何 ASP.NET 或 Windows® Forms 应用程序中使用的资源,即常规的 .NET Framework .resx 文件。这些资源都嵌入在程序集中,最终使用 ResourceManager 类进行检索。我将通过一个示例来解释这个过程:一个可调用 JavaScript 函数的按钮,该函数用警告的方式向用户显示一个可本地化的消息。如果打算同步进行,并自己创建示例,不妨使用这里指定的名称;否则,当您的示例无法得出正确结果时,将很难找到问题的根源。
首先,我们将创建一个启用了 AJAX 的 Web 应用程序项目(“文件”|“新建”|“项目”|“启用了 ASP.NET AJAX 的 Web 应用程序”)。请注意,您需要将 Visual Studio® 2005 SP1 用作此项目的模板(不能使用 AJAX 网站项目作为该示例的替代,因为无法对生成程序集进行确切的访问)。调用项目 AJAXEnabledWebApplicationI18N1(I18N 是“Internationalization”的缩写,因为“Internationalization”以“I”开头,中间有 18 个字母,末尾字母为“N”)。向窗体中添加一个按钮,然后再添加一个 OnClientClick 事件,就可以调用名为 displayAlert 的 JavaScript 函数:
<asp:Button ID="Button1" runat="server" OnClientClick="return displayAlert();" Text="Button"/>
现在,添加一个名为 DisplayFunctions.js 的 JScript 文件,如下所示:
function displayAlert()
{
window.alert(DisplayFunctionsResources.Greeting);
return false;
}
{
window.alert(DisplayFunctionsResources.Greeting);
return false;
}
displayAlert 函数使用 window.alert 显示 DisplayFunctionsResources.Greeting。请注意,DisplayFunctionsResources.Greeting 尚不存在,但这是可本地化的资源,我引用它就好像它是一个强类型化的资源类。我将在稍后讨论这一想法。使用“属性”窗口将 DisplayFunctions.js 文件的“生成操作”设为“嵌入的资源”。此操作即是告知编译器将 DisplayFunctions.js 作为资源嵌入到生成的 AJAXEnabledWebApplicationI18N1.dll 程序集中。
接下来,我们希望用户能够轻松地设置 Web 应用程序所使用的区域,所以将 Culture(区域性)和 UICulture 属性添加到页面页眉中,并将它们设置为 auto。当然,如果您使用“工具”|“生成本地资源”,将会自动添加这些页面属性,就像在本地化一个 ASP.NET 程序时所做的那样;但是在本示例中,我将手动添加,因为我想澄清一点,即 ASP.NET AJAX 的本地化模型并不要求您也使用 ASP.NET 的本地化模型。
现在添加一个资源文件,将其命名为 DisplayFunctionsResources.resx。再添加一个名为 Greeting 的资源条目,并且赋值为“Hello World”。现在,您应该能够了解 DisplayFunctions.js 中 DisplayFunctionsResources.Greeting 的含义了。现在添加一个名为 DisplayFunctionsResources.fr.resx 的资源文件,再添加一个名为 Greeting 的资源条目,赋值为“Bonjour Le Monde”。该资源文件在 .resx 文件扩展名之前使用了标准的包括区域名(此处 fr 表示法国)的 .NET Framework .resx 文件命名约定。
我稍后将会解释它是如何工作的,但是在这之前,您只需相信我就对了。现在,您需要了解的是:可通过使用 WebResource 和 ScriptResource HTTP 处理程序将 Web 应用程序中的资源(DisplayFunctions.js、DisplayFunctionsResources.resx 和 DisplayFunctionsResources.fr.resx)用于页面。这些处理程序不会自动使所有资源可用;相反,它们将查找程序集属性,这些属性标记允许公开的资源。打开项目的 AssemblyInfo.cs 文件,并将这些程序集属性添加到该文件:
若要向页面公开这些资源,您需要将 ASP.NET AJAX ScriptManager 添加到该页面,并对其进行配置以生成指向可本地化资源的链接。可通过将 ScriptManager 的 EnableScriptLocalization 属性设置为 True 来完成此操作。此外,您还需要指出将 ScriptReference 添加到 ScriptManager 后页面可引用哪些资源。完整的 ScriptManager 元素如下所示:
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnableScriptLocalization="true">
<Scripts>
<asp:ScriptReference
Name="AJAXEnabledWebApplicationI18N1.DisplayFunctions.js"
Assembly="AJAXEnabledWebApplicationI18N1" />
</Scripts>
</asp:ScriptManager>
ScriptReference 包括 JavaScript 资源的名称和对资源程序集的引用。
最后,您可以运行已完成的 Web 应用程序。单击该按钮,将会看到英语资源(“Hello World”),假设您系统的默认语言已设置为英语。若要查看 Internet Explorer® 中的法语资源,请打开“工具”|“Internet 选项”,依次单击“语言”按钮和“添加”按钮,选择“法语(法国)”,单击两次“确定”,然后刷新页面,再单击应用程序中的按钮,此时将显示法语资源(“Bonjour Le Monde”)。
工作原理
现在已经有了一个可运行的本地化 ASP.NET AJAX 应用程序,但是您实际上并不了解该架构是如何使其运行的。在这部分里,我将进行系统介绍。我们先从 web.config 开始。请看一下 HTTP 处理程序部分,您将会找到 ScriptResource 处理程序:
<add verb="GET,HEAD" path="ScriptResource.axd"
type="System.Web.Handlers.ScriptResourceHandler,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
validate="false"/>
type="System.Web.Handlers.ScriptResourceHandler,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
validate="false"/>
当您将 EnableScriptLocalization="true" 添加到 ScriptManager 时,将会在页面中引用 ScriptResourceHandler。如果继续操作,在新创建的、未经更改的启用了 ASP.NET AJAX 的 Web 应用程序中启用脚本本地化,然后查看源 HTML,您将会看到图 1 中显示的行。
Figure 1 WebResource 和 ScriptResource 参考
<script src="/WebResource.axd?d=wLnl3WuFAiWB3W8aelty5A2&
t=632969072944906146" type="text/javascript">
</script>
<script src="/ScriptResource.axd?d=
ekhhG997JkU79ensgQhEZepkzUh3YLeLuz9-kWs0_QimDVEmoCSwcFNOHayaCQX6y2v7StlLcqRUUHTtwG4zJloCtMocAEx2dXPoMNyO-AE1&
t=633132543078401746" type="text/javascript">
</script>
<script src="/ScriptResource.axd?d=
ekhhG997JkU79ensgQhEZepkzUh3YLeLuz9-kWs0_QimDVEmoCSwcFNOHayaCQX6y2v7StlLcqRUUHTtwG4zJjtbgQARGVzU-hP34jwIti3qwKit4BmBA5oDqgYMBzoU0&
t=633132543078401746" type="text/javascript">
</script>
t=632969072944906146" type="text/javascript">
</script>
<script src="/ScriptResource.axd?d=
ekhhG997JkU79ensgQhEZepkzUh3YLeLuz9-kWs0_QimDVEmoCSwcFNOHayaCQX6y2v7StlLcqRUUHTtwG4zJloCtMocAEx2dXPoMNyO-AE1&
t=633132543078401746" type="text/javascript">
</script>
<script src="/ScriptResource.axd?d=
ekhhG997JkU79ensgQhEZepkzUh3YLeLuz9-kWs0_QimDVEmoCSwcFNOHayaCQX6y2v7StlLcqRUUHTtwG4zJjtbgQARGVzU-hP34jwIti3qwKit4BmBA5oDqgYMBzoU0&
t=633132543078401746" type="text/javascript">
</script>
无论您是否已启用脚本本地化,WebResource 引用都将会出现,这是由于所有的处理程序都使用了相同的参数,我们将从这里开始。
理解 ASP.NET AJAX 工作原理的关键在于理解参数。在图 1 中,您可以看到两个参数:d 和 t。参数 d 表示数据,而参数 t 是日期/时间戳。参数 t 只是包含资源的程序集的 DateTime(以 ticks 表示)。值 632969072944906146 表示 2006 年 10 月 20 日 02 点 14 分 54 秒,这是 System.Web.dll 的日期/时间戳。此参数的优点在于,只要 URI 不发生变化,由 HTTP 处理程序返回的值都将由该浏览器缓存。这意味着,如果处理资源的程序集发生变化,其日期/时间戳也将发生变化,并且 ScriptManager 将生成其他的 URI(因为参数 t 将出现不同的值),缓存的值也将被替换。
而参数 d 已被加密,因此用肉眼无法察觉其变化。但是,System.Web.UI.Page 类有内部静态方法——DecryptString 和 EncryptString,可使 Page 加密和解密这些字符串。在图 1 中,WebResource 中参数 d (wLnl3WuFAiWB3W8aelty5A2) 的解密版本为 s|WebForms.js。竖线只是用于分隔字符串中不同的值。“s”表示该数据为脚本,“WebForms.js”是要检索的资源名称。WebForms.js 资源可从 System.Web.dll 检索。
ScriptResource 参数 d 增加了阅读的趣味性。在图 1 中显示的参数 d 是以下字符串
ZSystem.Web.Extensions,1.0.61025.0,31bf3856ad364e35|MicrosoftAjax.js|en
和以下字符串的加密版本:
ZSystem.Web.Extensions,1.0.61025.0,31bf3856ad364e35|
MicrosoftAjaxWebForms.js|en
MicrosoftAjaxWebForms.js|en
第一个字符 (Z) 表示应该以 GZip 压缩格式返回结果。下一个参数是完全限定的程序集名称,可以在这里找到资源(在此示例中为 ASP.NET AJAX System.Web.Extensions.dll)。倒数第二个参数是要检索的资源名称(在这里为 MicrosoftAjax.js),最后一个参数是应该检索的区域(在这里 en 表示英语)。
如果您查看示例应用程序的 HTML 源代码,将能看到第三个 ScriptResource,它是作为 ScriptManager 中包括 ScriptReference 元素的结果生成的。如下所示:
此参数 d 的解密版本为 ZAJAXEnabledWebApplicationI18N1|AJAXEnabledWebApplicationI18N1.DisplayFunctions.js|。它指示 ScriptResource HTTP 处理程序应该从 AJAXEnabledWebApplicationI18N1 程序集中检索 AJAXEnabledWebApplicationI18N1.DisplayFunctions.js 资源。回忆一下,我们曾将此资源的“生成操作”设置为嵌入的资源,因此它会将资源嵌入到一个程序集中。另外值得注意的一点是,区域(在已解密的参数 d 的末尾)为空。当浏览器的语言设置为 fr 后,参数 d 中的区域值也将设置为 fr。
如果在参数 d 的开始就考虑了 Z 值,则其他问题也会迎刃而解。与表示压缩的 Z 不同,您可以查找一个 u,它表示应该以未压缩的格式返回结果。所以,如果您将此值更改为 u,则会重新生成该参数 (uAJAXEnabledWebApplicationI18N1|AJAXEnabledWebApplicationI18N1.DisplayFunctions.js|),并对其加密,然后直接调用 ScriptResource HTTP 处理程序,如下所示:
http://localhost:49573/ScriptResource.axd?d=t9iZ-3-wfmF_qcNwBHaQrktF2T3rVGzj376NN7kvehJFET0JsrpDTsYt9i
KmZpSmrU1GjtTCo9PIQmuAmMNNkN2Yc_ipVH7JL-jt_hmL9_v8
HtplfhERvz-WsOUxZ-1k0&t=633137261664170000
KmZpSmrU1GjtTCo9PIQmuAmMNNkN2Yc_ipVH7JL-jt_hmL9_v8
HtplfhERvz-WsOUxZ-1k0&t=633137261664170000
这时您可以准确地看到处理程序所返回的内容。在这里为未压缩的、可识别的结果:
// JScript File
function displayAlert()
{
window.alert(DisplayFunctionsResources.Greeting);
return false;
}
DisplayFunctionsResources={
"Greeting":"Hello World"
};
function displayAlert()
{
window.alert(DisplayFunctionsResources.Greeting);
return false;
}
DisplayFunctionsResources={
"Greeting":"Hello World"
};
您可以看到,这是一个原始 DisplayFunctions.js 文件和 DisplayFunctionsResources.resx 的 JavaScript 序列化的合并。此合并将按照 ScriptResource HTTP 处理程序的请求进行序列化。请注意,JavaScript 类的名称为 DisplayFunctionsResources。一开始您可能会认为这是对应的 .resx 文件的名称。其实,这正是我精心设计的巧合。请回忆上述 ScriptResource 程序集引用:
[assembly: System.Web.UI.ScriptResource
("AJAXEnabledWebApplicationI18N1.DisplayFunctions.js", "AJAXEnabledWebApplicationI18N1.DisplayFunctionsResources", "DisplayFunctionsResources")]
("AJAXEnabledWebApplicationI18N1.DisplayFunctions.js", "AJAXEnabledWebApplicationI18N1.DisplayFunctionsResources", "DisplayFunctionsResources")]
最后一个参数 (DisplayFunctionsResources) 是 ScriptResource HTTP 处理程序生成的 JavaScript 类的名称。我之所以选择这个名称,是因为我觉得类和文件名使用相同的名称将会很有帮助,因为这是强类型化资源类所使用的规则。但是,如果您喜欢,可以以不同的名字命名它们。
当您继续操作,将区域切换为 fr 后,ScriptResource HTTP 处理程序将使用 DisplayFunctionResources.fr.resx 资源返回一个合并(承蒙自从发布 .NET Framework 1.0 以来一直存在的 ResourceManager 行为):
// JScript File
function displayAlert()
{
window.alert(DisplayFunctionsResources.Greeting);
return false;
}
DisplayFunctionsResources={
"Greeting":"Bonjour Le Monde"
};
基于文件的本地化模型
在两个 AJAX 本地化模型中,另外一个就是我称为基于文件的模型。也就是,JavaScript 文件没有嵌入到程序集中。正相反,它们以原样返回到客户端。很明显,这种模型适用于无法轻松识别的程序集的项目类型,但是它们还是有很多其他优点。我们来看一个示例。
创建一个启用了 AJAX 的网站(“文件”|“新建”|“网站...”|“启用了 ASP.NET AJAX 的网站)。您可以随意命名,在这里名称没有任何目的性。按照在第一个练习中的操作步骤添加一个相同的按钮:
<asp:Button ID="Button1" runat="server" OnClientClick="return displayAlert();" Text="Button"/>
然后,添加一个名为 DisplayFunctions.js 的 JScript 文件,其中包含此处显示的代码:
function displayAlert()
{
window.alert("Hello World");
return false;
}
请注意,在此示例中,很明显文本是硬编码的,无法以任何方式本地化。
现在,创建第二个 JScript 文件,并将其命名为 DisplayFunctions.fr.js,如下所示:
function displayAlert()
{
window.alert("Bonjour Le Monde");
return false;
}
和在第一个示例中一样,将 Culture(区域性)和 UICulture 属性添加到页面页眉中,并赋值为 auto。最后,将 EnableScriptLocalization="true" 设置为 ScriptManager,并将 ScriptReference 添加到 DisplayFunctions.js 文件中:
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnableScriptLocalization="true">
<Scripts>
<asp:ScriptReference Path="DisplayFunctions.js"
ResourceUICultures="fr"/>
</Scripts>
</asp:ScriptManager>
ScriptReference 使用 Path 和 ResourceUICultures 属性,而不是在基于程序集的模型中使用的 Name 和 Assembly 属性。如果您现在运行网站,并在无语言和法语之间切换,将能看到该按钮生成的已正确本地化的消息。
深入了解基于文件的模型
毫无疑问,基于文件的模型不同于基于程序集的模型。如果您在 Internet Explorer 中右键单击页面,然后选择“查看源文件”,将能看到此处生成的脚本标记不代表任何语言:
<script src="DisplayFunctions.js" type="text/javascript"></script>
这是代表法语的脚本标记:
<script src="DisplayFunctions.fr.js" type="text/javascript"></script>
所以,第一个区别就是,基于文件的模型具有独立的、完全自我包含的 .js 文件,可用于每个区域。不存在包含随后转换为 JavaScript 类的资源的 .resx 文件。每个 .js 文件都包含逻辑和资源。这种方法既有利也有弊:好处在于,它可使您轻松地为每个区域定制逻辑;坏处是,如果您不想定制逻辑,则需要复制每个区域的逻辑,才能更改其资源。维护起来非常困难。
让我们返回到您之前看到的 ScriptReference:
Path 属性引用 JavaScript 文件 (DisplayFunctions.js) 的名称。ResourceUICultures 属性列出了唯一脚本存在的区域,这是必需的。您可能会争辩,ScriptResource HTTP 处理程序能够轻松地从文件目录中找到答案,因此该属性是多余的。但是,为了提升性能,静态脚本文件将由 IIS 直接处理,所以 ScriptResource 处理程序永远没有机会评估该请求。鉴于此过程,该列表必须是硬编码的。
根据到现在为止我所介绍的内容,基于文件的模型可能存在不足,但是通过大量重组,它将会更加有用。我们曾在基于程序集的模型中介绍过,由 ScriptResource HTTP 处理程序返回到客户端的 JavaScript 是原始 JavaScript 文件和表示来自 .resx 文件资源的 JavaScript 类的合并。同样的方法也适用于基于文件的模型。与之前记录的函数不同,这里应该记为
function displayAlert()
{
window.alert("Hello World");
return false;
}
和:
function displayAlert()
{
window.alert("Bonjour Le Monde");
return false;
}
可以重构此代码,以便逻辑仍可用于每个区域,而资源则移到 JavaScript 文件的后半部分。尝试以下代码
function displayAlert()
{
window.alert(DisplayFunctionsResources.Greeting);
}
DisplayFunctionsResources={
'Greeting':'Hello World'
}
和:
function displayAlert()
{
window.alert(DisplayFunctionsResources.Greeting);
}
DisplayFunctionsResources={
'Greeting':'Bonjour Le Monde'
}
这样当然会改善维护过程,因为尽管仍必须对每个区域采用相同的更改,但是由于该文件的上半部分仍可用于每个区域,所以这个过程变得容易些了。
现在我们来考虑进一步的修改。可以在单独的公用 JavaScript 文件(如 DisplayFunctions.Common.js)中保存此逻辑,并按照 ScriptResource HTTP 处理程序对基于程序集模型所采取的相同方法,在生成时使用自定义生成任务从相应的 .resx 文件生成本地化版本。我将这个问题留给读者作为练习之用。
选择一个本地化模型
那么,现在您应该对这两种模型都有了一定的了解,很明显的问题就是您应该使用哪一种模型?图 2 提供了一个比较。
Figure 2 File-Based versus Assembly-Based Localization
基于文件模型的最大优点在于,您可以轻松地自定义客户端的逻辑,并将其与网站项目一起使用。而另一方面,使用基于程序集的模型,您可以轻松地利用本地化资源,而且 JavaScript 文件也可以嵌入到程序集中。如果将此模型与控件库一起使用,将会比较理想。哪种模型更适合您可能更多地取决于您的工作性质。由于我使用的是 Visual Studio 2005 SP1,并且不需要经常为不同的区域自定义逻辑,所以基于程序集的模型比较适合我。
本地化 ASP.NET AJAX 框架
.NET Framework 本身就能够在本地化版本——.NET Framework 语言包中使用。安装时,.NET Framework 将会利用这些语言包公开本地化的资源,如错误消息和对话框。ASP.NET AJAX 框架本身可利用必须能够在客户端使用的资源,如错误消息。当 Microsoft 外部的资源不能创建新的 .NET Framework 语言包时,任何人都可以创建 ASP.NET 框架的本地化版本。现在,只有英语版本的 ASP.NET AJAX 框架,而只有当其成为 Visual Studio 2008 的一部分后,该框架的本地化版本才可用。但是,本地化支持将仅限于 .NET Framework 支持的语言子集,所以想要获得 ASP.NET AJAX 框架的威尔士语本地化的希望是非常渺茫的。因此,我们仍需要一种能够本地化 ASP.NET AJAX 框架的方法。
幸运的是,这种方法已经存在,并且是在我们已经讨论过的基于文件的本地化模型的基础上构建的。ASP.NET AJAX 程序集包含英语资源。如果您使用 Reflector 打开了 System.Web.Extensions.dll(请参见图 3),将能看到默认使用的 JavaScript 资源。
0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc135974.fig03.gif">
Figure 3 Using Reflector to View the System.Web.Extensions.dll's JavaScript Resources (单击该图像获得较大视图)
然而,您只需为相同的 JavaScript 资源名称添加 ScriptReference,就可以构建 ScriptManager 以使用不同的 JavaScript 文件集,如下所示:
<asp:ScriptManager ID="ScriptManager1" runat="server"
ScriptMode="Debug" EnableScriptLocalization="true">
<Scripts>
<asp:ScriptReference Name="MicrosoftAjax.js"
Path="~/AjaxFramework/MicrosoftAjax.js"
ResourceUICultures="fr" />
</Scripts>
</asp:ScriptManager>
若要在操作中看到此过程,需要创建一个新的启用了 AJAX 的 Web 应用程序项目,将 ScriptManager 更改为以上显示的内容,添加名为 AjaxFramework 的文件夹,然后将 MicrosoftAjax.Debug.js 文件从 ASP.NET AJAX MicrosoftAjaxLibrary\System.Web.Extensions\1.061025.0 文件夹复制到 AjaxFramework 文件夹。现在,将 MicrosoftAjax.Debug.js 复制到 MicrosoftAjax.Debug.fr.js,以创建该框架的法语本地化。打开 MicrosoftAjax.Debug.fr.js,转到 Sys.Res 所在文件的底部,找到 argumentType 字段,并将它的值更改为“L\' objet ne peut pas être converti le type demandé”。(这是“对象无法转换为所需类型”的法语翻译。)
向窗体添加一个调用 showError 的 OnClientClick 事件按钮。将这里的 showError JavaScript 函数添加到 default.aspx:
function showError()
{
window.alert(Sys.Res.argumentType);
return false;
}
将 Culture(区域性)和 UICulture 属性添加到页面中,并赋值为“auto”。运行应用程序。当浏览器的语言为法语时,将能看到显示的法语资源。
创建框架的发布版本
这个问题已经得到解决,但那只是针对调试版本而言的。如果您将 ScriptManager 的 ScriptMode 设置为 Release,则应用程序将无法运行,这是因为它将会查找 MicrosoftAjax.js 或 MicrosoftAjax.fr.js 文件,很明显这些文件已经丢失。最简单的办法就是复制这些文件的调试版本,然后以发布版本的文件名重命名它们。这样是能解决最初的问题,但是很明显这意味着发布和调试版本是相同的,而且更重要的是,发布版本并没有经过优化,而且存在过多的注释和空白区域。
同样幸运的是,ASP.NET AJAX 有办法解决这个问题。AJAX 控件工具包包括名为 JavaScriptCommentStripper 的 MSBuild 任务,正好可以解决这个问题。这个任务将 JavaScript 文件作为一个输入,并输出一个相同的文件,但是不包括注释和空格。图 4 显示了简单的 MSBuild 脚本,它可将 debug.js 文件转换为同一文件的精炼版本。所以,在我们的示例中,MicrosoftAjax.Debug.js 和 MicrosoftAjax.Debug.fr.js 文件将会产生两个附加文件——MicrosoftAjax.Debug.js.Stripped 和 MicrosoftAjax.Debug.fr.js.Stripped。如果您对这个结果感到满意,可将它们分别重命名为 MicrosoftAjax.js 和 MicrosoftAjax.fr.js,这样就大功告成了。
Figure 4 JavaScriptCommentStripper MSBuild Task
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="StripComments">
<UsingTask TaskName="JavaScriptCommentStripperTask"
AssemblyFile="C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX
Extensions\AJAXControlToolkit\Binaries\JavaScriptCommentStripper.dll"/>
<ItemGroup>
<SourceFiles Include="*.Debug*.js"/>
</ItemGroup>
<Target Name="StripComments">
<JavaScriptCommentStripperTask SourceFiles="@(SourceFiles)"
DestinationFiles="@(SourceFiles->'$(TargetDir)%(RecursiveDir)
%(Filename)%(Extension).Stripped')"/>
</Target>
</Project>
<UsingTask TaskName="JavaScriptCommentStripperTask"
AssemblyFile="C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX
Extensions\AJAXControlToolkit\Binaries\JavaScriptCommentStripper.dll"/>
<ItemGroup>
<SourceFiles Include="*.Debug*.js"/>
</ItemGroup>
<Target Name="StripComments">
<JavaScriptCommentStripperTask SourceFiles="@(SourceFiles)"
DestinationFiles="@(SourceFiles->'$(TargetDir)%(RecursiveDir)
%(Filename)%(Extension).Stripped')"/>
</Target>
</Project>
您可以通过打开之前和之后的 JavaScript 文件来查看此任务对 JavaScript 所产生的影响。图 5 显示了原始 Microsoft.Debug.js 文件的上半部分。
Figure 5 The Top of Microsoft.Debug.js
//-----------------------------------------------------------------------
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------
// MicrosoftAjax.js
// Microsoft AJAX Framework.
Function.__typeName = 'Function';
Function.__class = true;
Function.createCallback = function Function$createCallback(method,
context) {
/// <param name="method" type="Function"></param>
/// <param name="context" mayBeNull="true"></param>
/// <returns type="Function"></returns>
var e = Function._validateParams(arguments, [
{name: "method", type: Function},
{name: "context", mayBeNull: true}
]);
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------
// MicrosoftAjax.js
// Microsoft AJAX Framework.
Function.__typeName = 'Function';
Function.__class = true;
Function.createCallback = function Function$createCallback(method,
context) {
/// <param name="method" type="Function"></param>
/// <param name="context" mayBeNull="true"></param>
/// <returns type="Function"></returns>
var e = Function._validateParams(arguments, [
{name: "method", type: Function},
{name: "context", mayBeNull: true}
]);
这是从 Microsoft.Debug.js.Stripped 文件中删除的代码。很明显,比较短(并且也不可读)。在本示例中,JavaScriptCommentStripper 任务已将该文件的大小从 260,096 字节减少到 182,454 字节:
Function.__typeName = 'Function';Function.__class = true;
Function.createCallback = function Function$createCallback(method, context) {var e = Function._validateParams(arguments, [
{name: "method", type: Function},
{name: "context", mayBeNull: true}
]);
全球化 ASP.NET AJAX
如果说 JavaScript 与 .NET Framework 相比之下缺少全球化支持,这在某种程度上是一种保守的说法。.NET Framework 拥有大量的全球化类,而对 JavaScript 的支持只是沧海一粟。而在增加内容方面却又是进退维谷。大部分的 System.Globalization 命名空间都可以在 ASP.NET AJAX 框架中实现,但是应该这样做吗?每个类都会增加下载大小,所以需要评估业务案例。因此,即使有了对 ASP.NET AJAX 框架的全球化支持,这与对 .NET Framework 的支持相比也是很有限的。记住这一点,然后来看看我们都有些什么。
默认情况下,ASP.NET AJAX 中的全球化支持为禁用状态。若要启用该状态,您必须将 ScriptManager 的 EnableScriptGlobalization 属性设置为 true。这个简单的操作包括生成的运行时页面中的 __cultureInfo 变量,如下所示:
var __cultureInfo = '{"name":"en-GB","numberFormat":{
"CurrencyDecimalDigits":2,
"CurrencyDecimalSeparator":".","IsReadOnly":true,
"CurrencyGroupSizes":[3],"NumberGroupSizes":[3],
"PercentGroupSizes":[3],"CurrencyGroupSeparator":",
","CurrencySymbol":"£","NaNSymbol":"NaN",
"CurrencyNegativePattern":1,"NumberNegativePattern":1,
"PercentPositivePattern":0,"PercentNegativePattern":0,
"NegativeInfinitySymbol":"-Infinity","NegativeSign":"-
这个变量是从服务器的 CultureInfo.CurrentCulture 生成的,并且遵循服务器的用户设置。ScriptManager 没有可忽略用户设置的选项。
MicrosoftAjax.js 包含对此大型变量的引用:
Sys.CultureInfo.CurrentCulture = Sys.CultureInfo._parse(__cultureInfo);
delete __cultureInfo;
该变量已经过读取和分析,并分配到 Sys.CultureInfo.CurrentCulture 变量,然后发布。
一些变量已经在 MicrosoftAjax.js 中准备就绪,您可以看到 InvariantCulture 的定义:
Sys.CultureInfo.InvariantCulture = Sys.CultureInfo._parse('{"name":"",
"numberFormat":
{"CurrencyDecimalDigits":2,"CurrencyDecimalSeparator":".",
"IsReadOnly":true,"CurrencyGroupSizes":[3],"NumberGroupSizes":[3],
"PercentGroupSizes":[3],"CurrencyGroupSeparator":",",
"CurrencySymbol":"\u00A4","NaNSymbol":"NaN",
"CurrencyNegativePattern":0,"NumberNegativePattern":1,
"PercentPositivePattern":0,"PercentNegativePattern":0,
"NegativeInfinitySymbol":"-Infinity","NegativeSign":"-
缺少的是 CurrentUICulture 的值。您可以假设该值与 CurrentCulture(这是相当不确定的假设)相同,也可以想出一个与 CurrentCulture 相同的办法,并将合适的变量插入已生成的发送到客户端的 HTML。
现在,让我们来考虑一下可以通过全球支持做些什么。创建一个启用了 AJAX 的 Web 应用程序,并在 ScriptManager 中将 EnableScriptGlobalization 设置为 true。添加一个 Label 和 Button,再添加一个 OnClientClick 事件以返回对 showToday JavaScript 函数的调用:
<script type="text/javascript">
function showToday()
{
var date = new Date();
$get('Label1').innerHTML = date.localeFormat("D");
return false;
}
</script>
将 Culture(区域性)和 UICulture 属性添加到页面中,并赋值为“auto”。在浏览器的语言设置为英语或无的情况下运行应用程序,然后单击按钮,日期将会以“区域和语言选项”对话框中设置的区域默认格式显示。将浏览器的语言更改为“fr”,刷新页面,然后单击按钮,Label 将会按照法语日期格式以法语显示日期(例如,lundi 2 juillet 2007)。
如果您对 JavaScript Date 类很熟悉,将会意识到它并没有用于 showToday 函数中名为 localeFormat 的方法。这是 ASP.NET AJAX 框架中所包括的其中一个类型扩展。您可以在 MicrosoftAjax.Debug.js 中查看这些内容的代码。Date 类的 localeFormat 方法如下所示:
Date.prototype.localeFormat = function Date$localeFormat(format) {
/// <param name="format" type="String"></param>
/// <returns type="String"></returns>
var e = Function._validateParams(arguments, [
{name: "format", type: String}
]);
if (e) throw e;
return this._toFormattedString(format,
Sys.CultureInfo.CurrentCulture);
}
有关这些类型扩展的完整列表,请参阅 ajax.asp.net/docs/ClientReference/Global/JavascriptTypeExtensions/default.aspx。
所以,通过 Sys.CultureInfo 类型、Sys.CultureInfo.InvariantCulture 和 Sys.CultureInfo.CurrentCulture 属性以及类型扩展,ASP.NET AJAX 框架提供了有限的全球框架。
结语
ASP.NET AJAX 框架提供了对 JavaScript 的本地化和全球化支持,这在除了自定义实现以外是不多见的。该框架提供了两种本地化模型选择,必有一种能够满足您的需要。与 .NET Framework 的丰富性相比,全球化支持还处于初级阶段,但是这并不意味着您不能有创造性、不能添加自己的类型扩展和 JavaScript 类来支持特定的需要。我相信,AJAX 将会继续升级,因为所有的技术也会升级,所以期待在未来的 ASP.NET AJAX 版本中看到此模型的增强功能。