xLibrary - 使用自定义 IHttpHandler 访问嵌入式 JavaScript






4.33/5 (4投票s)
本文介绍了如何创建一个自定义 IHttpHandler 来使用嵌入在程序集中的 www.cross-browser.com 的 X Library。
目录
引言
本文介绍了如何创建一个自定义的 IHttpHandler
来使用嵌入在程序集中的 www.cross-browser.com 的 X Library。
使用的关键技术
- 将资源嵌入到程序集中。
- 使用
IHttpHandler
对请求进行自定义处理。
特点
X 库的所有 JavaScript 和 XML 文档都将嵌入到程序集中。
自定义的 IHttpHandler
将返回使用所需 X Library 函数所需的所有必要方法。
程序集易于更新。只要 XML 文档文件的格式不变,要更新到新版本的 X Library,您只需在项目中更新 .js/.xml 文件并重新编译即可。
问题
来自 www.cross-browser.com 的 X Library 是一个非常有用的工具。它对许多 JavaScript 函数进行了标准化,以便在不同浏览器中使用。X Library 最棒的地方在于它是模块化的。每个函数都有自己的 .js 文件,因此您不必向浏览器发送不必要的内容。这对于最大限度地减少流量非常重要。为了避免我们必须在单独的 <script>
块中引用每个脚本,它有一个名为 XC 的有用工具,可以将您使用的所有脚本编译成一个 .js 文件。这是一个很棒的工具,但我很懒,不想每次更新页面时都运行它。
解决方案
我决定为此库创建一个 ASP.NET 包装器,该包装器将仅提供我需要的脚本,而无需使用上述 XC 工具进行编译,也无需为每个函数使用单独的 <script>
块。所有脚本和文档都将嵌入到这个新的 xLibrary 程序集中。它将有一个实现 IHttpHandler
接口的类,并且将仅为每个请求提供所需的脚本。
代码
概述
xLibrary 程序集包含三个类以及嵌入的 JavaScript 和 XML。这些类如下所示:
xFunction
xLibraryHttpHandler
xLibraryLoader
一个简单的对象,用于保存函数的名称、源和依赖项。
IHttpHandler
接口的实现,它将处理 X 函数的请求。
一个辅助对象,它将嵌入的 JavaScript 加载到内存中,以加快 X 函数的请求速度。
将 JavaScript 和 XML 嵌入到程序集中
首先,您需要将从 www.cross-browser.com 下载的 .zip 文件中 x\lib 文件夹下的所有 .xml 和 .js 文件添加到项目中的 lib 文件夹。选择所有这些文件,然后在“属性”窗口中,将 “生成操作” 属性更改为 “嵌入的资源”。当 Visual Studio 构建程序集时,此设置将导致编译器将所有这些文件嵌入到程序集中。每个嵌入的资源将命名如下:
[assemblyName].[relative-path-to-file].[filename].[extn]
在这种情况下,程序集名称是 xLibrary,路径是 lib。因此,lib 文件夹中的 xaddclass.js 文件将被命名为嵌入式名称 xLibrary.lib.xaddclass.js。如果文件位于单独的文件夹中,例如“lib\xml”和“lib\js”,则 xaddclass.js 将被命名为 xLibrary.lib.js.xaddclass.js,而 xaddclass.xml 将被命名为 xLibrary.lib.xml.xaddclass.xml。
实现
一旦它们被嵌入到程序集中,我们就需要能够从代码中访问它们。这就是 xLibraryLoader
类发挥作用的地方。我将首先介绍 GetResourceStream
方法。
private Stream GetResourceStream( string resourceName )
{
return Assembly.GetExecutingAssembly().GetManifestResourceStream( resourceName );
}
这是一个非常简单的方法,用于将嵌入的资源检索为 Stream
。传入一个资源名称,如 xLibrary.lib.xaddclass.js,它将返回该文件的流。
下一个方法是 GetResource
。
private string GetResource( string resourceName )
{
return new StreamReader( GetResourceStream( resourceName ) ).ReadToEnd();
}
此方法与 GetResourceStream
类似,但它不是返回 Stream
,而是将整个 Stream
转换为 string
返回给调用者。
为了节省请求时的处理时间(注意:这会以内存使用量为代价),当我们初始化 xLibraryLoader
类时,它会加载一个 Hashtable
,其中包含它在程序集中找到的所有函数。然后,可以使用 Hashtable
来返回所需的脚本,而不是依赖于反射。为了实现这一点,我们使用 FillHashtable
。
此方法首先创建 Hashtable
并获取当前程序集的引用。如果需要,我们可以将资源嵌入到另一个程序集中,并修改此方法和 GetResource
方法。一旦它获取了对必要程序集的引用,它就会获取该程序集中所有嵌入资源的列表。然后,我们开始循环遍历所有找到的资源。
private void FillHashtable ()
{
xLibraryData = new Hashtable();
Assembly asm = Assembly.GetExecutingAssembly();
string[] resources = asm.GetManifestResourceNames();
foreach ( string resource in resources )
{
我们首先要做的是确保我们处理的是 XML 文件而不是 JavaScript 文件。我们只需要处理这两种文件,因为这就是我们程序集中的所有内容。如果我们有除此处使用的文件之外的 XML 文件,则需要额外的检查来验证我们是否拥有 X Library XML 文档文件。然后,我们设置将保存有关 XML 文档文件指定的函数信息的变量。
if ( String.Compare( Path.GetExtension( resource ), ".xml", true ) == 0 )
{
string id = string.Empty;
string source = string.Empty;
ArrayList dependencyIds = new ArrayList();
现在我们知道我们有一个 XML 文档文件,我们需要解析它以获取所需的信息。首先,我们创建一个 System.Xml.XmlDocument
实例,并将其加载到嵌入文件的 Stream
中。接下来,使用 XPath,我们选择根元素 x_symbol
(/x_symbol
) 的 ID 属性 (/@id
)。我们将此属性的值赋给之前设置的 id
变量。
接下来,我们选择 /x_symbol/sources/src/file
元素。每个 XML 文件应该只有一个这样的元素,但如果存在多个,它将只选择第一个实例。因为它是一个元素,所以它没有 Value
属性,但有一个 InnerXml
属性。我们使用 string.Format
将文件名格式化为 GetResource
和 GetResourceStream
可以用来查找我们的 JavaScript 文件的格式。
最后,我们获取此 X 函数的所有依赖项列表。SelectNodes
方法返回一个 XmlNodeList
,其中包含所有匹配 XPath 表达式 /x_symbol/dependencies/dep
的节点。我们遍历其中的每一个,并将此函数所依赖的 X 函数的名称添加到 ArrayList
中。
XmlDocument doc = new XmlDocument();
doc.Load( GetResourceStream( resource ) );
XmlNode x_symbolId = doc.SelectSingleNode( "/x_symbol/@id" );
id = x_symbolId.Value;
x_symbolId = null;
XmlNode srcFile = doc.SelectSingleNode( "/x_symbol/sources/src/file" );
source = GetResource(
string.Format( "xLibrary.lib.{0}", srcFile.InnerXml )
);
srcFile = null;
XmlNodeList deps = doc.SelectNodes( "/x_symbol/dependencies/dep" );
foreach( XmlNode dep in deps )
{
dependencyIds.Add( dep.InnerXml );
}
deps = null;
现在,我们将收集到的 id
、source
和 dependencyIds
放入一个简单的对象来保存它们。如果您从未用过 ArrayList
类的 ToArray
方法,那么它非常方便,但它是一对一的复制。请记住,如果您处理的是非常大的数组(这里不是这种情况),则复制可能需要一些时间。
然后,我们将创建的 xFunction
添加到 Hashtable
中,并按其名称进行索引。最后,完成循环并处理下一个。
xFunction function =
new xFunction( id, source, dependencyIds.ToArray( typeof(string) ) as string[] );
xLibraryData.Add( function.Name.ToLower(), function );
}
}
}
IHttpHandler
我们还有两个方法,但在介绍它们之前,我想谈谈 IHttpHandler
接口和 xLibraryHttpHandler
类。
当我第一次开始接触 IHttpHandler
时,我对它的工作方式和作用感到有些困惑。所以,为了开始学习,让我们举个例子。IHttpHandler
最常见的实现是 System.Web.UI.Page
类。IHttpHandler
的实现只有一个方法(ProcessRequest
)和一个属性(IsReusable
)。IsReusable
属性不言自明。如果返回 true
,则 ASP.NET 进程将为每个请求重用相同的实例。如果 IsReusable
返回 false
,则为每个请求创建一个新实例。ProcessRequest
方法接受一个参数:HttpContext
的实例。HttpContext
对象包含对 HttpRequest
对象(其中包含查询字符串等)和 HttpResponse
对象(用于将信息从服务器流式传输到客户端)的引用。当用户请求 ASPX 页面时,ASP.NET 进程通过调用其 ProcessRequest
方法,将请求的责任移交给 System.Web.UI.Page
类的实例。然后,Page
确定请求地址实际对应的页面。然后解析该页面,并将所有内容返回到 Response
对象的流中。(注意:这是实际发生情况的简化版本。有关更多信息,请参阅 .NET Framework 文档中的 ASP.NET 请求处理,或者要了解 Microsoft 的实现方式,请使用 Lutz Roeder 的 Reflector 来查看 System.Web 程序集。)
我们将做一些与 Page
对象类似但更简单的事情。因为 xLibraryHttpHandler
实现 IHttpHandler
,所以它具有 IsReusable
属性和 ProcessRequest
方法。
private xLibraryLoader loader;
public xLibraryHttpHandler()
{
loader = new xLibraryLoader();
}
我们在构造函数中所做的第一件事是创建一个 xLibraryLoader
类的新实例。请注意,xLibraryLoader
构造函数调用上面介绍的 FillHashtable
方法。
public bool IsReusable
{
get
{
return true;
}
}
由于 xLibraryLoader
构造函数调用 FillHashtable
方法,因此我们不希望它在每次请求时都进行解析,否则繁忙的服务器会很快崩溃。这就是为什么我们将 IsReusable
设置为返回 true
。这样,ASP.NET 工作进程将在每次请求时重用 xLibraryHttpHandler
的同一实例。(注意:我没有对哪种方式更快进行基准测试,而是凭直觉进行的。在投入生产使用之前,我建议实际测试这两种方法,并决定哪种方法更适合您的使用。)
public void ProcessRequest ( HttpContext context )
{
HttpResponse Response = context.Response;
HttpRequest Request = context.Request;
Response.ContentType = "application/x-javascript";
string[] functions = Request.QueryString["functions"].Split( ',' );
string script = loader.GetScript( functions, true );
Response.Write( "// Copyright (C) 2007 Brodrick Bassham..." );
Response.Write( script );
}
xLibraryHttpHandler
的最后一部分是 ProcessRequest
方法。它首先创建 Response
和 Request
对象的别名,以便更容易使用(Page
对象也这样做)。然后,我们将 ContentType
标头设置为 “application/x-javascript”。关于是使用 “application/x-javascript” 还是 “text/javascript” 或其他值,存在很多争论。标准是 “application/x-javascript”,而 Internet Explorer 不支持 “application/x-javascript”,无论您将其设置为何值,它都会忽略它。下一行应该会给您一个关于我们如何使用它的提示。它获取 QueryString
并查找其中的 “functions” 部分(例如:https:///xLibrary.xlib?functions=func1,func2 将返回 “func1,func2”),然后以逗号为分隔符将其拆分为一个数组。我们的 xLibraryLoader
实例的 GetScript
方法正是这样做;它会构建使用给定数组中列出的函数所需的脚本。我将在下面讨论该方法。ProcessRequest
方法最后要做的就是将脚本发送到客户端的 Response
流。
public string GetScript ( string[] functions, bool alertOnError )
{
ArrayList added = new ArrayList();
StringBuilder output = new StringBuilder();
这是 xLibraryLoader
中的 GetScript
方法。它接受两个参数:一个名为 functions
的字符串数组和一个名为 alertOnError
的布尔值。functions
参数是要输出的函数名称数组。如果我们请求了一个我们没有的函数,则使用 alertOnError
参数显示一个 JavaScript 警报框。我们创建的 ArrayList
是我们已经添加到输出中的所有函数名称的列表。
foreach( string function in functions )
{
if ( !added.Contains( function ) )
{
首先检查我们是否已经添加了该函数。我们不希望输出中出现重复项。
xFunction func = xLibraryData[ function.ToLower() ] as xFunction;
从 Hashtable
中提取请求的函数。
if ( func != null )
{
output.Append( func.Source );
added.Add( func.Name );
output.Append( GetScript( func.Dependencies, alertOnError, added ) );
}
如果找到函数,则将源追加到输出,然后获取所有其依赖项的源。
else
{
output.AppendFormat( "// X Function {0} was not found.\n", function );
if ( alertOnError )
{
output.AppendFormat( "alert('X Function {0} was not found.');\n", function );
}
}
此代码将一个注释追加到输出,以表明找不到所需的函数。如果我们应该向用户发出错误警报,则追加代码以显示带有消息的 JavaScript 警报框。
}
}
return output.ToString();
}
循环完所有请求的 function
后,返回生成的脚本。
private string GetScript ( string[] functions, bool alertOnError, ArrayList added )
{
StringBuilder output = new StringBuilder();
foreach( string function in functions )
{
if ( !added.Contains( function ) )
{
xFunction func = xLibraryData[ function.ToLower() ] as xFunction;
if ( func != null )
{
output.Append( func.Source );
added.Add( func.Name );
output.Append( GetScript( func.Dependencies, alertOnError, added ) );
}
else if ( alertOnError )
{
output.AppendFormat( ERROR_ALERT, function );
}
}
}
return output.ToString();
}
此版本的 GetScript
方法也在 xLibraryLoader
类中。这个方法是一个递归方法,它会不断调用自身,直到找到所有需要的依赖项。它首先由非递归的 GetScript
方法调用。
如何使用这一切
现在我们已经回顾了代码的实际作用,我们如何使用它?嗯,我们构建的程序集需要放到我们使用的 Web 应用程序的 bin 文件夹中。接下来,我们有两个选择:我们可以修改 web.config 文件,或者我们可以创建一个自定义 .ashx 文件。
选项 1 - Web.config
我们先从 web.config 文件开始。更改将应用于以下部分(如果不存在,则需要添加此部分)
<configuration>
<system.web>
<httpHandlers>
<!-- Option 1 - Custom extension -->
<add verb="GET" path="*.xlib" type="xLibrary.xLibraryHttpHandler,xLibrary" />
<!-- Option 2 - Specific .ashx file -->
<add verb="GET" path="xLibrary.ashx" type="xLibrary.xLibraryHttpHandler,xLibrary" />
</httpHandlers>
<system.web>
<configuration>
上面的第一个 web.config 选项有点复杂。因为 IIS 进程不知道 .xlib 文件,所以它不知道需要以任何方式处理它。如果我们不进行任何更改,那么任何不存在于服务器上的 .xlib 文件都会返回 404(未找到)。如果文件确实存在于服务器上,它将向浏览器提供该文件,而不会执行我们想要的处理。为了纠正这一点,我们需要告诉 IIS 进程关于 .xlib 文件类型。
首先,右键单击“我的电脑”并选择“管理”,打开 IIS 管理控制台。展开树中的“服务和应用程序”项。钻取到您的 Web 应用程序;在本例中是“默认网站”。如下图所示,右键单击 Web 应用程序名称并选择“属性”。
在此,单击“主目录”选项卡并选择“配置”。
接下来,您将看到“应用程序配置”对话框。在这里,您将找到处理服务器上不同文件的程序。如果此列表中未指定扩展名,它将被视为常规文件。选择“添加”按钮继续。
最后,我们看到“添加/编辑应用程序扩展映射”对话框。浏览到“aspnet_isapi.dll”。它应该位于“C:\Windows\Microsoft.NET\Framework\v1.0.3705\”目录中(注意:将“Windows”替换为您的 Windows 安装文件夹,“v1.0.3705”替换为您使用的 .NET Framework 版本)。
在 Windows XP 中,“添加/编辑应用程序扩展映射”对话框可能会有点棘手。扩展名字段必须以句点开头。例如:.xlib。如果您不需要向具有此扩展名的任何文件发布数据,则可以将动词限制为 GET
。如果您不想费心创建实际文件(IHttpHandler
实现将处理所有必要的输出),则取消选中“检查文件是否存在”。注意:在 Windows XP 中,有时“确定”按钮会保持灰色。如果是这种情况,请确保所有设置都符合您的要求,然后单击包含“aspnet_isapi.dll”位置的文本框。这将展开文本框的内容并启用“确定”按钮。
此过程会告诉 IIS,当请求具有此扩展名的页面时——而不是发送文件的内容并让浏览器处理——我们希望 ASP.NET 进程对其进行处理并确定如何操作。当我们将在 web.config 文件的 <HttpHandlers>
部分中添加 xLibraryHttpHandler
时,它会告诉 ASP.NET 进程使用哪个类来处理请求。
因此,现在当浏览器请求任何具有 .xlib 扩展名的文件时,服务器不会查找文件本身,而是调用 xLibrary 程序集中的 xLibrary.xLibraryHttpHandler
类的 ProcessRequest
方法。
第二个 web.config 选项告诉应用程序,对“xLibrary.ashx”的每个请求都应通过 xLibrary
程序集中的 xLibrary.xLibraryHttpHandler
类进行路由。优点是,IIS 默认知道它必须让 ASP.NET 处理 .ashx 文件。通过对 web.config 文件进行此更改,ASP.NET 进程知道,如果请求“xLibrary.ashx”,则需要调用 xLibraryHttpHandler
。
对于那些无法访问管理控制台的用户(例如许多共享托管环境)来说,此选项要容易得多。
选项 2 - 自定义 .ashx 文件
最后一个也是可能是最简单的选项是创建一个自定义 .ashx 文件。我之前说过,IIS 和 ASP.NET 默认都知道它们需要处理对 .ashx 文件的任何请求。如果我们创建一个名为 “xLib.ashx” 的文件并将其留空,则在请求该文件时会发生错误。这是因为 ASP.NET 知道它需要处理它,但还没有被告知如何处理。为了解决这个问题,我们在文件中添加此行:
<%@ WebHandler Class="xLibrary.xLibraryHttpHandler,xLibrary" %>
这就像 .aspx 文件中的 @Page
指令一样。它告诉 ASP.NET 进程,为了处理请求,它必须调用 xLibrary 程序集中的 xLibrary.xLibraryHttpHandler
类。
实际使用
假设您上面创建了自定义 .xlib 扩展名并使用了相应的 web.config 条目,则可以使用嵌入的 JavaScript 如下:
<script
type="text/javascript"
src="xLibrary.xlib?functions=[comma,sparated,list,of,needed,functions]">
</script>
示例
<script
type="text/javascript"
src="xLibrary.xlib?functions=xAddEventListener,xGetElementsByClassName">
</script>
关注点
.NET 框架提供了一种将所有 JavaScript 和 XML 文件嵌入程序集中的方法,以便于维护。我喜欢将所有松散文件都这样嵌入,因为它创建了更整洁的目录结构,并且允许您检查应用程序中设置的文件的自定义权限。因为一些共享托管提供商很难编辑,所以您可以使用自定义 IHttpHandler
来实现相同的目标。
通过添加自定义扩展名和 IHttpHandler
实现,您可以创建自己的处理服务器文件的方式。这包括自定义解析器。
演示网站使用 Visual Web Developer Express 构建,源解决方案使用 Visual C# Express 构建,两者都可从 Microsoft 免费获取。
其他资源
- www.cross-browser.com
- W3Schools XPath 教程
- Dean Edwards 的 Packer
- Lutz Roeder 的 Reflector
- MSDN Library
- 我的博客
X,一个跨浏览器 JavaScript 库的家园。
XPath 的良好概述。
一个很棒的工具,可以缩小和混淆 JavaScript。该工具的 .NET 版本可以进一步扩展此项目,允许即时压缩和混淆 JavaScript。我可能会在以后实现这一点。
用于查看非混淆 .NET 程序集源代码的工具。
有关 .NET Framework 的信息,您应该始终首先查阅的地方。
历史
- 2007 年 7 月 4 日
- 2007 年 7 月 14 日
初始版本。
更新至 X 4.17。