Bundling and Minification (ASP.NET 4.0)






4.89/5 (14投票s)
以可管理的方式实现打包和最小化
介绍
我知道现在讨论 ASP.NET 4.0 有点晚了,但我认为实现捆绑和最小化这个想法非常重要。我希望现在每个人都知道最小化页面资源和提高页面性能的重要性。本文不讨论捆绑和最小化为何重要,而是讨论如何在 ASP.NET 4.0 中有效地实现它。
捆绑和最小化对于网站性能至关重要。我们需要找到一种方法来使其易于管理,这样我们就无需手动进行捆绑和最小化。一旦定义,我们的代码应该会自动处理这些过程。
本文提供的代码示例已经有 1 年了(有一些修改)。我在一个项目中实现了这种管理捆绑和最小化方式,并认为应该分享这个想法。
实现捆绑和最小化的步骤
以下是使捆绑和最小化可管理的要点:
- 添加 Microsoft ASP.NET Web Optimization Framework:该框架用于创建文件的最小化版本以及指定的捆绑包。从 这里 下载并为您的项目添加文件引用。
- 创建一个 XML 文件来定义文件的捆绑。您可以为每个页面定义两个捆绑包,一个是所有页面都需要的通用文件捆绑包。第二个是页面特定文件的捆绑包。页面特定文件可能包括页面 JavaScript 和添加到页面上的所有控件的 JavaScript。
- 一旦在 XML 文件中定义了捆绑包,您就可以在 global.asax 页面中添加捆绑代码。此代码将负责创建文件捆绑和最小化。
- 将捆绑包引用添加到页面。如果调试模式为
false
,则只应将Bundle文件添加到页面。否则,应该将具有正常版本(非最小化)的每个单独文件添加到页面。这使得开发人员能够方便地调试 JavaScript 代码。
查看实际效果
让我们一步一步地实现所有步骤
1. 添加 Microsoft ASP.NET Web Optimization Framework
访问 https://nuget.net.cn/packages/microsoft.aspnet.web.optimization/
这将指导您如何将 System.Web.Optimization.dll 的引用添加到您的项目中。此类负责实际的捆绑和最小化。
2. 创建一个 XML 来添加捆绑定义,如下所示
<Bundling>
<Js Name="~/AdminJS.js">
<Path>~/scripts/jquery-1.9.0.min.js</Path>
<Path>~/scripts/AppCommon.js</Path>
<Path>~/scripts/jqGrid/jquery.jqGrid.min.js</Path>
<Path>~/Scripts/jqGrid/grid.locale-en.js</Path>
</Js>
<Css Name="~/AdminCss.css">
<Path>~/Styles/Site.css</Path>
</Css>
</Bundling>
上面的 XML 定义了主捆绑包及其子文件。同样,我们也可以用来创建 CSS 捆绑包。
您可能需要先定义 Js
、Css
和 Bundling
类,然后才能添加更多代码。这将允许您序列化 XML 以从 XML 文件读取捆绑文件详细信息。以下是这些类:
[Serializable]
public class Js
{
[XmlAttribute]
public string Name { get; set; }
[XmlElement]
public string[] Path { get; set; }
}
public class Css
{
[XmlAttribute]
public string Name { get; set; }
[XmlElement]
public string[] Path { get; set; }
}
[Serializable]
public class Bundling
{
[XmlElement]
public Js[] Js { get; set; }
[XmlElement]
public Css[] Css { get; set; }
}
3. 注册捆绑信息
我们已经定义了 JavaScript 文件及其捆绑包名称。让我们看看如何读取捆绑包信息并将其添加到内存中。
public void AddBundling()
{
string filePath = String.Empty;
string bundleName = String.Empty;
try
{
StreamReader reader = new StreamReader(HttpContext.Current.Server.MapPath
("~/Settings/FileBundling.xml"));
XmlSerializer serializer = new XmlSerializer(typeof(Bundling));
Bundling bundlingInfo = (Bundling)serializer.Deserialize(reader);
reader.Close();
reader.Dispose();
#region Bundling of the css files.
Bundle cssBundle;
foreach (Css css in bundlingInfo.Css)
{
cssBundle = new Bundle(css.Name);
foreach (string cssFile in css.Path)
{
cssBundle.Include(cssFile);
}
BundleTable.Bundles.Add(cssBundle);
}
#endregion
#region Bundling of the java script files.
Bundle jsBundle;
foreach (Js js in bundlingInfo.Js)
{
bundleName = js.Name;
jsBundle = new Bundle(js.Name);
foreach (string jsFile in js.Path)
{
filePath = jsFile;
jsBundle.Include(jsFile);
}
BundleTable.Bundles.Add(jsBundle);
}
#endregion
}
catch (Exception ex)
{
throw new Exception("There is a problem while creating Bundle", ex);
}
}
上面的代码将捆绑包添加到应用程序内存中。为了允许添加这些捆绑包的添加/创建,我们倾向于在 Application Start 事件中调用它们。这是代码:
void Application_Start(object sender, EventArgs e)
{
ApplicationSettingsHelper appSettings = new ApplicationSettingsHelper();
appSettings.AddBundling();
}
到目前为止,我们已经创建了 XML 文件中的捆绑定义。根据捆绑定义,我们在 Application Start 事件中将捆绑包添加到内存中。
这是通用代码,允许我们将脚本文件引用添加到页面:
public static void AddScriptFromBundle(string bundleName, Page varThis, string scriptKey)
{
StringBuilder strScript = new StringBuilder();
if (HttpContext.Current.IsDebuggingEnabled)
{
StreamReader reader = new StreamReader(HttpContext.Current.Server.MapPath
("~/Settings/FileBundling.xml"));
XmlSerializer serializer = new XmlSerializer(typeof(Bundling));
Bundling bundlingInfo = (Bundling)serializer.Deserialize(reader);
reader.Close();
reader.Dispose();
foreach (Js js in bundlingInfo.Js)
{
if (js.Name.Trim('~') == bundleName)
{
foreach (string jsFile in js.Path)
{
strScript.Append(String.Format("<script src=\"{0}?v=" +
ConfigurationManager.AppSettings["ScriptVersion"] +
"\" type=\"text/javascript\">
</script>", jsFile.Trim('~')));
}
break;
}
}
}
else
{
strScript.Append(String.Format("<script src=\"{0}?v=" +
ConfigurationManager.AppSettings["ScriptVersion"] +
"\" type=\"text/javascript\"></script>",
bundleName));
}
varThis.ClientScript.RegisterStartupScript(varThis.GetType(), scriptKey, strScript.ToString());
}
为什么需要这段代码来仅将一个 JavaScript 引用添加到页面?
以下几点提供了答案:
- 我们希望在页面末尾添加脚本引用,而不是在页面中间或用户控件中。
- 仅当调试模式为
true
时,才应将捆绑包添加到页面。如果调试模式为false
,我们希望将每个单独的 JavaScript 文件添加到页面。这将允许我们获得干净正常的 JavaScript 文件,以便我们可以轻松调试。 - 我们希望为添加到页面的单个文件捆绑包添加版本控制。
4. 将捆绑包引用添加到页面
现在,我们已经准备好将所有 Javascript 捆绑包添加到页面。有多种方法可以添加这些引用。每种方法都有其优点和缺点。
方法 1
向 aspx 页面添加简单的 Javascript 捆绑包引用。就像我们添加任何 javascript 引用一样,我们可以直接向页面添加捆绑文件引用。
<script language="javascript" type="text/javascript" src="/AdminJS.js"></script>
这种方法的问题是,它不会为捆绑包添加任何版本信息。
方法 2
我们可以使用 Scripts.Render 方法向页面添加引用。此方法解决了两个问题。一是将捆绑包引用添加到页面,二是为页面添加版本信息。这些版本信息将由 Scripts.Render 本身管理。因此,每次更改 JavaScript 时,捆绑包都会随版本信息一起刷新。这将应用于回收应用程序池。
<%:System.Web.Optimization.Scripts.Render("~/AdminJS.js")%>
方法 2 的问题是,它将在我们添加此行的页面上添加引用。首选方法是在页面底部添加文件,而不是在中间。
方法 3
以下是通过代码将 javascript 引用注入页面的方法。当您想将 Javascript 引用绑定到用户控件而不是 aspx 页面时,这是一个很大的优势。因此,如果您添加/移动用户控件,您的控件的 Javascript 文件将随用户控件一起移动。
还有一行 Page.ClientScript.IsClientScriptIncludeRegistered。这一行确保,如果您将用户控件添加到页面两次,JavaScript 将不会被添加两次,而只会添加一次。
#region Constants private const string JAVA_SCRIPT_CLASS_KEY = "AdminHomePage"; private const string CONTROL_KEY = "ControlKey"; private const string SCRIPT_PATH = "~/AdminJS.js"; #endregion protected void Page_Load(object sender, EventArgs e) { RegisterClientClass(); } /// <summary> /// Register javascript class file. Also create a javascript class object. /// </summary> private void RegisterClientClass() { if (!Page.ClientScript.IsClientScriptIncludeRegistered(CONTROL_KEY)) { Page.ClientScript.RegisterStartupScript(typeof(Page), CONTROL_KEY, Scripts.Render(SCRIPT_PATH).ToHtmlString()); } Dictionary<string, string> jsControls = new Dictionary<string, string>(); jsControls.Add(divBannerContainer.ID, divBannerContainer.ClientID); WebHelper.RegisterClientScriptClass(this.ClientID, this.Page, JAVA_SCRIPT_CLASS_KEY, jsControls); }
方法 3 将是向页面添加引用的最佳方式,但它有一个缺点。如果我向页面添加自定义 JavaScript,它应该在应用程序池回收和文件更改时具有版本。但是,像 jQuery 插件这样的文件不会随着时间的推移频繁更改。因此,我不会倾向于每次回收应用程序池时都添加新的版本信息。为了避免这个问题,我们可以添加自定义代码来从 web.config 文件维护版本信息,如以下方法所述。
方法 4
以下是 Page.ClientScript.IsClientScriptIncludeRegistered 方法的替换代码。AddScriptFromBundle 方法从 web.config 文件管理版本信息。
<% ApplicationSettingsHelper.AddScriptFromBundle("/AdminJS.js", this, "DefaultPage"); %>
根据需求,选择您要实现的方法。
关注点
捆绑和最小化始终有利于页面性能。上述概念确实有助于我们免于担心 JavaScript 文件的捆绑和最小化。
此外,管理 XML 以维护页面捆绑包也很容易。
我希望这些代码和概念能帮助改进项目的框架设计。我知道总有改进和编写好代码的空间。任何评论和建议都将不胜感激。
谢谢。