web优化之-Asp.net MVC js、css动态合并 动态压缩 (2)
在前一篇文章web优化之-Asp.net MVC js、css动态合并 动态压缩中 的js和css的路径都是Scripts/jquery-1.5.1.js,Scripts/jquery.validate.js这在http请求时路 径比较长,为此我们可以改用[Scripts/jquery-1.5.1,jquery.validate]这种格式。同时为了防止文件更新后客户端无法刷新的问题我们加了version标记
修改后的代码:
CombineFiles:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Text; using System.IO; using Yahoo.Yui.Compressor; /// <summary> /// Summary description for CombineFiles /// </summary> public class CombineFiles : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/javascript"; HttpRequest request = context.Request; HttpResponse response = context.Response; string[] allkeys = request.QueryString.AllKeys; string version = string.Empty; if (allkeys.Contains("version")) { version = request.QueryString["version"].Trim().ToLower(); } if (!allkeys.Contains("href") || !allkeys.Contains("type") || !allkeys.Contains("compress")) { response.Write("请求格式不正确,正确格式是type=....&href=....&compress=...\r\n"); response.Write("type只能是js或则css,compress只能是true或则false,href则是请求的文件,多个文件已逗号分隔\r\n"); response.Write("示例如下:\r\n type=js&compress=true&href=[Scripts/jquery-1.5.1,jquery.validate][Scripts/MicrosoftAjax]"); } else { string cacheKey = request.Url.Query; #region /*确定合并文件类型*/ string fileType = request.QueryString["type"].Trim().ToLower(); string contenType = string.Empty; if (fileType.Equals("js")) { contenType = "text/javascript"; } else if (fileType.Equals("css")) { contenType = "text/css"; } /*确定合并文件类型*/ #endregion CacheItem cacheItem = HttpRuntime.Cache.Get(cacheKey) as CacheItem;//服务端缓存 if (cacheItem != null && !String.IsNullOrEmpty(cacheItem.Version) && !string.IsNullOrEmpty(version)) { if (cacheItem.Version != version) { cacheItem = null; } } if (cacheItem == null) { #region 合并压缩文件 /*合并文件*/ string href = context.Request.QueryString["href"].Trim(); string content = string.Empty; string[] files = GetFilePath(href); StringBuilder sb = new StringBuilder(); foreach (string fileName in files) { string filePath = context.Server.MapPath(fileName + "." + fileType); if (File.Exists(filePath)) { string readstr = File.ReadAllText(filePath, Encoding.UTF8); sb.Append(readstr); //content = JavaScriptCompressor.Compress(content); //response.Write(content); } else { sb.AppendLine("\r\n未找到源文件" + filePath + "\r\n"); } } content = sb.ToString(); /*合并文件*/ /*压缩文件*/ string compressStr = request.QueryString["compress"].Trim(); bool iscompress = bool.Parse(compressStr); if (iscompress) { if (fileType.Equals("js")) { //content = JavaScriptCompressor.Compress(content); content = (new JavaScriptCompressor()).Compress(content); } else if (fileType.Equals("css")) { // content = CssCompressor.Compress(content); content = (new CssCompressor()).Compress(content); } } /*压缩文件*/ #endregion cacheItem = new CacheItem() { Content = content, Expires = DateTime.Now.AddHours(1), Version = version }; HttpRuntime.Cache.Insert(cacheKey, cacheItem, null, cacheItem.Expires, TimeSpan.Zero); } response.ContentType = contenType; if (request.Headers["If-Modified-Since"] != null && TimeSpan.FromTicks(cacheItem.Expires.Ticks - DateTime.Parse(request.Headers["If-Modified-Since"]).Ticks).Seconds < 100) { response.StatusCode = 304; // response.Headers.Add("Content-Encoding", "gzip"); response.StatusDescription = "Not Modified"; } else { response.Write(cacheItem.Content); SetClientCaching(response, DateTime.Now); } } //合并文件结束 } string[] GetFilePath(string filesrc) { List<string> result = new List<string>(); string[] files = filesrc.Split(new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries); foreach (string file in files) { string[] srcs = file.Split(new char[] { ',' }); string first = srcs[0]; int index = first.LastIndexOf("/"); string prefx = first.Substring(0, index); result.Add(first); if (srcs.Length > 1) { srcs = srcs.Where((x, i) => { return i > 0; }).Select(x => prefx + "/" + x).ToArray(); result.AddRange(srcs); } } return result.ToArray(); } private void SetClientCaching(HttpResponse response, DateTime lastModified) { response.Cache.SetETag(lastModified.Ticks.ToString()); response.Cache.SetLastModified(lastModified); //public 以指定响应能由客户端和共享(代理)缓存进行缓存。 response.Cache.SetCacheability(HttpCacheability.Public); //是允许文档在被视为陈旧之前存在的最长绝对时间。 response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0)); //将缓存过期从绝对时间设置为可调时间 response.Cache.SetSlidingExpiration(true); } class CacheItem { public string Content { set; get; } public DateTime Expires { set; get; } public string Version { set; get; } } public bool IsReusable { get { return false; } } }
CombineResFile:
namespace System.Web.Mvc.Html { using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Text; using System.Collections; using System.IO; public enum ResOrderType { Highest = 1, High = 2, Normal = 3, Low = 4, Lowest = 5 } public enum ResourceType { /// <summary> /// CSS /// </summary> StyleSheet = 0, /// <summary> /// JS /// </summary> Script = 1 } public static class CombineResFile { class ResourceInfo { public string Url { set; get; } public string Group { set; get; } public ResOrderType Order { set; get; } } const string conAppendFileKey = "AppendFileKey"; const string conRemoveFileKey = "RemoveFileKey"; const string conRemoveGroupKey = "RemoveGroupKey"; /// <summary> /// 合并路径 /// </summary> /// <param name="paths"></param> /// <returns></returns> /// <summary> /// 添加资源文件 /// </summary> /// <param name="htmlHelper"></param> /// <param name="resType">资源类型</param> /// <param name="url">文件的路径</param> /// <param name="group">文件的分组名称</param> /// <param name="order">文件同组中的优先级,默认:Normal</param> public static void AppendResFile(this HtmlHelper htmlHelper, ResourceType resType, string url, string group = "", ResOrderType order = ResOrderType.Normal) { AppendResFile(htmlHelper, resType, GetFilePath(url), group, order); } /// <summary> /// 添加资源文件 /// </summary> /// <param name="htmlHelper"></param> /// <param name="resType">资源类型</param> /// <param name="urls">文件的路径列表,如“channel/fanbuxie/urlcommon”,不支[]限定符</param> /// <param name="group">文件的分组名称</param> /// <param name="order">文件同组中的优先级,默认:Normal</param> public static void AppendResFile(this HtmlHelper htmlHelper, ResourceType resType, string[] urls, string group = "", ResOrderType order = ResOrderType.Normal) { Dictionary<string, ResourceInfo> resFiles = null; var urlArray = urls.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray<string>(); if (urlArray == null || urlArray.Length == 0) { return; } string key = string.Format("{0}_{1}", resType.ToString(), conAppendFileKey); if (htmlHelper.ViewContext.HttpContext.Items.Contains(key)) { resFiles = htmlHelper.ViewContext.HttpContext.Items[key] as Dictionary<string, ResourceInfo>; } else { resFiles = new Dictionary<string, ResourceInfo>(); htmlHelper.ViewContext.HttpContext.Items.Add(key, resFiles); } for (int i = 0; i < urlArray.Length; i++) { if (resFiles.Keys.Contains(urlArray[i])) { resFiles[urlArray[i]].Group = group; resFiles[urlArray[i]].Order = order; } else { resFiles.Add(urlArray[i], new ResourceInfo() { Url = urlArray[i], Group = group, Order = order }); } } htmlHelper.ViewContext.HttpContext.Items[key] = resFiles; } /// <summary> /// 移除资源文件 /// </summary> /// <param name="resType">资源类型</param> /// <param name="htmlHelper"></param> /// <param name="urls">移除文件,可以为空或则null </param> /// <param name="group">移除文件所在的组可以为null</param> public static void RemoveResFile(this HtmlHelper htmlHelper, ResourceType resType, string url, string group = "") { RemoveResFile(htmlHelper, resType, GetFilePath(url), group); } /// <summary> /// 移除资源文件 /// </summary> /// <param name="resType">资源类型</param> /// <param name="htmlHelper"></param> /// <param name="urls">移除文件列表,可以为空或则null </param> /// <param name="group">移除文件所在的组可以为null</param> public static void RemoveResFile(this HtmlHelper htmlHelper, ResourceType resType, string[] urls, string group = "") { var urlArray = urls.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray<string>(); #region 按照js的地址移除 if (urlArray != null && urlArray.Length > 0) { List<string> removeFileKeys = null; string key = string.Format("{0}_{1}", resType.ToString(), conRemoveFileKey); if (htmlHelper.ViewContext.HttpContext.Items.Contains(key)) { removeFileKeys = htmlHelper.ViewContext.HttpContext.Items[key] as List<string>; } else { removeFileKeys = new List<string>(); htmlHelper.ViewContext.HttpContext.Items.Add(key, removeFileKeys); } for (int i = 0; i < urlArray.Length; i++) { var url = urlArray[i].Trim().ToLower(); if (!removeFileKeys.Contains(url)) { removeFileKeys.Add(url); } } } #endregion #region 按照js的group移除 if (!string.IsNullOrEmpty(group)) { List<string> removeGroupKeys = null; string keyGroup = string.Format("{0}_{1}", resType.ToString(), conRemoveGroupKey); if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyGroup)) { removeGroupKeys = htmlHelper.ViewContext.HttpContext.Items[keyGroup] as List<string>; } else { removeGroupKeys = new List<string>(); htmlHelper.ViewContext.HttpContext.Items.Add(keyGroup, removeGroupKeys); } if (!removeGroupKeys.Contains(group)) { removeGroupKeys.Add(group); } } #endregion } /// <summary> /// 输出js /// </summary> /// <param name="htmlHelper"></param> /// <returns></returns> public static MvcHtmlString RenderResFile(this HtmlHelper htmlHelper, ResourceType resType) { string keyAppend = string.Format("{0}_{1}", resType.ToString(), conAppendFileKey); string keyRemove = string.Format("{0}_{1}", resType.ToString(), conRemoveGroupKey); string keyRemoveGroup = string.Format("{0}_{1}", resType.ToString(), conRemoveGroupKey); Dictionary<string, ResourceInfo> resFiles = null; StringBuilder content = new StringBuilder(); if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyAppend)) { resFiles = htmlHelper.ViewContext.HttpContext.Items[keyAppend] as Dictionary<string, ResourceInfo>; List<string> removeFileKey = new List<string>(); if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemove)) { removeFileKey = htmlHelper.ViewContext.HttpContext.Items[keyRemove] as List<string>; } List<string> removeGroupKey = new List<string>(); if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemoveGroup)) { removeGroupKey = htmlHelper.ViewContext.HttpContext.Items[keyRemoveGroup] as List<string>; } List<ResourceInfo> files = resFiles.Select(x => x.Value) .Where(x => !removeFileKey.Contains(x.Url) && !removeGroupKey.Contains(x.Group)) .ToList<ResourceInfo>(); IEnumerable<IGrouping<string, ResourceInfo>> jsGroupFiles = files.OrderByDescending(x => x.Group).GroupBy(x => x.Group); foreach (IGrouping<string, ResourceInfo> item in jsGroupFiles) { var resPath = CombinePath(item.ToArray()); string version = "0"; //获取版本号 version = GetVersion(item.ToArray(), resType); switch (resType) { case ResourceType.StyleSheet: string cssformat = "<link charset=\"utf-8\" rel=\"stylesheet\" type=\"text/css\" href=\"/CombineFiles.ashx?type=js&compress=false&href={0}&version={1}\">"; content.Append(string.Format(cssformat, resPath, version)); break; case ResourceType.Script: string jsformat = "<script type=\"text/javascript\" src=\"/CombineFiles.ashx?type=js&compress=true&href={0}&version={1} \"></script>"; content.Append(string.Format(jsformat, resPath, version)); break; } } }//end if return new MvcHtmlString(content.ToString()); } static string CombinePath(ResourceInfo[] items) { var all = items.OrderBy(x => x.Order).GroupBy(x => x.Order).ToArray(); StringBuilder sb = new StringBuilder(); foreach (var item in all) { int order = 1; var files = item.Select(x => { int lastIndex = x.Url.LastIndexOf('/'); string prefix = x.Url.Substring(0, lastIndex); string fileName = x.Url.Substring(lastIndex + 1); return new { Prfx = prefix, FileName = fileName, FileOrder = order++ }; }).OrderBy(x => x.FileOrder); var keysgroup = files.GroupBy(x => x.Prfx).ToArray(); foreach (var key in keysgroup) { var list = files.Where(x => x.Prfx.Equals(key.Key)).ToArray(); sb.Append("[" + list[0].Prfx + "/" + list[0].FileName); string jsprfx = list[0].Prfx; for (int i = 1; i < list.Length; i++) { sb.Append("," + list[i].FileName); } sb.Append("]"); } } return sb.ToString(); } static string[] GetFilePath(string filesrc) { List<string> result = new List<string>(); string[] files = filesrc.Split(new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries); foreach (string file in files) { string[] srcs = file.Split(new char[] { ',' }); string first = srcs[0]; int index = first.LastIndexOf("/"); string prefx = first.Substring(0, index); result.Add(first); if (srcs.Length > 1) { srcs = srcs.Where((x, i) => { return i > 0; }).Select(x => prefx + "/" + x).ToArray(); result.AddRange(srcs); } } return result.ToArray(); } static string GetVersion(ResourceInfo[] items, ResourceType restype) { StringBuilder sb = new StringBuilder(); string ext = ".js"; if (restype == ResourceType.StyleSheet) { ext = ".css"; } foreach (ResourceInfo item in items) { string filename = item.Url + ext; string filepath = HttpContext.Current.Server.MapPath(filename); FileInfo file = new FileInfo(filepath); sb.Append(file.LastWriteTime.ToString("yyyyMMddHHmmss")); } string version = sb.ToString().GetHashCode().ToString(); version = version.Replace("-", string.Empty); ///Read web config /// return version; } } }
结果如图:
注意Yahoo.Yui.Compressor版本不同,调用方式也不同, 如果是在MVC4项目里,这里可以不用Yahoo.Yui.Compressor来压缩文件,可以用WebGrease.1.1.0\lib\WebGrease.dll中的Minifier类来实现压缩.代码如下:
CodeSettings codeSettings = new CodeSettings
{
EvalTreatment = EvalTreatment.MakeImmediateSafe,
PreserveImportantComments = false
};
content= (new Microsoft.Ajax.Utilities.Minifier()).MinifyJavaScript(content,codeSettings);
--------------------------------------------------------------------
CssSettings setting = new CssSettings() { CommentMode=CssComment.None };
content = (new Microsoft.Ajax.Utilities.Minifier()).MinifyStyleSheet(content,setting);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构