接上篇。在我们最终调用 @Html.RenderResFile(ResourceType.Script) 或者 @Html.RenderResFile(ResourceType.StyleSheet) 将页面中添加的文件路径合并成类似以下格式后:

<script type="text/JavaScript" src="Resource/script?href=[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]&compress"></script>

接下来需要做的就是在 Resource/script 中接收压缩后的路径href参数,按顺序读取服务器存取的资源文件,合并,压缩(如果有compress参数传入的话),输出至客户端。有了基本的流程和思路,代码实现其实没有什么难度。看代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Mcmurphy.Common; namespace Mcmurphy.Web.Controllers
{
/// <summary>
/// 资源文件请求处理
/// </summary>
public class ResourceController : Controller
{
/// <summary>
/// 处理脚本文件
/// </summary>
/// <returns></returns>
public void Script()
{
//原始Url链接
var rawUri = new Uri(Request.Url.Scheme + "://" + Request.Url.Host + Request.RawUrl);
//是否压缩参数
var isCompressJs = rawUri.Query.IndexOf("&compress", StringComparison.OrdinalIgnoreCase) >=
|| rawUri.Query.IndexOf("?compress", StringComparison.OrdinalIgnoreCase) >= ;
var queryHref = Request["href"];
//如果参数为空或者参数中包含有危险字符
if (string.IsNullOrEmpty(queryHref) || !UrlFilter.FiltUrl(Server.UrlDecode(rawUri.Query)))
{
Response.Write("请求参数错误!");
}
try
{
//请求参数拆分成文件列表
var jsPaths = PathHelper.QueryToFileList(queryHref, ".js");
//转成成物理文件列表
jsPaths = PathHelper.GetPhysicPaths(jsPaths, Request);
//获取合并后的文件内容(如果标识isCompressJs == true,则需要压缩)
var finalJsContent = JsCombiner.CombineJs(jsPaths, Server.MapPath("~/"), isCompressJs);
//替换内容中模板(就简单的替换了一下时间)
finalJsContent = finalJsContent.Replace("{{now}}", DateTime.Now.ToString("yyyyMMddHHmmss"));
//输出字节流
Response.ContentType = "text/javascript";
Response.BinaryWrite(EncodingHelper.ConvertToUTF8BomEncodingStringBytes(finalJsContent));
Response.AddHeader("Vary", " Accept-Encoding");
}
catch (Exception ex)
{
Response.Write(ex.StackTrace);
}
} /// <summary>
/// 处理样式文件
/// </summary>
/// <returns></returns>
public void Style()
{
var rawUri = new Uri(Request.Url.Scheme + "://" + Request.Url.Host + Request.RawUrl);
//是否压缩参数
var isCompressJs = rawUri.Query.IndexOf("&compress", StringComparison.OrdinalIgnoreCase) >=
|| rawUri.Query.IndexOf("?compress", StringComparison.OrdinalIgnoreCase) >= ;
var queryHref = Request["href"];
if (string.IsNullOrEmpty(queryHref) || !UrlFilter.FiltUrl(Server.UrlDecode(rawUri.Query)))
{
Response.Write("请求参数错误!");
}
try
{
//请求参数拆分成文件列表
var cssPaths = PathHelper.QueryToFileList(queryHref, ".css");
//转成成物理文件列表
cssPaths = PathHelper.GetPhysicPaths(cssPaths,Request);
//获取合并后的文件内容
var finalCssContent = CssCombiner.CombineAndCompressCss(cssPaths, Server.MapPath("~/"), isCompressJs);
//输出字节流
Response.ContentType = "text/css";
Response.BinaryWrite(EncodingHelper.ConvertToUTF8BomEncodingStringBytes(finalCssContent));
Response.AddHeader("Vary", " Accept-Encoding"); }
catch (Exception ex)
{
Response.Write(ex.StackTrace);
}
}
}
}

以处理脚本文件的Script Action为例:

1,首先对接收的参数进行常规的空值及危险字符检查。

检查是否存在危险字符的UrlFilter.FiltUrl方法代码为

namespace Mcmurphy.Common
{
public class UrlFilter
{
/// <summary>
/// 判断是否有特殊字符
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static bool FiltUrl(string url)
{
if (url.Contains("&_="))
{
return false;
}
if (url.Contains("(") || url.Contains(")"))
{
return false;
}
if (url.Contains("{") || url.Contains("}"))
{
return false;
}
if (url.Contains(";") || url.Contains(":"))
{
return false;
} return true;
}
}
}

2,将获取到的诸如“[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]”类型的路径参数,拆分为单独的路径数组,并获取其真实的物理路径,以便读取内容。

3,调用JsCombiner.CombineJs方法,传入物理路径集合,对脚本文件进行合并。如果标记压缩,则需要进行压缩处理。
其中,JsCombiner.CombineJs 方法代码为:

namespace Mcmurphy.Common
{
public class JsCombiner
{
/// <summary>
/// 拼接js文件内容
/// </summary>
/// <param name="jsPaths"></param>
/// <param name="serverPath"></param>
/// <param name="isCompress"></param>
/// <returns></returns>
public static string CombineJs(string[] jsPaths, string serverPath, bool isCompress)
{
var jsBuilder = new StringBuilder();
foreach (string path in jsPaths)
{
if (File.Exists(path))
{
var sourceFileContent = AutoDetectEncodingFileReader.ReadFileContent(path, Encoding.Default);
if (string.IsNullOrEmpty(sourceFileContent) == false)
{
if (isCompress)
{
var compressor = new JavaScriptCompressor();
//ASCII, BigEndianUnicode, Unicode, UTF32, UTF7, UTF8, Default (default).
compressor.Encoding = Encoding.UTF8;
//True (default) | False. True => Obfuscate function and variable names
compressor.ObfuscateJavascript = true;
//True | False (default). True => compress any functions that contain 'eval'. Default is False, which means a function that contains
compressor.IgnoreEval = false;
//True | False (default).
compressor.DisableOptimizations = false;
//True | False (default). True => preserve redundant semicolons (e.g. after a '}'
compressor.PreserveAllSemicolons = false;
//The position where a line feed is appened when the next semicolon is reached.
compressor.LineBreakPosition = -;
sourceFileContent = compressor.Compress(sourceFileContent);
}
jsBuilder.Append(sourceFileContent);
}
}
else
{
jsBuilder.Append(string.Format("/*未找到文件{0}*/", path.Replace(serverPath, "")));
}
}
return jsBuilder.ToString();
}
}
}

由上述代码可以看到,对脚本文件的压缩,我们调用了 YUI Compressor for .Net 这个第三方类库。

YUI Compressor for .Net 为脚本的压缩提供了丰富的选项,比如是否进行代码混淆,是否自动在'}'后添加分号,是否进行代码优化,错误日志记录,文件编码等。上面的代码仅仅是最常规的应用。更多信息可以参考其开源地址:http://yuicompressor.codeplex.com/,在该页面的 documentation 标签下,可以查看到更多的使用说明。

需要说明的是,在调用最新的YUI Compressor for .Net 对脚本文件进行压缩时,需要添加三个引用:

EcmaScript.NET.dll
Yahoo.Yui.Compressor.Build.MsBuild.dll
Yahoo.Yui.Compressor.dll

4,将合并[压缩]后的内容,以UTF-8 BOM的字符流形式输出至客户端。

其中EncodingHelper.ConvertToUTF8BomEncodingStringBytes代码为:

namespace Mcmurphy.Common
{
public static class EncodingHelper
{
/// <summary>
/// 为字符串添加BOM头信息
/// 让客户端正确识别UTF-8编码
/// </summary>
/// <param name="originalString"></param>
/// <returns></returns>
public static byte[] ConvertToUTF8BomEncodingStringBytes(string originalString)
{
var bom = Encoding.UTF8.GetPreamble();
var content = Encoding.UTF8.GetBytes(originalString);
var resultBytes = new byte[bom.Length + content.Length];
bom.CopyTo(resultBytes, );
content.CopyTo(resultBytes, bom.Length);
return resultBytes;
}
}
}

接下来我们定位到Mcmurphy.Web项目,在Scripts文件夹下添加几个脚本文件,测试一下。

Scripts/common/jquery.js   

Scripts/functionA/A1.js :
$(function() {
console.log("folder:functionA,name:A1.js");
}); Scripts/functionA/A2.js :
$(function() {
console.log("folder:functionA,name:A2.js");
}); Scripts/functionB/B1.js :
$(function () {
console.log("folder:functionB,name:B1.js");
}); Scripts/functionB/B2.js :
$(function () {
console.log("folder:functionB,name:B2.js");
});

然后我们将 Views/Shared/_Layout.cshtml 布局文件修改为:

<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
@{
Html.AppendResFile(ResourceType.StyleSheet, "[Content/common]");
Html.AppendResFile(ResourceType.Script, "[Scripts/common/jquery]"); @RenderSection("Head_Section", false)
}
@Html.RenderResFile(ResourceType.StyleSheet)
</head> <body>
@RenderBody() @RenderSection("Foot_Section", false) @Html.RenderResFile(ResourceType.Script)
</body>
</html>

再定位到 Views/Home/Index.cshtml 文件,修改其内容为:

@{
ViewBag.Title = "Index";
}
@section Head_Section{
@{
//添加样式文件A
Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleA]");
//添加样式文件B,但设置了高优先级
Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleB]","",PriorityType.High); //添加脚本文件functionA/A1,functionB/B1
Html.AppendResFile(ResourceType.Script, "[Scripts/functionA/A1],[Scripts/functionB/B1]");
}
}
@section Foot_Section{
@{
//添加脚本文件functionA/A2,functionB/B2
Html.AppendResFile(ResourceType.Script, "[Scripts/functionA/A2],[Scripts/functionB/B2]");
}
}
<h2>Index</h2>

(关于ASP.NET MVC及Razor,在此不再赘述)

Okay,Ctrl + F5 直接运行。查看浏览器网络请求及控制台

可以看到已合并的样式及脚本文件网络请求,而控制台也得到了正确的输出。

当然,也可以直接在浏览器地址栏输出Url,查看到合并[压缩]后的脚本或样式文件。

http://localhost:17509/Resource/Script?href=[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]&compress

或者:

http://localhost:17509/Resource/Style?href=[Content/Styles/styleB][Content/common][Content/Styles/styleA]&compress

至此关于JS&CSS文件的请求合并及压缩处理的实现就告一段落。当然,在此优化的基础上其实还有很多其它方面的工作可做。比如可将Resource/[Script][Style]的处理,放到独立的资源服务器,可以得到额外的CDN的好处。另外,诸如脚本样式等文件通常都为静态资源,并不会经常变动,可以考虑在服务器做一些缓存及版本号方面的控制等。

最后附上全部源码种子:

MvcResourceHandle.rar

JS&CSS文件请求合并及压缩处理研究(五)的更多相关文章

  1. JS&CSS文件请求合并及压缩处理研究(三)

    上篇我们进行了一些代码方面的准备工作.接下来的逻辑是:在View页面解析时,通过 Html.AppendResFile 方法添加的资源文件,我们需要按照分组.优先级,文件名等条件,对其路径进行合并.具 ...

  2. JS&CSS文件请求合并及压缩处理研究(一)

    在我们日常的网站开发工作中,一个页面难免会引用到各种样式及脚本文件.了解Web开发的朋友们都知道,页面引用的每一个: <link href="style.css" rel=& ...

  3. JS&CSS文件请求合并及压缩处理研究(四)

    本篇将会尝试对之前的代码进行相关的单元测试,验证路径合并规则的覆盖率及正确性. 熟悉 ASP.NET MVC 开发的朋友应该知道,微软在MVC框架下集成了一款名为 Microsoft.VisualSt ...

  4. JS&CSS文件请求合并及压缩处理研究(二)

    上篇交待了一些理论方面的东西,并给出了另外一种解决方案的处理流程.本篇将根据该处理流程,开始代码方面的编写工作. 1,打开VS,新建ASP.NET MVC Web项目,项目类型选择空.名称为 Mcmu ...

  5. ASP.NET MVC 4 Optimization的JS/CSS文件动态合并及压缩

    JS/CSS文件的打包合并(Bundling)及压缩(Minification)是指将多个JS或CSS文件打包合并成一个文件,并在网站发布之后进行压缩,从而减少HTTP请求次数,提高网络加载速度和页面 ...

  6. 开箱即用 - Grunt合并和压缩 js,css 文件

    js,css 文件合并与压缩 Grunt 是前端自动化构建工具,类似webpack. 它究竟有多强悍,请看它的 介绍. 这里只演示如何用它的皮毛功能:文件合并与压缩. 首先说下js,css 合并与压缩 ...

  7. Web性能优化之动态合并JS/CSS文件并缓存客户端

    来源:微信公众号CodeL 在Web开发过程中,会产生很多的js/css文件,传统的引用外部文件的方式会产生多次的http请求,从而加重服务器负担且网页加载缓慢,如何在一次请求中将多个文件一次加载出来 ...

  8. 前端js,css文件合并三种方式,bat命令

    前端js,css文件合并三种方式,bat命令 前端js文件该如何合并三个方式如下:1. 一个大文件,所有js合并成一个大文件,所有页面都引用它.2. 各个页面大文件,各自页面合并生成自己所需js的大文 ...

  9. 使用PHP和GZip压缩网站JS/CSS文件加速网站访问速度

    使用PHP和GZip压缩网站JS/CSS文件加速网站访问速度 一些泛WEB 2.0网站为了追求用户体验,可能会大量使用CSS和JS文件.这就导致在服务器带宽一定的情况下,多用户并发访问速度变慢.如何加 ...

随机推荐

  1. zookeeper 的多线程和单线程库使用对比

    zookeeper提供了两个库,zookeeper_st和 zookeeper_mt. 前者是单线程库,仅仅提供了异步API和集成在应用程序实现循环中的回调函数,这个库是为了支持pthread库不支持 ...

  2. 3年的坚持,最终造就著作——《Learninghard C#学习笔记》

    前言 起初开始写博文主要是记录学习过程中对学到内容的自我总结和理解,同时也希望本人的理解可以帮助到一些走在学习路上的朋友.但是令我没有想到的是,我总结的博文得到了广大园友的评论和支持,正是博友的支持, ...

  3. 【Python】调用WPS V9 API,实现Word转PDF

    WPS 的API,即COM,主要分为V8与V9两个版本,网上容易查到的例子,都是V8的. 现在官网上可以下载的,2013抢鲜版,就是V9的API. Python 调用COM 需要安装 Python f ...

  4. 【腾讯Bugly干货分享】iOS10 SiriKit QQ适配详解

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ece0331288fb4d31137da6 1. 概述 苹果在iOS10开放 ...

  5. 作业七:团队项目——Alpha版本冲刺阶段-11

    部分功能实现: public void actionPerformed(ActionEvent ae) { //重新开始按钮 if (ae.getSource().equals(anew)){ int ...

  6. printf背后的故事

    printf背后的故事 说起编程语言,C语言大家再熟悉不过.说起最简单的代码,Helloworld更是众所周知.一条简单的printf语句便可以完成这个简单的功能,可是printf背后到底做了什么事情 ...

  7. 在.net中使用aquiles访问Cassandra(二)

    上文中我们已经建立了项目的基本结构,今天实现数据的修改.在NoSQL中,通常添加和修改都认为是对数据的一种Mutation.   1.建立描述修改Row的实体. public class RowMut ...

  8. java 锁2

    并发,其实是多线程才有的场景... java 多线程? 锁? 现在看来,即使已经工作了4.5年,这仍然不是一个简单的问题. 其实java 本身有提供锁的机制. 比如 Object对象的 wait .n ...

  9. 为什么PCB上的单端阻抗控制50欧姆

    很多刚接触阻抗的人都会有这个疑问,为什么常见的板内单端走线都是默认要求按照50欧姆来管控而不是40欧姆或者60欧姆?这是一个看似简单但又不 好回答的问题.在写这篇文章前我们也查找了很多资料,其中最有知 ...

  10. CSS3与页面布局学习总结(四)——页面布局大全BFC、定位、浮动、7种垂直居中方法

    目录 一.BFC与IFC 1.1.BFC与IFC概要 1.2.如何产生BFC 1.3.BFC的作用与特点 二.定位 2.2.relative 2.3.absolute 2.4.fixed 2.5.z- ...