Pokud si ve VS 2012 vytvoříme novou ASP.NET WebForms aplikaci s použitím výchozí šablony je v master page (v souboru Site.Master) použit serverový prvek ScriptManager s registracemi některých základních skriptů (JQuery, WebForms, MSAjax apod.). Konkrétně registrace skriptů, které se týkají Web Forms, jsou zde následující:
<asp:ScriptManager runat="server">
<Scripts>
<%--Framework Scripts--%>
<asp:ScriptReference Name="WebForms.js" Assembly="System.Web" Path="~/Scripts/WebForms/WebForms.js" />
<asp:ScriptReference Name="WebUIValidation.js" Assembly="System.Web" Path="~/Scripts/WebForms/WebUIValidation.js" />
<asp:ScriptReference Name="MenuStandards.js" Assembly="System.Web" Path="~/Scripts/WebForms/MenuStandards.js" />
<asp:ScriptReference Name="GridView.js" Assembly="System.Web" Path="~/Scripts/WebForms/GridView.js" />
<asp:ScriptReference Name="DetailsView.js" Assembly="System.Web" Path="~/Scripts/WebForms/DetailsView.js" />
<asp:ScriptReference Name="TreeView.js" Assembly="System.Web" Path="~/Scripts/WebForms/TreeView.js" />
<asp:ScriptReference Name="WebParts.js" Assembly="System.Web" Path="~/Scripts/WebForms/WebParts.js" />
<asp:ScriptReference Name="Focus.js" Assembly="System.Web" Path="~/Scripts/WebForms/Focus.js" />
<asp:ScriptReference Name="WebFormsBundle" />
<%--Site Scripts--%>
</Scripts>
</asp:ScriptManager>
To ale vypadá jako by byly Web Forms skripty “zaregistrovány dvakrát”, poprvé jednotlivě a podruhé jako “WebFormsBundle”. O co jako jde? Je to tak v pořádku a opravdu to musí být takhle?
No je pravda, že na první pohled to možná opravdu vypadá trochu divně, přesto je to tak ale dobře. Aby jsme si vysvětlili proč tomu tak je, pojďme nejprve na chvilku ignorovat skutečnost, že je ve výchozí šabloně použit Bundling and Minification (*) tj. tu poslední script referenci.
OK, takže co ty ostatní?
V ASP.NET Web Forms se používají serverové ovládací prvky. Tyto ovládací prvky nebo minimálně některé z nich (jako TreeView, GridView, validátory a další) potřebují pro svojí funkčnost občas i nějaký ten klientský JavaScript. Tyto skripty jsou umístěné přímo součástí assembly System.Web jako resource a ve stránce se na ně lze odkázat přes WebResource.axd a ScriptResource.axd. Tyto odkazy vypadají například nějak takto:
<script src="/WebResource.axd?d=pynGkmcFUV13He1Qd6_TZMdeTz_gEUorZEhSY3bHZOtH2_K465hgfiQGd1uGY-NKim97DapC7ocQHjLU3RiDSQ2&t=634971751065510185" type="text/javascript"></script>
<script src="/ScriptResource.axd?d=nv7asgRUU0tRmHNR2D6t1Gi91XdHPsycWNF6EheLopo6iSubibYRjbg1IUUFozVKthEXxDYx6KERfEiFos-s3UMlx60XYCQJIt-1ESK6owJiItHQ0hxrEX69wLPVyStqQnnyZ_qJ1f-4JbEdCsltZg2&t=ffffffff8a2817e0" type="text/javascript"></script>
Pokud tedy například v naší výchozí Web Forms aplikaci výše uvedené registrace do ScriptManageru zakomentujeme, uvidíme, že skutečně obdobné odkazy budou do stránky vygenerovány.
A jo. Teď si vzpomínám, že v dřívějších verzích ASP.NET ve stránkách podobné odkazy opravdu bývaly.
Přesně tak. Od verze .NETu 4 .0 byla ale komponenta ScriptManager rozšířena tak, že umožňuje odkaz na resource v assembly přemapovat na standardní statický soubor uložený na uvedené cestě. Dělá se to tak, že se v elementu <asp:ScriptReference /> uvede název (atribut Name) a Assembly existujícího pojmenovaného resource a pomoci atributu Path se určí statický soubor, který tento resource nahrazuje.
A to je právě to co tyto registrace a výchozí šablona dělá. Odkazované soubory jako WebForms.js nebo GridView.js jsou přitom normálně součástí šablony v podadresáři Scripts/WebForms. Konkrétně jsou tam přidány NuGet balíčkem “Microsoft.AspNet.ScriptManager.WebForms”.
Dobře, takže tyto soubory tam pak normálně musí být a budou se používat místo těch v System.Web. A to má výhodu jakou?
Výhody to má minimálně dvě nebo vlastně tři. První je to, že je nad skripty větší kontrola tj. například, kdyby jsme našli ve skriptu GridView.js chybu, můžeme si jí opravit nebo soubor se skriptem přehrát jinou verzí apod. a bude se používat změněná verze. Druhou výhodou je to, že se zbavíme těch poměrně ošklivých a dlouhých odkazů na .axd a HTML, které ASP.NET generuje, tak bude zase o něco čistší.
V tomto případě (bez reference “WebFormsBundle”) by totiž odkazy na skripty ve stránce vypadaly takto:
<script src="Scripts/WebForms/WebForms.js" type="text/javascript"></script>
<script src="Scripts/WebForms/WebUIValidation.js" type="text/javascript"></script>
<script src="Scripts/WebForms/MenuStandards.js" type="text/javascript"></script>
<script src="Scripts/WebForms/GridView.js" type="text/javascript"></script>
<script src="Scripts/WebForms/DetailsView.js" type="text/javascript"></script>
<script src="Scripts/WebForms/TreeView.js" type="text/javascript"></script>
<script src="Scripts/WebForms/WebParts.js" type="text/javascript"></script>
<script src="Scripts/WebForms/Focus.js" type="text/javascript"></script>
A poslední výhodou je to, že tento způsob je slučitelný s dalšími postupy jako možnost použití CDN, jiná verze skriptů pro debug a release nebo právě přímo použití Bundling and Minification.
A co ta poslední reference? A jak do toho zapadá použití Bundling and Minification?
Dříve zmíněný NuGet balíček “Microsoft.AspNet.ScriptManager.WebForms” kromě přidání *.js souborů dále provádí vytvoření a přidání ScriptResourceDefinition se jménem "WebFormsBundle". To je tedy ta definice, na kterou se pak odkazuje ona poslední skript reference. Tato definice vypadá konkrétně takto:
ScriptResourceDefinition definition = new ScriptResourceDefinition()
{
Path = "~/bundles/WebFormsJs",
CdnPath = "http://ajax.aspnetcdn.com/ajax/4.5/6/WebFormsBundle.js",
LoadSuccessExpression = "window.WebForm_PostBackOptions",
CdnSupportsSecureConnection = true
};
ScriptManager.ScriptResourceMapping.AddDefinition("WebFormsBundle", definition);
Kromě zajištění spuštění tohoto kódu po startu webové aplikace pomoci třídy PreApplicationStartCode, je toto ve skutečnosti veškerý kód, který je obsažen v assembly balíčku “Microsoft.AspNet.ScriptManager.WebForms”.
Současně s tím máme ve třídě BundleConfig v metodě RegisterBundles uvedeno následující:
bundles.Add(new ScriptBundle("~/bundles/WebFormsJs").Include(
"~/Scripts/WebForms/WebForms.js",
"~/Scripts/WebForms/WebUIValidation.js",
"~/Scripts/WebForms/MenuStandards.js",
"~/Scripts/WebForms/Focus.js",
"~/Scripts/WebForms/GridView.js",
"~/Scripts/WebForms/DetailsView.js",
"~/Scripts/WebForms/TreeView.js",
"~/Scripts/WebForms/WebParts.js"));
Což je registrace odkazovaného bundlu "~/bundles/WebFormsJs”, který obsahuje používané Web Forms skripty.
Skript reference "WebFormsBundle" pak způsobí, že místo odkazů na jednotlivé .js soubory budou Web Forms skripty spojeny, minifikovány a ze stránky odkazovány najednou, nebo se dokonce budou tahat z Microsoftí CDN, pokud chceme a použití CDN na ScriptManageru povolíme.
Pořád mi ale není úplně jasné, jak je možné, že přidáním poslední reference zároveň dojde ve stránce k odstranění původních odkazů na jednotlivé .js.
To je způsobeno tím, že v .NET 4.5 byla komponenta ScriptManager ještě dále rozšířena tak, že se umí “podívat” dovnitř do definice referencovaného bundlů. Reference na samostatné skripty, které jsou zároveň obsažené i jako součást některého z použitých bundlu, pak již ignoruje a na stránku příslušné odkazy negeneruje (tento proces se někdy označuje jako deduping).
V případě výchozí šablony je tak ve výsledné stránce pouze jeden odkaz na Web Forms bundle:
<script src="/bundles/WebFormsJs?v=5JltzM7KDneUw5G1P3oS9Oam475FEYG0FegmKeed7x01" type="text/javascript"></script>
(Pozn.: Ve verzi Microsoft.AspNet.Web.Optimization 1.1.0.0 , kterou například používá nová ASP.NET šablona ve VS 2013 Preview, je chyba a skripty referencované přes bundle odstraněny nejsou, viz článek zde.)
Takže registrace jednotlivých Web Forms skriptů ve ScriptManageru být musí, aby nebyly generovány odkazy na .axd a aby se vůbec umožnilo případné použití bundlingu. Ta poslední registrace je pak vlastní použití bundlingu, který má ale interně ošetřeno, aby mu předchozí registrace jednotlivých skriptů nevadili.
Některé další reference ohledně použití Bundling and Minification a ScriptManageru:
http://blogs.msdn.com/b/webdev/archive/2012/09/21/asp-net-4-5-scriptmanager-improvements-in-webforms.aspx http://weblogs.asp.net/infinitiesloop/archive/2009/11/23/asp-net-4-0-scriptmanager-improvements.aspx http://blogs.msdn.com/b/rickandy/archive/2012/08/14/adding-bundling-and-minification-to-web-forms.aspx http://igorzelmanovich.blogspot.cz/2012/09/using-aspnet-bundling-and-minification.html
http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
http://www.aspnet.cz/articles/402-bundling-a-scriptmanager-znovu-jeste-jednodussi-nez-jsme-cekali
(*) Bundling and Minification je funkce, která přišla do ASP.NET původně s MVC 4, ale lze jí používat i v ASP.NET WebForms. Jedná se o projekt ASP.NET Web Optimization Framework a je k dispozici jako NuGet balíček “Microsoft.AspNet.Web.Optimization”.
Tato funkce má za úkol několik (obvykle malých) JS skriptů nebo CSS stylů sloučit do jednoho souboru a provést jeho minifikaci (odstranění komentářů, prázdných řádků, odřádkování, zkrácení názvů proměnných apod. tak, aby byl výsledný soubor co možná nejmenší). Tím lze snížit počet nutných requestů a velikost přenášených dat při načítání webové stránky.