巧用HttpHandler管理Xslt
来博客园差不多一年多的时间了,一直都是潜水。感觉自己的知识很单薄,只能努力向各位大牛学习。今天鼓起万分勇气,抱着与大家一起分享的愿望,写了这片文章。还望大家多多拍砖。
好了,废话不多说。进入主题,这篇文章不是介绍xslt的使用方法,关于这类文章,随便在Google,百度一面一搜就是一打。
我想介绍的是,如果用HttpHandler来管理xslt的。有这样的一个想法,主要是来源于公司今年推出的一个产品(选才网)中有好几处都采用了xml+xslt来实现的。
采用xslt的优点我就不重复了,而缺点不知道大家有没有和我一样的感受。不太容易配置。我这里指的的配置,是指,比如说,xslt里面需要引用某个JS,而这个JS的路径根据开发环境和生产环境的不同,路径也会不同。还有就是子系统一多,系统直接访问就可能会牵扯到跨域的问题,这么一来还要在相关xslt文件里面加上Domain的脚本,这样一来,每次发布的时候,都需要来来回回到处配置。我是很讨厌太多配置的,并且还是分布在很多不同的位置。
所以产生了写这么个handler的想法,主要是用来解决这么4个问题:
1. 能够自动为指定Xslt加上Domain。
2. 能够自动为指定Xslt加上指定的一个或多个JS。
3. 能够给Xslt做一下压缩(去空格,去换行)。
4. 能够控制一下客户端缓存。
那么定义Xml中xml-stylesheet连接地址为:xslt.axd?path=~/Common/XSLT/CV/resume.xslt&include=jquery.js&v=1.6.0.27201&age=2592000
path: 就是指定的xslt文件的路径。
include: 需要引用的JS的名称。多个用半角逗号隔开。这里我没用用全路径。考虑到怕JS一多,路径一长,导致url超过限制了。
v: 这个主要是用于强制更新客户端缓存而用的。
age: 这个就是指定客户端缓存的生命期。以秒为单位。加入这个主要是考虑到,每个XSLT根据用途的不同,可能缓存的时间也是不同的。
那么介绍就这么多, 接下来就直接贴代码了。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
1 public class XsltHandler : IHttpHandler
2 {
3 private const string INCLUDE_SCRIPT = "<script type=\"text/javascript\" src=\"{0}\" ></script>";
4 private const string INCLUDE_SCRIPT_PATH_PRE = "http://localhost/JS/jquery/";
5 private const string INCLUDE_DOMAIN_SCRIPT = "<script type=\"text/javascript\">document.domain=\"{0}\"</script>";
6 private static string Domain = String.Empty;
7
8
9 public XsltHandler()
10 {
11 Domain = GetCurrentDomain();
12 }
13
14 public void ProcessRequest(HttpContext context) {
15 string fileName = context.Request.QueryString["path"];
16 string include = context.Request.QueryString["include"];
17 string age = context.Request.QueryString["age"];
18 string xslt;
19 string includeScripts = string.Empty;
20 int maxAge = int.MinValue;
21
22 if (!fileName.EndsWith("xslt", StringComparison.OrdinalIgnoreCase))
23 throw new SecurityException("Invalid Xslt file extension");
24
25 if (!string.IsNullOrEmpty(include)) {
26 string[] includes = include.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
27 foreach (string s in includes) {
28 if (s.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
29 includeScripts += string.Format(INCLUDE_SCRIPT, (INCLUDE_SCRIPT_PATH_PRE + s));
30 }
31 }
32
33 if (!string.IsNullOrEmpty(fileName)) {
34 string path = HttpContext.Current.Server.MapPath(fileName);
35 //检测缓存中是否已经存在该xslt
36
37 if (context.Cache[context.Request.RawUrl] == null) {
38 xslt = RetrieveLocalXslt(path, includeScripts);
39 context.Cache.Insert(HttpContext.Current.Request.RawUrl, xslt, new CacheDependency(path));
40 }
41 else {
42 xslt = (string)context.Cache[context.Request.RawUrl];
43 }
44
45 SetHeaders(path, context, age);
46 context.Response.Write(xslt);
47
48 if (true) //可做配置项
49 Compress(context);
50 }
51 else
52 {
53 context.Response.StatusCode = (int)HttpStatusCode.NotFound;
54 context.Response.Status = "404 Bad Request";
55 }
56
57 }
58
59 /// <summary>
60 /// 获取当前Domain
61 /// </summary>
62 /// <returns>当前请求的Url中的Domain。如:xuancai.com</returns>
63 private static string GetCurrentDomain()
64 {
65 HttpRequest request = HttpContext.Current.Request;
66 Uri uri = request.Url;
67 if (uri.HostNameType == UriHostNameType.Dns)
68 {
69 // 假定域名符合如下格式:*.domain,如:www.ata.net.cn、www.xuancai.com。
70 if (Regex.IsMatch(uri.DnsSafeHost, "\\w+(\\.\\w+){2,}", RegexOptions.Singleline))
71 return Regex.Replace(uri.DnsSafeHost, Regex.Match(uri.DnsSafeHost, "(\\w+\\.)?", RegexOptions.Singleline).Value, String.Empty);
72 if (!request.IsLocal)
73 return uri.DnsSafeHost;
74 }
75 return string.Empty;
76 }
77
78 /// <summary>
79 /// 读取本地的xslt文件,并做相关处理。如:去空格,去换行符,加入域,加入需要的js文件等等。
80 /// </summary>
81 /// <param name="path">xslt文件路径</param>
82 /// <param name="include">需要引入的js的字符串 <code><![CDATA[<script type="text/javascript" src="http://localhost/JS/jquery.js"></script]]></code> </param>
83 /// <returns>经过处理的xslt字符串</returns>
84 private static string RetrieveLocalXslt(string path, string include) {
85 try {
86 string xslt;
87 using (var reader = new StreamReader(path)) {
88 xslt = reader.ReadToEnd();
89
90 xslt = StripWhitespace(xslt);
91
92 xslt = xslt.Insert(xslt.LastIndexOf("</body>"), include);
93
94 if (true && !string.IsNullOrEmpty(Domain)) //可配置是否需要设置Domain。
95 xslt = xslt.Insert(xslt.IndexOf("</title>") + 8, string.Format(INCLUDE_DOMAIN_SCRIPT, Domain));
96 }
97 return xslt;
98 }
99 catch {
100 return string.Empty;
101 }
102 }
103
104 /// <summary>
105 /// 去掉xslt中的换行符,空格,注释。
106 /// </summary>
107 /// <param name="body">xslt body</param>
108 /// <returns>去空格后的xslt</returns>
109 private static string StripWhitespace(string body) {
110 body = body.Replace(" ", String.Empty);
111 body = body.Replace("\t", string.Empty);
112 body = body.Replace(Environment.NewLine, String.Empty);
113 return body;
114 }
115
116 /// <summary>
117 /// 设置Response Header
118 /// </summary>
119 /// <param name="path">xslt文件路径</param>
120 /// <param name="context">HttpContext</param>
121 /// <param name="age">需要缓存的时间(单位:秒)</param>
122 private static void SetHeaders(string path, HttpContext context, string age) {
123 context.Response.ContentType = "text/xml";
124 context.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
125
126 //设置对应的xslt依赖
127 context.Response.AddFileDependency(path);
128 context.Response.Cache.SetETagFromFileDependencies();
129 context.Response.Cache.SetLastModifiedFromFileDependencies();
130 context.Response.Cache.SetSlidingExpiration(true);
131
132 int maxAge;
133 if (!string.IsNullOrEmpty(age) && int.TryParse(age, out maxAge)) {
134 context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddSeconds(maxAge));
135 context.Response.Cache.SetMaxAge(TimeSpan.FromSeconds(maxAge));
136 context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
137 }
138
139 string incoming = context.Request.Headers["If-Modified-Since"];
140 //比较客户端缓存中的Modify Time是否与服务器上一样,如果一样则直接输出304。
141 if (incoming != null && DateTime.Parse(incoming).ToShortTimeString() == File.GetLastWriteTime(path).ToShortTimeString()) {
142 context.Response.Clear();
143 context.Response.StatusCode = (int)HttpStatusCode.NotModified;
144 context.Response.SuppressContent = true;
145 }
146 }
147
148 #region Compression
149
150 private const string GZIP = "gzip";
151 private const string DEFLATE = "deflate";
152
153 private static void Compress(HttpContext context) {
154 if (context.Request.UserAgent != null && context.Request.UserAgent.Contains("MSIE 6"))
155 return;
156
157 if (IsEncodingAccepted(GZIP)) {
158 context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
159 SetEncoding(GZIP);
160 }
161 else if (IsEncodingAccepted(DEFLATE)) {
162 context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
163 SetEncoding(DEFLATE);
164 }
165 }
166
167 /// <summary>
168 /// 检查是否接受编码
169 /// </summary>
170 private static bool IsEncodingAccepted(string encoding) {
171 return HttpContext.Current.Request.Headers["Accept-encoding"] != null && HttpContext.Current.Request.Headers["Accept-encoding"].Contains(encoding);
172 }
173
174 /// <summary>
175 /// 设置编码header标签
176 /// </summary>
177 /// <param name="encoding"></param>
178 private static void SetEncoding(string encoding) {
179 HttpContext.Current.Response.AppendHeader("Content-encoding", encoding);
180 }
181
182 #endregion
183
184 public bool IsReusable {
185 get { return false; }
186 }
187 }
不知道大家是否能够看明白。嘿嘿,第一次写文章,肯定有很多问题。希望大家能够多多指点。也希望能给和我遇到一样问题的同志们有那么一点点帮助。
代码中也有部分注释。如果有朋友没看太明白的,可以提出问题,我尽力解释。
我的MSN是:Wright.Jin@Hotmail.com 欢迎大家一起交流