由于默认的 ASP.NET MVC 模板使用了 Bundle 技术,大家开始接受并喜欢上这种技术。Bundle 技术通过 Micorosoft.AspNet.Web.Optimization 包实现,如果在 ASP.NET WebForm 项目中引入这个包及其依赖包,在 ASP.NET WebForm 项目中使用 Bundle 技术也非常容易。

创新互联专注于镇安企业网站建设,响应式网站开发,购物商城网站建设。镇安网站建设公司,为镇安等地区提供建站服务。全流程按需求定制网站,专业设计,全程项目跟踪,创新互联专业和态度为您提供的服务
关于在 WebForm 中使用 Bundle 技术的简短说明
通过 NuGet 很容易在 WebForm 项目中引入
Microsoft.AspNet.Web.Optimization包及其依赖包。不过在 MVC 项目的 Razor 页面中可以使用类似下面的语句引入资源@Scripts.Render("...")而在
*.aspx页面中则需要通过<%= %>来引入了:<%@ Import Namespace="System.Web.Optimization" %> // ... <%= Scripts.Render("...") %>备注 有些资料中是使用的
<%: %>,我实在没有发现它和<%= %>有啥区别,但至少我在《ASP.NET Reference》的《Code Render Blocks》一节找到了<%= %>,却暂时没在官方文档里找到<%: %>
然后,我在一个使用了 EasyUI 的项目中使用了 Bundle 技术。才开始一切正常,至到第一个 Release 版本测试的那一天,“血案”发生了——
由于一个脚本错误,EasyUI 没有生效。最终原因是 Bunlde 在 Release 版中将 EasyUI 的脚本压缩了——当然,定位到这个原因还是经历了一翻周折,这就不细说了。
[方案一] 禁用代码压缩
这个解决方案理论上只需要在配置里加一句话就行:
BundleTable.EnableOptimizations = false;
但问题在于,这样一来,为了一个 EasyUI,就放弃了所有脚本的压缩,而仅仅只是合并,效果折半,只能当作万不得已的备选。
[方案二] 分段引入并阻止压缩 EasyUI 的 Bundle
先看看原本的 Bundle 配置(已简化)
public static void Register(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/libs")
.Include("~/scripts/jquery-{version}.js")
.Include("~/scripts/jquery.eaysui-{versoin}.js")
.Include("~/scripts/locale/easyui-lang-zh_CN.js")
.IncludeDirectory("~/scripts/app", "*.js", true)
);
}这段配置先引入了 jquery,再引入了 easyui,最后引入了一些为当前项目写的公共脚本。为了实现解决方案二,必须要改成分三个 Bundle 引入,同时还得想办法阻止压缩其中一个 Bundle。
要分段,简单
public static void Register(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/jquery")
.Include("~/scripts/jquery-{version}.js")
);
bundles.Add(new ScriptBundle("~/easyui")
.Include("~/scripts/jquery.eaysui-{versoin}.js")
.Include("~/scripts/locale/easyui-lang-zh_CN.js")
);
bundles.Add(new ScriptBundle("~/libs")
.IncludeDirectory("~/scripts/app", "*.js", true)
);
}但为了阻止压缩,查了文档,也搜索了不少资料都没找到解决办法,所以只好看源码分析了,请出 JetBrains dotPeek。分析代码之后得出结论,只需要去掉默认的 Transform 就行
// bundles.Add(new ScriptBundle("~/easyui")
// .Include("~/scripts/jquery.eaysui-{versoin}.js")
// .Include("~/scripts/locale/easyui-lang-zh_CN.js")
// );
Bundle easyuiBundle = new ScriptBundle("~/easyui")
.Include("~/scripts/jquery.eaysui-{versoin}.js")
.Include("~/scripts/locale/easyui-lang-zh_CN.js")
);
easyuiBundle.Transforms.Clear();
bundles.Add(easyuiBundle);关键代码的分析说明
首先从 ScriptBunlde 入手
public class ScriptBundle: Bundle { public ScriptBundle(string virtualPath) : this(virtualPath, (string) null) {} public ScriptBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath, (IBundleTransform) new JsMinify() ) { this.ConcatenationToken = ";" + Environment.NewLine; } }可以看出,ScriptBunlde 的构建最终是通过其基类 Bunlde 中带 IBunldeTransform 参数的那一个来构造的。再看 Bunlde 的关键代码
public class Bunlde public IListTransforms { get { return this._transforms; } } public Bundle( string virtualPath, string cdnPath, params IBundleTransform[] transforms ) { // ... foreach(IBundleTransform bundleTransform in transforms) { this._transforms.Add(bundleTransform); } } } 容易理解,ScriptBunlde 构建的时候往 Transforms 中添加了一默认的 Transform——JsMinify,从名字就可以看出来,这是用来压缩脚本的。而 IBundleTransform 只有一个接口方法
public interface IBundleTransform { void Process(BundleContext context, BundleResponse response); }看样子它是在处理 BundleResponse。而 BundleResponse 中定义有文本类型的 Content 和 ContentType 属性,以及一个 IEnumerable
Files。 为什么是 Files 而不是 File 呢,我猜 Content 中包含的是一个 Bundle 中所有文件的内容,而不是某一个文件的内容。要验证也很容易,自己实现个 IBundleTransform 试下就行了
Bundle b = new ScriptBundle("~/test") .Include(...) .Include(...); b.Transforms.Clear();b.Transforms.Add(new MyTransform()) // MyTransform 可以自由发挥,我其实啥都没写,只是在 Process 里打了个断点,检查了 response 的属性值而已实验证明在 BundleResponse 传入 Transforms 之前,其 Content 就已经有所有引入文件的内容了。
方案二解决了方案一不能解决的问题,但同时也带来了新问题。原来只需要一句话就能引入所有脚本
@Scripts.Render("~/libs")而现在需要 3 句话
@Scripts.Render("~/jquery")
@Scripts.Render("~/easyui")
@Scripts.Render("~/libs")[方案三] Bundle 的 Bundle
鉴于方案二带来的新问题,试想,如果有一个东西,能把 3 个 Bundle 对象组合起来,变成一个 Bundle 对象,岂不是就解决了?
于是,我发明了 Bundle 的 Bundle,不妨就叫 BundleBundle 吧。
public class BundleBundle : Bundle{
readonly List bundles = new List();
public BundleBundle(string virtualPath)
: base(virtualPath)
{
}
public BundleBundle Include(Bundle bundle)
{
bundles.Add(bundle);
return this;
}
// 在引入 Bundle 对象时申明清空 Transforms,这几乎就是为 EasyUI 准备的
public BundleBundle Include(Bundle bundle, bool isClearTransform)
{
if (isClearTransform)
{
bundle.Transforms.Clear();
}
bundles.Add(bundle);
return this;
}
public override BundleResponse GenerateBundleResponse(BundleContext context)
{
List allFiles = new List();
StringBuilder content = new StringBuilder();
string contentType = null;
foreach (Bundle b in bundles)
{
var r = b.GenerateBundleResponse(context);
content.Append(r.Content);
// 考虑到 BundleBundle 可能用于 CSS,所以这里进行一次判断,
// 只在 ScriptBundle 后面加分号(兼容 ASI 风格脚本)
// 这里可能会出现在已有分号的代码后面加分号的情况,
// 考虑到只会浪费 1 个字节,忍了
if (b is ScriptBundle)
{
content.Append(';');
}
content.AppendLine();
allFiles.AddRange(r.Files);
if (contentType == null)
{
contentType = r.ContentType;
}
}
var response = new BundleResponse(content.ToString(), allFiles);
response.ContentType = contentType;
return response;
}
} 使用 BundleBundle 也简单,就像这样
bundles.Add(new BundleBundle("~/libs")
.Include(new ScriptBundle("~/bundle/jquery")
.Include("~/scripts/jquery-{version}.js")
)
.Include(
new ScriptBundle("~/bundle/easyui")
.Include("~/scripts/jquery.easyui-{version}.js")
.Include("~/scripts/locale/easyui-lang-zh_CN.js")
)
.Include(new ScriptBundle("~/bundle/app")
.IncludeDirectory("~/scripts/app", "*.js", true)
)
);然后
@Scripts.Render("~/libs")注意,每个子 Bundle 都有名字,但这些名字不能直接给 @Scripts.Render() 使用,因为它们并没有直接加入 BundleTable.Bundles 中。但名字是必须的,而且不能是 null,不信就试试。
本文名称:Bundle小镇中由EasyUI引发的“血案”
文章出自:http://www.jxjierui.cn/article/jjsjhg.html


咨询
建站咨询
