逍遥法外第二季 更新:高效管理ASP.NET的JavaScript库

来源:百度文库 编辑:九乡新闻网 时间:2024/10/04 05:27:22

简介

对于ASP.NET开发人员来说,管理项目中的JavaScript都很随意:

我想这很大程度上可能是因为网上没有如何妥善处理ASP.NET中JavaScript的可靠信息。此文的目的就是提供一种最佳方案,用于管理ASP.NET中的JavaScript。该方案将能解决以下问题:

  • 内联JS:把JS直接放在页面中将导致页面臃肿不堪。
  • 发布JS:经常忘记发布JS文件。
  • 错误引用:在其它Web程序中引用JS时经常失败。
  • 依赖性:需要记住JS文件中错综复杂的依赖关系。
  • 无效引用:页面上引用的JS从来没有被用到。
  • HTTP/HTTPS:跨HTTPS页面引用HTTP的JS。
  • 重构:重构一个新版本将花费大量时间。
  • 冗余:多次引用统一个JS文件。

预备知识

确保已安装Visual Studio 2010。Express版可能不支持此文涉及到的一些概念。

概述

大部分上述问题是由把JS或JS文件引用直接放到ASPX页面引起的。对几乎所有上述问题的解决方法是使用ASP.NET的内置功能来嵌入JS文件到一个DLL,然后动态引用这些文件。本文将演示这些功能,以及一些充分使用它们的技巧。接下来我们将逐步介绍该如何实现。

开始

第一步,启动Visual Studio 2010,并新建一个名为ParchmentPurveyor的空Web程序。

接下来添加一个窗体:Default.aspx,并添加一些简单的HTML代码。大致如下:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="ParchmentPurveyor.Default" %>



Parchment Purveyor



Parchment Purveyor


Paper for printers, painting, publication,
paper planes, and plenty of other plebeian projects!





添加JS

不同于在站点中添加JS文件,我们新建一个项目,用于包含我们所有的JS文件。在解决方案中添加一个新的类库项目JavaScriptLibrary:

项目添加后删除Class1.cs文件,右键项目,选择添加文件夹,并命名为JavaScript,然后在该文件夹中添加两个JS文件,分别为ShowMessage.js和GreetUser.js,下一步,在项目中添加一个类JavaScriptHelper(注意不要放到JavaScript目录下),现在解决方案目录结构如下:

接下来编写JS,在ShowMessage.js中添加如下代码:

function ShowMessage(msg) {
alert("Message From Website: " + msg);
}

在GreetUser.js文件中添加如下代码:

function GreetUser() {
ShowMessage("Greetings and Salutations!");
}

注意,GreetUser()依赖于ShowMessage()。

嵌入JS文件

相比把JS文件发布到站点,我们更乐于把它们嵌入到DLL。这样子,如果DLL被发布到站点,那么所有JS文件也被自动发布。做到一点很简单,我们只需要右键JS文件,打开属性页,为“生成操作”选择“嵌入资源”即可,如下:

在确定JS文件嵌入DLL后,你需要使它们能够被Web用户访问。为此,需要为项目JavaScriptLibrary添加System.Web的引用:

然后编辑JavaScriptHelper.cs,添加如下代码:

using System.Web.UI;

[assembly: WebResource("JavaScriptLibrary.JavaScript.ShowMessage.js", "application/x-javascript")]
[assembly: WebResource("JavaScriptLibrary.JavaScript.GreetUser.js", "application/x-javascript")]

这样就能保证Web用户通过客户端访问嵌入式JS文件了。

引用嵌入式JS文件

现在你已嵌入了JS文件,并能通过客户端电脑访问它们。在使用的时候,你必须在页面上引用它们。为此,需要对JavaScriptHelper类做如下修改:

using System;
using System.Web.UI;

[assembly: WebResource("JavaScriptLibrary.JavaScript.ShowMessage.js", "application/x-javascript")]
[assembly: WebResource("JavaScriptLibrary.JavaScript.GreetUser.js", "application/x-javascript")]

namespace JavaScriptLibrary
{

///
/// 帮助页面引用嵌入式JS文件
///

public class JavaScriptHelper{

#region 静态字段
private const string NAME_SHOW_MESSAGE = "JavaScriptLibrary.JavaScript.ShowMessage.js";
private const string NAME_GREET_USER = "JavaScriptLibrary.JavaScript.GreetUser.js";
#endregion

#region 公共方法
///
/// 在页面上引用ShowMessage.js文件
///

/// 通过Page.ClientScript访问
public static void Include_ShowMessage(ClientScriptManager manager){
IncludeJavaScript(manager, NAME_SHOW_MESSAGE);
}

///
/// 在页面上引用GreetUser.js文件 (包括所有依赖文件)
///

/// 通过Page.ClientScript访问
public static void Include_GreetUser(ClientScriptManager manager){
//依赖(ShowMessage.js).
Include_ShowMessage(manager);
//引用 GreetUser.js.
IncludeJavaScript(manager, NAME_GREET_USER);
}
#endregion

#region 私有方法
///
/// 在页面上引用指定的嵌入式js文件
///

/// 通过Page.ClientScript访问
/// 用于标示嵌入式JS文件的名字
private static void IncludeJavaScript(ClientScriptManager manager, string resourceName){
var type = typeof(JavaScriptLibrary.JavaScriptHelper);
manager.RegisterClientScriptResource(type, resourceName);
}
#endregion
}
}

IncludeJavaScript()是关键所在。它通过调用RegisterClientScriptResource()确保为嵌入式JS文件获取一个脚本标签。Include_GreetUser()调用了IncludeJavaScript(),同时也调用了Include_ShowMessage()(用于处理依赖关系)。因此,任何页面在引用GreetUser()时也将引用ShowMessage()。

现在我们有了可用的类,接下在让我们在Default.aspx页面中试用它。首先在站点ParchmentPurveyor中添加对JavaScriptLibrary的引用:

接下来我们需要修改引用JS页面的后台代码。

using System;
using System.Web.UI;

namespace ParchmentPurveyor
{
public partial class Default : System.Web.UI.Page{
protected override void OnPreRender(EventArgs e){
base.OnPreRender(e);
JavaScriptLibrary.JavaScriptHelper.Include_GreetUser(Page.ClientScript);
}
}
}

最后,还有一件事要做——从页面调用GreetUser()。为此,我们需要在页面中添加如下JS(我选择把它添加到标签中):


Parchment Purveyor

好了,除了还有一些琐碎的事要处理外,我们已基本完成。在处理那些之前,让让我们看一下成果。编译整个解决方案—〉右键在浏览器中查看Default.aspx页面:

右键查看页面源码,你可能看到如下内容(src="/WebResource.axd.."部分有删减):


<br s="box-sizing: border-box; "> Parchment Purveyor<br s="box-sizing: border-box; ">










Parchment Purveyor


Paper for printers, painting, publication,
paper planes, and plenty of other plebeian projects!




请注意引用“WebResource.axd”的两个\r\n";
private const string NAME_SHOW_MESSAGE = "JavaScriptLibrary.JavaScript.ShowMessage.js";
private const string NAME_GREET_USER = "JavaScriptLibrary.JavaScript.GreetUser.js";
#endregion

#region 公共方法
///


/// 页面引用ShowMessage.js文件
///

/// 通过Page.ClientScript访问
/// 是否在HTML底部引用JS
public static void Include_ShowMessage(ClientScriptManager manager, bool late = false) {
IncludeJavaScript(manager, NAME_SHOW_MESSAGE, late);
}
///
/// 页面引用GreetUser.js文件(包括所有依赖文件)
///

/// 通过Page.ClientScript访问
/// 是否在HTML底部引用JS
public static void Include_GreetUser(ClientScriptManager manager, bool late = false) {
// 依赖 (ShowMessage.js).
Include_ShowMessage(manager, late);
// 引用 GreetUser.js.
IncludeJavaScript(manager, NAME_GREET_USER, late);
}
#endregion

#region 私有方法
///
/// 页面引用指定的嵌入式JS文件
///

/// 通过Page.ClientScript访问
/// 标示嵌入式JS文件的名字
/// 是否在HTML底部引用JS
private static void IncludeJavaScript(ClientScriptManager manager, string resourceName, bool late) {
var type = typeof(JavaScriptLibrary.JavaScriptHelper);
if (!manager.IsStartupScriptRegistered(type, resourceName)) {
if (late) {
var url = manager.GetWebResourceUrl(type, resourceName);
var scriptBlock = string.Format(TEMPLATE_SCRIPT, HttpUtility.HtmlEncode(url));
manager.RegisterStartupScript(type, resourceName, scriptBlock);
}
else {
manager.RegisterClientScriptResource(type, resourceName);
manager.RegisterStartupScript(type, resourceName, string.Empty);
}
}
}
#endregion
}
}

为每个方法添加一个参数late。该参数默认值为false,因此这些方法依旧可以按照原有方式调用。该参数为false表示原有行为不变,为true时将导致在HTML结尾部分引用脚本段。可能注意到,在late=false时,我仍然调用了RegisterStartupScript(),但传入了一个空字符串(所以不会在HTML插入任何内容)。完成后IsStartupScriptRegistered()将会返回正确值。这样,即使在late被置false后调用了其中的一个函数,又把late置为true,JS也不会被多次引用。如果要看效果,注释掉后台代码OnPreRender(),并在页面中做如下修改:



Parchment Purveyor


Paper for printers, painting, publication,
paper planes, and plenty of other plebeian projects!


<% // This gets called during the render stage.
JavaScriptLibrary.JavaScriptHelper.Include_GreetUser(Page.ClientScript, true); %>

在运行程序时,如果观察页面源码,你会发现这将调用HTML底部

添加新的JS文件

一旦所有内容完成部署,在需要添加新的JS文件时,只需要几步即可完成,如下:

  • 在JavaScript目录中添加js文件。引用外部js文件跳过该步骤;
  • 设置“生成操作”为“嵌入资源”。引用外部js文件跳过该步骤;
  • 添加assembly属性表示js文件为Web资源。引用外部js文件跳过该步骤;
  • JavaScriptHelper类中添件一个引用JS文件的函数;
  • 从页面,控件或母版页上调用你创建的函数;

不引用JS文件

以上所做都是为了引用JS文件,但也有时候你可能不需要引用JS文件。例如,在使用第三方控件库时,它们可能通过其他方式引用了JS,这时唯一阻止某一JS文件被两次引用的方法是通过你的代码消除重复引用(由第三方库帮你引用,不需要重复引用)。这可以通过在JavaScriptHelper增加额外的函数实现。在实现之前,先让我们演示一下这些技术应用的场景。假设你的第三方控件InlineGreeting.ascx引用了jQuery,其内容大致如下:

<%@ Control Language="C#" %>

<%-- This is a bad way to do things, but we can luckily overcome this obstacle. --%>




现在假设我们有另一个自己的控件Hello.ascx,使用了同样的jQuery文件:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Hello.ascx.cs" Inherits="ParchmentPurveyor.Hello" %>

Hello.ascx的后台代码引用了jQuery,如下:
        protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
JavaScriptLibrary.JavaScriptHelper.Include_GreetUser(Page.ClientScript);
JavaScriptLibrary.JavaScriptHelper.Include_jQuery(Page);
}

现在,如果在Default.aspx中引用上述用户控件,jQuery将会被引用两次(第三方一次,我们一次)。为避免此类情况发生,我们将在JavaScriptHelper类中添加两个方法,ExcludeJavaScript()和Exclude_jQuery():
        private const string NAME_DUMMY_FILE = "JavaScriptLibrary.JavaScript.DummyFile.js";
///
/// 该页面排除jQuery.js
///

/// 通过Page.ClientScript访问
public static void Exclude_jQuery(ClientScriptManager manager) {
ExcludeJavaScript(manager, NAME_JQUERY);
}
///
/// 注册一个虚假的脚本来阻止包含真实的JavaScript
///

/// 通过Page.ClientScript访问
/// 唯一标示JavaScript文件的名字
private static void ExcludeJavaScript(ClientScriptManager manager, string key) {
var type = typeof(JavaScriptLibrary.JavaScriptHelper);
var url = manager.GetWebResourceUrl(type, NAME_DUMMY_FILE);
manager.RegisterStartupScript(type, key, string.Empty);
manager.RegisterClientScriptInclude(type, key, url);
}

注意,我们定义了一个新的常量NAME_DUMMY_FILE。上面函数假定我们按照上述步骤在JavaScript文件夹里添加了一个空JS文件,并嵌入了它。这个空虚拟JS文件可以引用在任何我们想不引用JS文件的地方。为阻止我们的库引用jQuery只需要调用在Default.aspx页面的Page_Load()中调用Exclude_jQuery():

        protected void Page_Load(object sender, EventArgs e) {            
//我们通过第三方控件引用了jQuery,那么将避免再一次引用
JavaScriptLibrary.JavaScriptHelper.Exclude_jQuery(Page.ClientScript);
}

现在我们要做的是修改Default.aspx引用InlineHello.ascx和Hello.ascx,结果如下:

...


Parchment Purveyor





Parchment Purveyor


Paper for printers, painting, publication,
paper planes, and plenty of other plebeian projects!


<% // 这在渲染阶段调用
JavaScriptLibrary.JavaScriptHelper.Include_GreetUser(Page.ClientScript, true); %>

<%-- An inline greeting (pretend this comes from a third-party control library. --%>

<%-- Our jQuery greeting. --%>



现在我们已清楚在调用Include_jQuery()之前先调用Exclude_jQuery(),就能够阻止我们的JS库引用jQuery.js文件。这就是说jQuery只会被我们的第三方控件引用。大多数时候这种情况可以避免。然而,有时候不可避免,这时该技术也可以让我们的HTML更加干净整洁。

你都有哪些收获?

经过这些工作,依旧有同样的HTML输出,你可能会问“通过添加这些额外的代码,我获得了什么?”。那么这里就列出几点:

    • 内联JS:通过避免把JS直接内联到页面,减小了页面尺寸。
    • 发布JS:当你发布Web站点时,你不需要发布引用的JS文件,只发布DLL就足够了。
    • 错误引用:即使改变了程序路径,你也不用为修改JS路径担忧。
    • 依赖性:文件依赖自动管理。如果你引用了GreetUser.js文件,那么ShowMessage.js文件会自动被引用。
    • 无效引用:除非你调用的函数引用了它,否则不会有JS加载到页面。这将避免页面上出现无用的JS(潜在的加快了页面载入时间)。
    • HTTP/HTTPS:脚本标记的代码输出与协议无关,因此协议对所有页面一样。
    • 重构:如果你想使用一个不同版本的脚本,你只需要在一个地方修改它。例如,如果你决定切换到CDN版本的jQuery,而不是你自己承载,这可能非常有用。更新一个新版本的jQuery时也非常有用。
    • 冗余:不管你在方法中引用多少次,该脚本标签仅会在页面上本引用一次