httpmodule和httphandler配合的又一应用——合并脚本样式
页面上数十个脚本和样式文件怎么去合并又少写人工干预?
首先,使用查找替换把所有的<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);
}
}
}
}
整个测试项目下载:
https://files.cnblogs.com/lovecherry/StaticResourceMerge.rar