利用WebResource.axd通过一个URL来访问装配件的内置资源

导言:

很多ASP.NET server控件都需要另外的外部资源来实现某些功能.比如,使用任何一个ASP.NET validation验证控件时,都需要一系列的JavaScript functions来执行它们的客户端验证.虽然可以在页面上添加这些JavaScript functions,不过更高效的方法是将这些函数封装在一个外部的JavaScript文件里,然后在页面通过<script src="PathToExternalJavaScriptFile" type="text/javascript" >的形式来将该文件包含在页面里.这样一来不仅可以实现对页面的瘦身,还可以允许浏览器对该JavaScript文件施行缓存(这样就不用每个页面在登录/回传时向浏览器发送该JavaScript代码了)

在ASP.NET 2.0之前,用户浏览器要访问这种外部资源的话,我们必须将它们作为具体的文件放在文件系统里.如果你使用ASP.NET 1.x的验证控件的话,你的页面必须添加一个对JavaScript文件的引用,如/aspnet_client/system_web/version/WebUIValidation.js.这些外部文件有碍最后的部署.

为解决这个问题,ASP.NET 2.0允许将外部资源植入控件的装配件里,通过一个指定的URL对其访问.将外部images, JavaScript files,CSS files植入控件的装配件后,部署就容易了,因为所有的资源都包含在.dll文件里了. 完成植入操作后,在ASP.NET 2.0页面里我们可以通过一个指定的URL(WebResource.axd)来实现对这些资源的访问.

本文我们探讨如何将外部资源植入装配件以及如何通过一个指定的URL来对其访问,该技术简化了安装和部署.

一个Custom Control示例...

为了考察ASP.NET 2.0里的外部资源特性,我们需要构建一个包含外部资源的自定义控件.就本文而言,我将创建一个很简单的控件来对TextBox控件的基本函数进行扩充.该控件—FunkyTextBox,在其客户端的onkeypress event事件里,用一个color数组里定义的颜色随机的改变TextBox的底色.

该行为需要一些客户端脚本:首先,我们要添加一个函数来获取一个随机数,然后根据该随机数设置TextBox的背景颜色.第二,我们要定义要用到的color数组.我们可以轻而易举的绕开外部文件,直接向页面注入要用到的JavaScript.下面的代码显示了FunkyTextBox控件的完整代码(为简化起见,using声明、命名空间、以及定义好的colors数组都省略掉了)

public class FunkyTextBox : TextBox
{
    protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
    {
        // Wire up the onkeypress event handler to the ChangeBackgroundColor() JavaScript function
        writer.AddAttribute("onkeypress", "ChangeBackgroundColor(this);");

        base.AddAttributesToRender(writer);
    }


    protected override void OnPreRender(EventArgs e)
    {
        // Dump in the JavaScript directly into the page
        // (Although the external JavaScript approach is more efficient)
        Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "FunkyTextBox",
@"
function ChangeBackgroundColor(ctrl)
{
  var rndNum = Math.floor(Math.random() * 256);
  if (ctrl && funkyTextBoxColors.length > rndNum)
    ctrl.style.backgroundColor = funkyTextBoxColors[rndNum];
}

var funkyTextBoxColors =  new Array('#89C142', '#EF14E5', '#75762C', '#BB9E24', '#BF4A44', '#9D4A77', ...);
", true);

        base.OnPreRender(e);
    }
}

该server control继承自ASP.NET的TextBox,重写了2个方法.第一,在AddAttributesToRender方法里,添加了一个客户端特性(client-side attribute),那么任何时候,当用户在textbox控件里键入字符时,就会调用名为ChangeBackgroundColor的JavaScript function,传入对该textbox的引用.第二, 在FunkyTextBox的PreRender event事件里,通过 Page.ClientScript class's RegisterScriptBlock method方法直接将所需要的JavaScript注入到页面里.具体来说,定义了ChangeBackgroundColor function和funkyTextBoxColor数组,而ChangeBackgroundColor function获取一个随机值,再根据该值用funkyTextBoxColor数组里对应的颜色来设置背景色.

当把FunkyTextBox添加到一个ASP.NET页面后,最终页面将包含如下的代码:

<script type="text/javascript">
<!--

function ChangeBackgroundColor(ctrl)
{
    var rndNum = Math.floor(Math.random() * 256);
    if (ctrl && funkyTextBoxColors.length > rndNum)
        ctrl.style.backgroundColor = funkyTextBoxColors[rndNum];
}

var funkyTextBoxColors =  new Array('#89C142', '#EF14E5', '#75762C', '#BB9E24', '#BF4A44', '#9D4A77', ...);
// -->
</script>


Here is a FunkyTextBox:
<input onkeypress="ChangeBackgroundColor(this);" name="MyTextBox" type="text" id="MyTextBox" />

通过OnPreRender method方法将JavaScript注入到页面,而FunkyTextBox控件呈现为一个 <input type="text">标记,其onkeypress event事件调用ChangeBackgroundColor function函数.

将JavaScript转移到一个外部文件以对页面瘦身

该FunkyTextBox控件使用的JavaScript很简单,稍微复杂点的控件为实现其功能所需的JavaScript远不止这么多.直接将JavaScript植入页面将增大页面的size,也会让用户花更多的时间加载页面. 为了对页面瘦身,应将JavaScript转移到一个外部文件里,再在页面里添加一个<script>标签,以实现对JavaScript file的引用,比如:

<script src="PathToExternalJavaScriptFile" type="text/javascript" >

用<script>元素来替换JavaScript就达到了对页面瘦身的目的.此外,如果浏览器对外部文件实施缓存的话,那么我们就只需要下载一次就可以了(如果直接将JavaScript植入页面的话,每个页面每次登陆/加载时都要进行加载)

在ASP.NET 1.x里,对这些外部资源文件——JavaScript文件、CSS classes, images等,都需要与custom control一起加载和展开.在ASP.NET 2.0里我们可以将这些外部资源植入该控件的装配件,再通过一个指定的URL实现对这些资源的访问.

将外部文件植入控件的Assembly

要将资源植入控件的装配件,我们需要在Visual Studio里把资源添加到你的server control project里(本文结尾处可下载的代码里,包含了FunkyTextBox Web control Project,以及一个test website).对FunkyTextBox控件来说,我添加了一个名为Funky.js的文件,它包含的JavaScript就是我们在前面的OnPreRender方法向页面注入的那个JavaScript..

完成添加后,在Solution Explorer里选中它并打开Properties窗口,将Build Action设置为"Embedded Resource", 如下图:

这样一来,一旦你生成解决方案时,该文件就会被植入最终的assembly里.

通过一个URL来访问Embedded Assembly


对这些内置资源,我们可以通过WebResource.axd HTTP Handler来进行访问。通过一个URL,比如http://yoursite/WebResource.axd?d=assemblyKey&t=dateTimeOfLastAssemblyWrite, 其中,assemblyKey是要访问的装配件的名称的加密处理后的string, 而dateTimeOfLastAssemblyWrite描述了该装配件最后一次被修改的日期.给定一个有效的assemblyKey,那么WebResource.axd HTTP Handler就可以返回内置资源的内容.

使用WebResource.axd HTTP Handler,我们要面临3个问题:

1.通过WebResource.axd HTTP Handler,使一个内置资源允许被访问.
2.向WebResource.axd HTTP Handler传递准确的querystring值,以便检索某个特定的内置资源
3.将第2步生成的URL放置在ASP.NET页面恰当的位置

默认情况下是不允许WebResource.axd HTTP Handler对装配件里的内置资源进行访问的,为重写该行为,我们必须向server control project的AssemblyInfo文件添加一个特性(attribute),具体来说,要添加一个WebResource特性,比如:

[assembly: WebResource(webResource, contentType, performSubstitution)] 

注意:如果你使用的是Visual Basic,那么你需要用“<”和“>”来限定装配件,比如<assembly: WebResource(...)>.要看该AssemblyInfo文件的话,在Solution Explorer里选"Show All Files",它出现在 "My Project"文件夹下;再者,WebResource特性位于System.Web.UI命名空间,因此我们要在AssemblyInfo.cs文件顶端添加using System.Web.UI声明(如果是VB的话,用Imports System.Web.UI).

其中webResource参数指定了要访问的资源的名称,其命名模式为:RootNamespace.PathToFile.对本文示例而言,根命名空间为FunkyTextBox;又因资源文件在工程的根目录下,那么PathToFile就是Funky.js(假如Funky.js位于根目录的Scripts文件夹,那么PathToFile值就变成了Scripts.Funky.js).因此,webResource参数的值就为FunkyTextBox.Funky.js.

而contentType参数指定了要检索的资源的MIME type.当查询一个外部资源时,浏览器单独向服务器发出一个HTTP request.而MIME type就告知浏览器要返回的数据的类型.对JavaScript文件而言,其对应的contentType类型为text/javascript,有关MIME Media Types的更多信息,请参阅官方的MIME types清单.

最后,performSubstitution是一个可选的布尔值,用于指明该资源是否可以被其它的外部资源引用.比如,你可能将image文件和一个JavaScript文件植入到了装配件里,而该脚本文件可能需要引用某些image资源.如果确实需要的话,就将performSubstitution参数设为True.

对本文用到的控件而言,我们用如下的attribute声明来对Funky.js外部文件注册:

[assembly: WebResource("FunkyTextBox.Funky.js", "text/javascript")]

对资源进行注册后,我们就可以用WebResource.axd HTTP Handler来对其进行访问了.剩下的工作就是写代码来生成该外部资源对应的相应URL.

为此,我们使用ClientScriptManager class类里的GetWebResourceUrl(type, webResource) method方法.其中type参数就是该控件的类型,

webResource参数就是前面的WebResource特性里使用的webResource的值. 比如,要获取一个从该server control里访问内置的Funky.js资源的URL,我这样实现:

Page.ClientScript.GetWebResourceUrl(this.GetType(), "FunkyTextBox.Funky.js")

另外,我们在FunkyTextBox控件的OnPreRender事件里,将RegisterClientScriptInclude 方法和GetWebResourceUrl方法搭配使用,将相应的JavaScript包含进来(<script src="PathToExternalJavaScriptFile" type="text/javascript" >),如下:

protected override void OnPreRender(EventArgs e)
{
    // When pre-rendering, add in external JavaScript file
    Page.ClientScript.RegisterClientScriptInclude("FunkyJavaScript",
                 Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                             "FunkyTextBox.Funky.js"));

    base.OnPreRender(e);
}

通过这个重写的OnPreRender方法,控件将向页面添加如下的标记:

<script src="/TestWebsite/WebResource.axd?d=NLu6bm6a2XinJZt4M-ujmQ13X5ALig6NEAZa1-AxV0HCbE3M-

VHNomDQt_qnxjdT0&t=632902136868023078" type="text/javascript"></script>

Here is a FunkyTextBox:
<input onkeypress="ChangeBackgroundColor(this);" name="MyTextBox" type="text" id="MyTextBox" />

这样就将JavaScript引进来了.WebResource.aspx HTTP Handler获取该内置资源并将其发送回客户端.

结语

本文我们考察了如何将资源植入一个自定义ASP.NET server control的装配件里,以及如何通过一个URL来访问这些资源.这是ASP.NET 2.0里的新技术,便于开发者将外部资源植入装配件.