JS&CSS文件请求合并及压缩处理研究(三)
上篇我们进行了一些代码方面的准备工作。接下来的逻辑是:在View页面解析时,通过 Html.AppendResFile 方法添加的资源文件,我们需要按照分组、优先级,文件名等条件,对其路径进行合并。具体的合并规则如下:
(1),优先级高的文件优先渲染。
假如页面中有以下文件添加代码:
//添加样式文件A
Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleA]");
//添加样式文件B,但设置了高优先级
Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleB]","",PriorityType.High);
对于 styleB 来说,虽然较 styleA 后添加,但由于指定其优先级为 High(styleA为默认优先级:Normal),则路径合并之后,其位置反而提前。
合并后路径示例如下:
<link href="/Resource/style?href=[Content/Styles/styleB,styleA]&compress" type="text/css" rel="stylesheet" charset="utf-8" />
PS:在Resource/style处理程序中,我们会按照这个顺序对文件进行合并及压缩。
(2), 不同的分组分开渲染。
假如页面中:
Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleA]","groupA");
Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleB]","groupB");
经过路径合并后渲染为:
<link href="/Resource/style?href=[Content/Styles/styleA]&compress" type="text/css" rel="stylesheet" charset="utf-8" />
<link href="/Resource/style?href=[Content/Styles/styleB]&compress" type="text/css" rel="stylesheet" charset="utf-8" />
(3),相同文件夹下的文件,合并渲染。
这一点在上述(1)中就有所体现,同属文件夹 Content/Styles 下的styleA,styleB,路径合并后会位于同一个[]中。
(4),可同时添加多个资源文件。
Html.AppendResFile(ResourceType.Style, "[folderA/A1,A2,A3],[folderB/B1],[folderC/C1]");
路径合并后为:
<link href="/Resource/style?href=[folderA/A1,A2,A3][folderB/B1][folderC/C1]&compress" type="text/css" rel="stylesheet" charset="utf-8" />
通过上述合并规则,结合 Resource/style 端的处理,可以对资源文件的加载进行智能且精确的控制。
下面我们参照上述规则,继续完善 CombineResourceExt 类。在开始之前,需要先解决一个问题:
我们调用Html.AppendResFile方法添加的文件,肯定需求临时保存起来,以供最后统一合并路径并输出。那么我们将其保存在何处?
这里我们的选择是:HtmlHelper 对象的 HttpContext.Items,(对应ASP.NET为HttpContext.Current.Items)。
HttpContext.Items 仅在一次Http会话中存活。对于ASP.NET应用程序,多用于IHttpModule 和 IHttpHandler 之间的共享数据。
我们可以方便的以键值对的形式向其存放数据。当然,由于 HttpContext.Items 的流动性,体积大的数据其实是不适合保存在其中的。
解决了数据存放的问题,下面看代码:
/// <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 = "", PriorityType order = PriorityType.Normal)
{
var pathArray = QueryToFileList(url);
AppendResFile(htmlHelper, resType, pathArray, 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 = "", PriorityType order = PriorityType.Normal)
{
Dictionary<string, ResourceInfo> resFiles;
var urlArray = urls.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
if (urlArray.Length == )
{
return;
}
var key = String.Format("{0}_{1}", resType, 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);
}
foreach (var urlItem in urlArray)
{
if (resFiles.Keys.Contains(urlItem)) //如果已添加,则更新分组和优先级信息
{
resFiles[urlItem].Group = @group;
resFiles[urlItem].Order = order;
}
else
{
resFiles.Add(urlItem, new ResourceInfo { Url = urlItem, Group = @group, Order = order });
}
}
htmlHelper.ViewContext.HttpContext.Items[key] = resFiles;
} /// <summary>
/// 请求参数拆分成为文件列表
/// </summary>
/// <param name="queryContent"></param>
/// <returns></returns>
private static string[] QueryToFileList(string queryContent)
{
var originQuery = queryContent.Replace("\\", "/").Replace(" ", "");
var pathSegments = new List<string>();
var filePathBuilder = new StringBuilder();
var isCurrentCharInPathGroup = false;
foreach (char currentChar in originQuery)
{
if (currentChar == '[')
{
isCurrentCharInPathGroup = true;
filePathBuilder.Append(currentChar);
continue;
}
if (currentChar == ']')
{
isCurrentCharInPathGroup = false;
filePathBuilder.Append(currentChar);
pathSegments.Add(filePathBuilder.ToString());
filePathBuilder.Clear();
continue;
}
if (isCurrentCharInPathGroup)
{
filePathBuilder.Append(currentChar);
continue;
}
if (currentChar == ',')
{
if (filePathBuilder.Length > )
{
pathSegments.Add(filePathBuilder.ToString());
filePathBuilder.Clear();
}
continue;
}
filePathBuilder.Append(currentChar);
}
if (filePathBuilder.Length > )
{
pathSegments.Add(filePathBuilder.ToString());
filePathBuilder.Clear();
} var paths = new List<string>();
foreach (var pathSegment in pathSegments)
{
if (pathSegment.StartsWith("[") && pathSegment.EndsWith("]"))
{
var clearedPathSegment = pathSegment.TrimStart('[').TrimEnd(']');
var splitterIndex = clearedPathSegment.LastIndexOf('/');
var pathPrefix = clearedPathSegment.Substring(, splitterIndex + );
var pathContents = clearedPathSegment.Substring(splitterIndex + ).Split(','); paths.AddRange(pathContents.Select(pathContent => pathPrefix + pathContent));
}
else
{
paths.Add(pathSegment);
}
} return paths.Distinct().ToArray();
}
有了上述逻辑,假如我们在页面中这样调用:
Html.AppendResFile(ResourceType.Style, "[folderA/A1,A2,A3],[folderB/B1],[folderC/C1]");
则会首先将url参数分拆成独立路径的数组:
["folderA/A1","folderA/A2","folderA/A3","folderB/B1","folderC/C1"]
然后在 AppendResFile 的重载方法中,我们对每一个单独的文件路径,构建 ResourceInfo 对象,并存放于一个字典。最后将包含所有 ResourceInfo 信息的字典,存放于
htmlHelper.ViewContext.HttpContext.Items 中。以供最后统一的路径合并。
细心的朋友可能已经发现,我们为 Append 方法定义了一个 ConAppendFileKey 常量。假如我们现在Append的是脚本资源,则htmlHelper.ViewContext.HttpContext.Items 中存放字典的键为:
String.Format("{0}_{1}", "Script", ConAppendFileKey);
Append样式文件则为:
String.Format("{0}_{1}", "StyleSheet", ConAppendFileKey);
之所以定义这个键,是因为我们在开发中有可能会在需要的时候移除之前添加的某个资源或资源分组,那么我们就需要定义一个键标识移除资源字典。另外,我们还定义了一个键标记添加的代码块。代码如下:
private const string ConAppendFileKey = "AppendFileKey";
private const string ConRemoveFileKey = "RemoveFileKey";
private const string ConRemoveGroupKey = "RemoveGroupKey";
private const string ConScriptBlockKey = "ScriptBlockKey";
我们继续添加向CombineResourceExt类中添加移除资源的代码:
/// <summary>
/// 移除资源文件
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="resType">资源类型</param>
/// <param name="url">移除文件路径,可以为空或null</param>
/// <param name="group">移除文件所在分组,可以为null</param>
public static void RemoveResFile(this HtmlHelper htmlHelper, ResourceType resType, string url, string group = "")
{
var urlArray = QueryToFileList(url);
RemoveResFile(htmlHelper, resType, urlArray, 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(); //按照地址移除
if (urlArray.Length > )
{
List<string> removeFileKeys;
var 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);
}
foreach (var urlItem in urlArray)
{
var url = urlItem.Trim().ToLower();
if (!removeFileKeys.Contains(url))
{
removeFileKeys.Add(url);
}
}
} //按照group移除
if (!string.IsNullOrEmpty(group))
{
List<string> removeGroupKeys;
var 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);
}
}
}
可以看到,我们依然将标记为移除的资源字典存放于htmlHelper.ViewContext.HttpContext.Items中,同时,允许按url地址移除和分组移除。
对于添加代码块的功能:
/// <summary>
/// 添加内嵌脚本或样式
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="resType"></param>
/// <param name="script"></param>
public static void AppendScriptBlock(this HtmlHelper htmlHelper, ResourceType resType, string script)
{
var key = string.Format("{0}_{1}", ConScriptBlockKey, resType);
if (htmlHelper.ViewContext.HttpContext.Items.Contains(key))
{
var sb = htmlHelper.ViewContext.HttpContext.Items[key] as StringBuilder;
sb.Append(script);
}
else
{
var sb = new StringBuilder();
sb.Append(script);
htmlHelper.ViewContext.HttpContext.Items[key] = sb;
}
}
在最终的合并之前,让我们先整理一下HttpContext.Items中都存放了哪些数据(以脚本文件为例):
- Items["Script_AppendFileKey"],value为一个字典,存放了所有调用AppendResFile方法添加的脚本资源信息。
- Items["Script_RemoveFileKey"],value为一个字典,存放了所有标记为已移除脚本资源的信息。
- Items["Script_RemoveGroupKey"],value为一个字典,存放了所有标记为已移除脚本分组的信息。
- Items["Script_ScriptBlockKey"],value为一个StringBuilder,存放了所有添加的代码块信息。
接下来,我们按照之前列出的合并规则进行路径合并。首先需要取出 Items["Script_AppendFileKey"] 中所有的资源信息,然后过滤掉 Items["Script_RemoveFileKey"] 和 Items["Script_RemoveGroupKey"],再对过滤后的数据进行合并处理。代码如下:
/// <summary>
/// 输出合并后路径
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="resType">资源文件类型</param>
/// <returns></returns>
public static MvcHtmlString RenderResFile(this HtmlHelper htmlHelper, ResourceType resType)
{
var keyAppend = string.Format("{0}_{1}", resType, ConAppendFileKey);
var keyRemove = string.Format("{0}_{1}", resType, ConRemoveFileKey);
var keyRemoveGroup = string.Format("{0}_{1}", resType, ConRemoveGroupKey);
var keyScriptBlock = string.Format("{0}_{1}", ConScriptBlockKey, resType); var content = new StringBuilder();
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyAppend))
{
var resFiles = htmlHelper.ViewContext.HttpContext.Items[keyAppend] as Dictionary<string, ResourceInfo>;
//取出已标记的移除的资源文件
var removeFileKey = new List<string>();
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemove))
{
removeFileKey = htmlHelper.ViewContext.HttpContext.Items[keyRemove] as List<string>;
}
//取出已标记的移除的资源分组
var removeGroupKey = new List<string>();
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemoveGroup))
{
removeGroupKey = htmlHelper.ViewContext.HttpContext.Items[keyRemoveGroup] as List<string>;
}
//过滤资源文件(不包含标记为已移除的文件和分组)
var files = resFiles.Select(x => x.Value)
.Where(x => !removeFileKey.Contains(x.Url) && !removeGroupKey.Contains(x.Group)).ToList(); var jsGroupFiles = files.OrderByDescending(x => x.Group).GroupBy(x => x.Group); //按分组输出合并后的资源文件
foreach (IGrouping<string, ResourceInfo> item in jsGroupFiles)
{
var resPath = CombinePath(item.ToArray());
switch (resType)
{
case ResourceType.Script:
content.Append(string.Format("<script type=\"text/JavaScript\" src=\"Resource/script?href={0}&compress\"></script>", resPath));
break;
case ResourceType.StyleSheet:
content.Append(string.Format("<link href=\"Resource/style?href={0}&compress\" type=\"text/css\" rel=\"stylesheet\" charset=\"utf-8\" />", resPath));
break;
}
}
}
//输出样式或者脚本文件代码块
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyScriptBlock))
{
var script = htmlHelper.ViewContext.HttpContext.Items[keyScriptBlock] as StringBuilder;
switch (resType)
{
case ResourceType.Script:
content.Append(string.Format("<script type=\"text/JavaScript\">{0}</script>", script));
break;
case ResourceType.StyleSheet:
content.Append(string.Format("<style type=\"text/css\">{0}</style>", script));
break;
}
}
return new MvcHtmlString(content.ToString());
} /// <summary>
/// 合并资源文件路径
/// </summary>
/// <param name="items"></param>
/// <returns></returns>
private static string CombinePath(ResourceInfo[] items)
{
if (!items.Any()) return String.Empty;
//按优先级分组,控制优先级高的先输出
var orderGroup = items.OrderBy(x => x.Order).GroupBy(x => x.Order).ToArray();
var sb = new StringBuilder();
foreach (var item in orderGroup)
{
var order = ;
var files = item.Select(x =>
{
var lastIndex = x.Url.LastIndexOf('/');
var prefix = x.Url.Substring(, lastIndex);
var fileName = x.Url.Substring(lastIndex + );
return new { Prfx = prefix, FileName = fileName, FileOrder = order++ };
}).OrderBy(x => x.FileOrder); //按资源所属文件夹分组
//假如有两个文件:A/a.js,A/b.js,属于同一个文件夹
//则最终输出为:[A/a,b]
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[].Prfx + "/" + list[].FileName);
for (var i = ; i < list.Length; i++)
{
sb.Append("," + list[i].FileName);
}
sb.Append("]");
}
}
return sb.ToString();
}
关键的地方已添加了注释。
最终,我们在页面中调用:
@Html.RenderResFile(ResourceType.Script)
//或者
@Html.RenderResFile(ResourceType.StyleSheet)
则会相应的输出合并后的资源文件路径。
简单的测试一下:
Html.AppendResFile(ResourceType.Script, "[Scripts/common/jquery]");
Html.AppendResFile(ResourceType.Script, "[Scripts/functionA/A1],[Scripts/functionB/B1]");
Html.AppendResFile(ResourceType.Script, "[Scripts/functionA/A2],[Scripts/functionB/B2]");
合并后的路径为:
<script type="text/JavaScript" src="/Resource/script?href=[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]&compress"></script>
CombineResourceExt 的完整代码为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Mcmurphy.Component;
using Mcmurphy.Component.Enumeration; namespace Mcmurphy.Extension
{
/// <summary>
/// 资源文件合并扩展
/// </summary>
public static class CombineResourceExt
{
private const string ConAppendFileKey = "AppendFileKey";
private const string ConRemoveFileKey = "RemoveFileKey";
private const string ConRemoveGroupKey = "RemoveGroupKey";
private const string ConScriptBlockKey = "ScriptBlockKey"; /// <summary>
/// 合并资源文件路径
/// </summary>
/// <param name="items"></param>
/// <returns></returns>
private static string CombinePath(ResourceInfo[] items)
{
if (!items.Any()) return String.Empty;
//按优先级分组,控制优先级高的先输出
var orderGroup = items.OrderBy(x => x.Order).GroupBy(x => x.Order).ToArray();
var sb = new StringBuilder();
foreach (var item in orderGroup)
{
var order = ;
var files = item.Select(x =>
{
var lastIndex = x.Url.LastIndexOf('/');
var prefix = x.Url.Substring(, lastIndex);
var fileName = x.Url.Substring(lastIndex + );
return new { Prfx = prefix, FileName = fileName, FileOrder = order++ };
}).OrderBy(x => x.FileOrder); //按资源所属文件夹分组
//假如有两个文件:A/a.js,A/b.js,属于同一个文件夹
//则最终输出为:[A/a,b]
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[].Prfx + "/" + list[].FileName);
for (var i = ; i < list.Length; i++)
{
sb.Append("," + list[i].FileName);
}
sb.Append("]");
}
}
return sb.ToString();
} /// <summary>
/// 请求参数拆分成为文件列表
/// </summary>
/// <param name="queryContent"></param>
/// <returns></returns>
private static string[] QueryToFileList(string queryContent)
{
var originQuery = queryContent.Replace("\\", "/").Replace(" ", "");
var pathSegments = new List<string>();
var filePathBuilder = new StringBuilder();
var isCurrentCharInPathGroup = false;
foreach (char currentChar in originQuery)
{
if (currentChar == '[')
{
isCurrentCharInPathGroup = true;
filePathBuilder.Append(currentChar);
continue;
}
if (currentChar == ']')
{
isCurrentCharInPathGroup = false;
filePathBuilder.Append(currentChar);
pathSegments.Add(filePathBuilder.ToString());
filePathBuilder.Clear();
continue;
}
if (isCurrentCharInPathGroup)
{
filePathBuilder.Append(currentChar);
continue;
}
if (currentChar == ',')
{
if (filePathBuilder.Length > )
{
pathSegments.Add(filePathBuilder.ToString());
filePathBuilder.Clear();
}
continue;
}
filePathBuilder.Append(currentChar);
}
if (filePathBuilder.Length > )
{
pathSegments.Add(filePathBuilder.ToString());
filePathBuilder.Clear();
} var paths = new List<string>();
foreach (var pathSegment in pathSegments)
{
if (pathSegment.StartsWith("[") && pathSegment.EndsWith("]"))
{
var clearedPathSegment = pathSegment.TrimStart('[').TrimEnd(']');
var splitterIndex = clearedPathSegment.LastIndexOf('/');
var pathPrefix = clearedPathSegment.Substring(, splitterIndex + );
var pathContents = clearedPathSegment.Substring(splitterIndex + ).Split(','); paths.AddRange(pathContents.Select(pathContent => pathPrefix + pathContent));
}
else
{
paths.Add(pathSegment);
}
} return paths.Distinct().ToArray();
} /// <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 = "", PriorityType order = PriorityType.Normal)
{
var pathArray = QueryToFileList(url);
AppendResFile(htmlHelper, resType, pathArray, 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>
private static void AppendResFile(this HtmlHelper htmlHelper, ResourceType resType, string[] urls, string group = "", PriorityType order = PriorityType.Normal)
{
Dictionary<string, ResourceInfo> resFiles;
var urlArray = urls.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
if (urlArray.Length == )
{
return;
}
var key = String.Format("{0}_{1}", resType, 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);
}
foreach (var urlItem in urlArray)
{
if (resFiles.Keys.Contains(urlItem)) //如果已添加,则更新分组和优先级信息
{
resFiles[urlItem].Group = @group;
resFiles[urlItem].Order = order;
}
else
{
resFiles.Add(urlItem, new ResourceInfo { Url = urlItem, Group = @group, Order = order });
}
}
htmlHelper.ViewContext.HttpContext.Items[key] = resFiles;
} /// <summary>
/// 移除资源文件
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="resType">资源类型</param>
/// <param name="url">移除文件路径,可以为空或null</param>
/// <param name="group">移除文件所在分组,可以为null</param>
public static void RemoveResFile(this HtmlHelper htmlHelper, ResourceType resType, string url, string group = "")
{
var urlArray = QueryToFileList(url);
RemoveResFile(htmlHelper, resType, urlArray, 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(); //按照地址移除
if (urlArray.Length > )
{
List<string> removeFileKeys;
var 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);
}
foreach (var urlItem in urlArray)
{
var url = urlItem.Trim().ToLower();
if (!removeFileKeys.Contains(url))
{
removeFileKeys.Add(url);
}
}
} //按照group移除
if (!string.IsNullOrEmpty(group))
{
List<string> removeGroupKeys;
var 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);
}
}
} /// <summary>
/// 添加内嵌脚本或样式
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="resType"></param>
/// <param name="script"></param>
public static void AppendScriptBlock(this HtmlHelper htmlHelper, ResourceType resType, string script)
{
var key = string.Format("{0}_{1}", ConScriptBlockKey, resType);
if (htmlHelper.ViewContext.HttpContext.Items.Contains(key))
{
var sb = htmlHelper.ViewContext.HttpContext.Items[key] as StringBuilder;
sb.Append(script);
}
else
{
var sb = new StringBuilder();
sb.Append(script);
htmlHelper.ViewContext.HttpContext.Items[key] = sb;
}
} /// <summary>
/// 输出合并后路径
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="resType">资源文件类型</param>
/// <returns></returns>
public static MvcHtmlString RenderResFile(this HtmlHelper htmlHelper, ResourceType resType)
{
var keyAppend = string.Format("{0}_{1}", resType, ConAppendFileKey);
var keyRemove = string.Format("{0}_{1}", resType, ConRemoveFileKey);
var keyRemoveGroup = string.Format("{0}_{1}", resType, ConRemoveGroupKey);
var keyScriptBlock = string.Format("{0}_{1}", ConScriptBlockKey, resType); var content = new StringBuilder();
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyAppend))
{
var resFiles = htmlHelper.ViewContext.HttpContext.Items[keyAppend] as Dictionary<string, ResourceInfo>;
//取出已标记的移除的资源文件
var removeFileKey = new List<string>();
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemove))
{
removeFileKey = htmlHelper.ViewContext.HttpContext.Items[keyRemove] as List<string>;
}
//取出已标记的移除的资源分组
var removeGroupKey = new List<string>();
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemoveGroup))
{
removeGroupKey = htmlHelper.ViewContext.HttpContext.Items[keyRemoveGroup] as List<string>;
}
//过滤资源文件(不包含标记为已移除的文件和分组)
var files = resFiles.Select(x => x.Value)
.Where(x => !removeFileKey.Contains(x.Url) && !removeGroupKey.Contains(x.Group)).ToList(); var jsGroupFiles = files.OrderByDescending(x => x.Group).GroupBy(x => x.Group); //按分组输出合并后的资源文件
foreach (IGrouping<string, ResourceInfo> item in jsGroupFiles)
{
var resPath = CombinePath(item.ToArray());
switch (resType)
{
case ResourceType.Script:
content.Append(string.Format("<script type=\"text/JavaScript\" src=\"Resource/script?href={0}&compress\"></script>", resPath));
break;
case ResourceType.StyleSheet:
content.Append(string.Format("<link href=\"Resource/style?href={0}&compress\" type=\"text/css\" rel=\"stylesheet\" charset=\"utf-8\" />", resPath));
break;
}
}
}
//输出样式或者脚本文件代码块
if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyScriptBlock))
{
var script = htmlHelper.ViewContext.HttpContext.Items[keyScriptBlock] as StringBuilder;
switch (resType)
{
case ResourceType.Script:
content.Append(string.Format("<script type=\"text/JavaScript\">{0}</script>", script));
break;
case ResourceType.StyleSheet:
content.Append(string.Format("<style type=\"text/css\">{0}</style>", script));
break;
}
}
return new MvcHtmlString(content.ToString());
}
}
}
接下来鄙人会创建一个简单的单元测试项目,对之前列出的合并规则进行相应的验证。
JS&CSS文件请求合并及压缩处理研究(三)的更多相关文章
- JS&CSS文件请求合并及压缩处理研究(五)
接上篇.在我们最终调用 @Html.RenderResFile(ResourceType.Script) 或者 @Html.RenderResFile(ResourceType.StyleSheet) ...
- JS&CSS文件请求合并及压缩处理研究(一)
在我们日常的网站开发工作中,一个页面难免会引用到各种样式及脚本文件.了解Web开发的朋友们都知道,页面引用的每一个: <link href="style.css" rel=& ...
- JS&CSS文件请求合并及压缩处理研究(四)
本篇将会尝试对之前的代码进行相关的单元测试,验证路径合并规则的覆盖率及正确性. 熟悉 ASP.NET MVC 开发的朋友应该知道,微软在MVC框架下集成了一款名为 Microsoft.VisualSt ...
- JS&CSS文件请求合并及压缩处理研究(二)
上篇交待了一些理论方面的东西,并给出了另外一种解决方案的处理流程.本篇将根据该处理流程,开始代码方面的编写工作. 1,打开VS,新建ASP.NET MVC Web项目,项目类型选择空.名称为 Mcmu ...
- ASP.NET MVC 4 Optimization的JS/CSS文件动态合并及压缩
JS/CSS文件的打包合并(Bundling)及压缩(Minification)是指将多个JS或CSS文件打包合并成一个文件,并在网站发布之后进行压缩,从而减少HTTP请求次数,提高网络加载速度和页面 ...
- 开箱即用 - Grunt合并和压缩 js,css 文件
js,css 文件合并与压缩 Grunt 是前端自动化构建工具,类似webpack. 它究竟有多强悍,请看它的 介绍. 这里只演示如何用它的皮毛功能:文件合并与压缩. 首先说下js,css 合并与压缩 ...
- Web性能优化之动态合并JS/CSS文件并缓存客户端
来源:微信公众号CodeL 在Web开发过程中,会产生很多的js/css文件,传统的引用外部文件的方式会产生多次的http请求,从而加重服务器负担且网页加载缓慢,如何在一次请求中将多个文件一次加载出来 ...
- 前端js,css文件合并三种方式,bat命令
前端js,css文件合并三种方式,bat命令 前端js文件该如何合并三个方式如下:1. 一个大文件,所有js合并成一个大文件,所有页面都引用它.2. 各个页面大文件,各自页面合并生成自己所需js的大文 ...
- 使用PHP和GZip压缩网站JS/CSS文件加速网站访问速度
使用PHP和GZip压缩网站JS/CSS文件加速网站访问速度 一些泛WEB 2.0网站为了追求用户体验,可能会大量使用CSS和JS文件.这就导致在服务器带宽一定的情况下,多用户并发访问速度变慢.如何加 ...
随机推荐
- layer-list实现只有左、右和下边框的圆角矩形
项目中需要实现如下效果的布局 也就是一个左右下角带圆角,上方不带圆角的白色背景矩形,而且只有左.右和下边框,颜色为浅灰色. 当然,切一个.9图片作为背景也能实现,但是能用代码实现的还是尽量用代码实现, ...
- Zabbix监控Windows事件日志
1.zabbix_agentd.win文件修改: LogFile=c:\zabbix\zabbix_agentd.log Server=1.16.2.4 ServerActive=1.16.2.4 H ...
- android对象关系映射框架ormlite之一对多(OneToMany)
前两天,用ormlite对单张表进行了基本的操作,但是,我们知道通常情况对于单张表格进行操作在实际情况中很前两天不现实,那么ormlite能否像Hibenate那样实现多张表之间的一对多,多对多(即O ...
- ZookeeperNet太难用,写了个RetryHelper来进行配套使用
普通的zk用法,如下写法: zk.Exists("/aaa", true); zk.Create(...); 但是由于这些API会抛Zookeeper的Exception,比如Co ...
- NYOJ 741 "数学家"ST
"数学家"ST 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描写叙述 ST是个非常特别的人类,不仅喜欢做一些不同平常的事,并且对于在无聊的时候怎样打发时 ...
- 把C编译成javascript的方法
把C编译成javascript的方法,便于嵌入到HTML5中 https://github.com/kripken/emscripten
- 2.C#中泛型在方法Method上的实现
阅读目录 一:C#中泛型在方法Method上的实现 把Persion类型序列化为XML格式的字符串,把Book类型序列化为XML格式的字符串,但是只写一份代码,而不是public static s ...
- android studio 中查找代码中的硬编码
在Android Studio中同时按下Ctrl + Shift+ F 或者其他自定义的快捷键,打开全局搜索,在全局搜索中输入 ^((?!(\*|//)).)+[\u4e00-\u9fa5] 并打勾 ...
- Oracle-ARCGIS-SDE 数据整合遇到的问题
一. 近日在做全文检索,基础采用oracle text,版本是10g,做好管理页面后,有功能是删除索引,就是生成drop index的语句.没有想到这个全文检索的index这么直接弄还不行,经过这样删 ...
- React 根据官方总结的规范
1.语法上,根据生命周期方法执行的顺序编写代码 (1 生命周期方法[getDefaultProps, getInitialState, componentWillMount, componentDid ...