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文件.这就导致在服务器带宽一定的情况下,多用户并发访问速度变慢.如何加 ...
随机推荐
- 如何成为一位优秀的创业CEO
英文原文:How to Be Startup CEO 编者按:本文来自 Ryan Allis,是一位来自旧金山的创业者和投资人.在 2003 年创立了 iContact,并任 CEO. 做创业公司的 ...
- RESTful 接口规范
原文地址:http://www.coderli.com/translate-restful-standard-resolved OneCoder最近一直在使用Restful API,最近正好看到一篇自 ...
- tcpdump交叉编译及使用
第一步.下载 官方网站:http://www.tcpdump.org/ 需要下载libpcap包和tcpdump包 我下载的版本是:libpcap-1.4.0.tar.gz和tcpdump-4.4.0 ...
- 将Sql Server迁移到Always on集群 - 账号的同步
Always on环境的建立,网上资料很多,主要是windows集群的建立以及Sql Server Always on的建立,略 容易忽略的是Sql server账号同步问题(Always on能实现 ...
- Linux内核源码详解——命令篇之iostat[zz]
本文主要分析了Linux的iostat命令的源码,iostat的主要功能见博客:性能测试进阶指南——基础篇之磁盘IO iostat源码共563行,应该算是Linux系统命令代码比较少的了.源代码中主要 ...
- 很好的一篇讲LTP在编解码中的作用的文章
原文链接 LONG-TERM PREDICTION by: Adit Aviv Kfir Grichman introduction: The speech signal has been ...
- Ext JS 6 新特性和工具
Ext JS 6 新特性和工具 Ext JS 6 带来很多新特性.工具和改进.以下是一些亮点: • 合并了 Ext JS & Sencha Touch - 在 Ext 6, 你可以访问 Ext ...
- Android adt v22.6.2 自动创建 appcompat_v7 解决方法,最低版本2.2也不会出现
Android 开发工具升级到22.6.2在创建工程时只要选择的最低版本低于4.0,就会自动生成一个项目appcompat_v7,没创建一个新的项目都会自动创建,很是烦恼... 之前在网上也找过方法, ...
- 构建基于WinRT的WP8.1 App 02:数据绑定新特性
基于WinRT的Windows Phone 8.1以及Windows 8.1中Xaml数据绑定增加了一些新特性. FallBackValue属性:FallBackValue在绑定的值属性值不存在时,可 ...
- Mac OSX Versions输入username按1下都会出现2个字符,并且不能create,解决方法
我的系统,安装的versions1.3.2,下载地址:http://www.jb51.net/softs/193467.html 安装好了以后Versions输入username按1下都会出现2个字符 ...