您的位置:首页 > 理论基础 > 计算机网络

httpmodule和httphandler配合的又一应用——合并脚本样式

2010-04-02 09:53 447 查看
页面上数十个脚本和样式文件怎么去合并又少写人工干预?

首先,使用查找替换把所有的<script>和<link>替换为<resource>然后在<resrouce>中加上runat="server":

母板页:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site1.master.cs" Inherits="StaticResourceMerge.DemoWebApp.Site1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<resource src='http://images.xxx.com/js/script/apiCommon.js' type="text/javascript"
runat="server"></resource>
<resource language="javascript" type="text/javascript" src="http://images001.xxx.com/js/my/v3/1m/site_sms_messages.js"
charset="utf-8" runat="server"></resource>
<resource href='http://images.xxx.com/css/0709/ibuyandisell.css' type="text/css"
rel="stylesheet" runat="server" />
<title></title>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div>
母板页
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>

用户控件:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs"
Inherits="StaticResourceMerge.DemoWebApp.WebUserControl1" %>

<resource language="JavaScript" type="text/javascript" src="http://images.xxx.com//js/jquery.1.3.2.js" runat="server"></resource>

<resource language="JavaScript" type="text/javascript" src="http://images.xxx.com//js/scroll.js" runat="server"></resource>

用户控件

页面:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site1.Master" AutoEventWireup="true"
CodeBehind="WebForm1.aspx.cs" Inherits="StaticResourceMerge.DemoWebApp.WebForm1" %>

<%@ Register Src="WebUserControl1.ascx" TagName="WebUserControl1" TagPrefix="uc1" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<resource href='http://images.xxx.com/css/gamestytle.css' rel="stylesheet" type="text/css"
runat="server" />
<resource rel="stylesheet" type="text/css" href="http://images001.xxx.com/css/header/header_v32.css"
runat="server" />

<resource language="JavaScript" type="text/javascript" src="http://images.xxx.com//js/jquery.1.3.2.js" runat="server"></resource>

</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<resource language="javascript" type="text/javascript" src="http://images.xxx.com/JS/JScript/FootV2.js"
runat="server"></resource>
<resource rel="stylesheet" type="text/css" href="http://images001.xxx.com/css/my/v3/1m/site_sms_messages.css"
runat="server" />
<div>
页面
</div>
<uc1:WebUserControl1 ID="WebUserControl11" runat="server" />
</asp:Content>

配置文件加上httomodule和handler:

<?xml version="1.0"?>
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<!--
设置 compilation debug="true" 可将调试符号插入
已编译的页面中。但由于这会
影响性能,因此只在开发过程中将此值
设置为 true。
-->
<compilation debug="true">
</compilation>
<!--
通过 <authentication> 节可以配置 ASP.NET 用来
识别进入用户的
安全身份验证模式。
-->
<authentication mode="Windows"/>
<!--
如果在执行请求的过程中出现未处理的错误,
则通过 <customErrors> 节可以配置相应的处理步骤。具体说来,
开发人员通过该节可以配置
要显示的 html 错误页
以代替错误堆栈跟踪。

<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
-->

<httpModules>
<add name="StaticResourceMergeResourceCollectorModule" type="StaticResourceMerge.Core.ResourceCollectorModule, StaticResourceMerge.Core"/>
</httpModules>
<httpHandlers>
<add path="ResourceHandler.ashx" type="StaticResourceMerge.Core.ResourceHandler, StaticResourceMerge.Core" verb="GET,HEAD"/>
</httpHandlers>
</system.web>
</configuration>

在httpmodule中可以遍历找出页面上所有静态资源然后生成合并后的script和style

代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Diagnostics;
using System.Web.Security;

namespace StaticResourceMerge.Core
{
public class ResourceCollectorModule : IHttpModule
{
private List<HtmlGenericControl> controls = new List<HtmlGenericControl>();

public void Dispose()
{

}

public void Init(HttpApplication application)
{
application.PreRequestHandlerExecute += new EventHandler(application_PreRequestHandlerExecute);
}

private void application_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext httpContext = HttpContext.Current;
Page page = httpContext.Handler as Page;
if (page != null)
{
page.PreRender += new EventHandler(page_PreRender);
}
}

private void page_PreRender(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();
Page page = sender as Page;
if (page != null)
{
FindResource(page);
if (controls.Count <= 0)
return;

foreach (HtmlGenericControl c in controls)
{
if (c.Parent != null)
c.Parent.Controls.Remove(c);
}

string pageTypeName = page.GetType().Name;
if (MergedResourceCache.Data[pageTypeName] == null)
{
StringBuilder stylePath = new StringBuilder();
StringBuilder scriptPath = new StringBuilder();
List<ResourceItem> rawStyleItems = new List<ResourceItem>();
List<ResourceItem> rawScriptItems = new List<ResourceItem>();

foreach (HtmlGenericControl c in controls)
{
if (!string.IsNullOrEmpty(c.Attributes["href"])
&& rawStyleItems.Find(d => d.Url == c.Attributes["href"].ToString()) == null)
{
var item = new ResourceItem
{
ResourceItemType = ResourceItemType.Style,
Url = c.Attributes["href"].ToString()
};
stylePath.Append(item.Url);
rawStyleItems.Add(item);
}

if (!string.IsNullOrEmpty(c.Attributes["src"])
&& rawScriptItems.Find(d => d.Url == c.Attributes["src"].ToString()) == null)
{
var item = new ResourceItem
{
ResourceItemType = ResourceItemType.Script,
Url = c.Attributes["src"].ToString()
};
scriptPath.Append(item);
rawScriptItems.Add(item);
}
}

string styleKey = FormsAuthentication.HashPasswordForStoringInConfigFile(stylePath.ToString(), "md5");
string scriptKey = FormsAuthentication.HashPasswordForStoringInConfigFile(scriptPath.ToString(), "md5");

RawResourceCache.Data[styleKey] = rawStyleItems;
RawResourceCache.Data[scriptKey] = rawScriptItems;

ResourceItem mergedStyle = new ResourceItem
{
ResourceItemType = ResourceItemType.Style,
Url = styleKey
};

ResourceItem mergedScript = new ResourceItem
{
ResourceItemType = ResourceItemType.Script,
Url = scriptKey
};

MergedResourceCache.Data[pageTypeName] = new List<ResourceItem>();
MergedResourceCache.Data[pageTypeName].Add(mergedStyle);
MergedResourceCache.Data[pageTypeName].Add(mergedScript);
}

if (page.Form != null && MergedResourceCache.Data[pageTypeName] != null)
{
foreach (var item in MergedResourceCache.Data[pageTypeName])
{
if (item.ResourceItemType == ResourceItemType.Style)
{
HtmlGenericControl style = new HtmlGenericControl();
style.TagName = "link";
style.Attributes.Add("href", string.Format("{0}?url={1}", page.ResolveUrl(ConfigProvider.ResourceHandlerPath), item.Url));
style.Attributes.Add("rel", "stylesheet");
style.Attributes.Add("type", "text/css");
page.Form.Controls.AddAt(0, style);
}
if (item.ResourceItemType == ResourceItemType.Script)
{
HtmlGenericControl script = new HtmlGenericControl();
script.TagName = "script";
script.Attributes.Add("src", string.Format("{0}?url={1}", page.ResolveUrl(ConfigProvider.ResourceHandlerPath), item.Url));
script.Attributes.Add("type", "text/javascript");
page.Form.Controls.AddAt(page.Form.Controls.Count, script);
}
}
}
}
page.Response.Write(sw.ElapsedMilliseconds);
}

private void FindResource(Control c)
{
if (c.Controls.Count > 0)
{
foreach (Control child in c.Controls)
FindResource(child);
}

HtmlGenericControl genericControl = c as HtmlGenericControl;
if (genericControl != null && genericControl.TagName.Equals("resource", StringComparison.InvariantCultureIgnoreCase))
{
controls.Add(genericControl);
}
}
}
}

然后在handler中生成合并后的静态资源:

在这里我们从网络上获取资源可以改为本地,这样速度快点

为了简单这里没有做合并后的文件缓存和输出缓存

代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO;
using System.Net;
using System.Threading;

namespace StaticResourceMerge.Core
{
class ResourceHandler : IHttpHandler
{
private StringBuilder data = new StringBuilder();
private int finished = 0;
private object locker = new object();

public bool IsReusable
{
get
{
return false;
}
}

public void ProcessRequest(HttpContext context)
{
string key = context.Request.QueryString["url"];
if (string.IsNullOrEmpty(key)) return;
List<ResourceItem> items = RawResourceCache.Data[key];
if (items == null) return;

foreach (var item in items)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri(item.Url), item.Url);
}

while (finished != items.Count)
Thread.Sleep(100);
context.Response.Write(data.ToString());
}

private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
lock (locker)
{
finished++;
data.AppendFormat("/******** {0} ********/{1}{2}{3}", e.UserState.ToString(), Environment.NewLine, e.Result, Environment.NewLine);
}
}
}
}

整个测试项目下载:
http://files.cnblogs.com/lovecherry/StaticResourceMerge.rar





2
0
0
(请您对文章做出评价)

« 上一篇:如何把ASP.NET TRACE HANDLER的信息持久化保存

posted on 2010-04-01 10:03 lovecherry 阅读(1191) 评论(6) 编辑 收藏 所属分类: .net 3.5



Feedback

1792303

#1楼 2010-04-01 10:27 沙加

好麻烦,还不如用 google closure tool 之类的东西一下就合并完了,不仅能合并还能优化,效率比这高。
回复 引用 查看

#2楼 2010-04-01 10:33 LanceZhang
很精彩!这样做的话脚本的客户端缓存好控制吗?

如果用的是asp.net 3.5 sp1或以上版本的话,<scriptmanager>里有个<CompositeScript>也可以实现脚本合并。

/article/4704792.html
Script Combining一节
回复 引用 查看

#3楼 2010-04-01 10:53 大力bober
不错的想法
回复 引用 查看

#4楼 2010-04-01 15:40 LanceZhang
引用沙加:好麻烦,还不如用 google closure tool 之类的东西一下就合并完了,不仅能合并还能优化,效率比这高。

关键是,如果脚本变动比较频繁的话就很麻烦了

其实也可以考虑把脚本合并工具配置一下,使其在网站发布之前自动执行,用这个插件好像可以的
http://www.microsoft.com/downloads/details.aspx?FamilyId=0AA30AE8-C73B-4BDD-BB1B-FE697256C459&displaylang=en

回复 引用 查看

#5楼 2010-04-01 17:17 沙加
@LanceZhang
我们都是在每次线上布置的时候跑一下合并脚本,不会直接大量修改生产代码吧
回复 引用 查看

#6楼 2010-04-01 22:19 Teddy's Knowledge Base
没必要造轮子,可以看看这个项目:
http://combres.codeplex.com/
回复 引用 查看
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: