The blog of dlaa.me

Script combining made better [Overview of improvements to the AJAX Control Toolkit's ToolkitScriptManager]

The 10606 release of the AJAX Control Toolkit introduced ToolkitScriptManager, a new class that extends the ASP.NET AJAX ScriptManager to perform automatic script combining. I blogged an overview of ToolkitScriptManager last week (including an explanation of what "script combining" is). This post will build on that overview to discuss some of the changes to ToolkitScriptManager in the 10618 release of the Toolkit.

The most obvious change is the addition of an optional new property: CombineScriptsHandlerUrl. Left unset, ToolkitScriptManager works just like before; setting CombineScriptsHandlerUrl specifies the URL of an IHttpHandler (feel free to use @ WebHandler to implement it) that's used to serve the combined script files for that ToolkitScriptManager instance instead of piggybacking them on the host page itself. Implementing this handler is simple: just use the CombineScriptsHandler.ashx file that's part of the SampleWebSite that comes with the Toolkit! :) If you look at how that handler works, the ProcessRequest method simply calls through to the ToolkitScriptManager.OutputCombinedScriptFile(HttpContext context) method which is public and static for exactly this purpose. OutputCombinedScriptFile does all the work of generating the combined script file and outputting it via the supplied HttpContext - in fact, this is the same method that ToolkitScriptManager now uses internally to output a piggybacked combined script file. Because adding a handler in this manner doesn't require modifying the server configuration, CombineScriptsHandlerUrl can also be used by people in hosted and/or partial trust scenarios.

At the end of my previous post, I mentioned two tradeoffs that were part of switching from ScriptManager to ToolkitScriptManager. Both of those tradeoffs are addressed by the 10618 ToolkitScriptManager - plus one more I didn't know about then and another that's implicit:

  • Using CombineScriptsHandlerUrl incurs no unnecessary load on the server. One of the tradeoffs of the piggyback method for generating combined script files is that it involves a little bit of extra processing as part of the host page's page lifecycle that's not strictly necessary for the purposes of generating the combined script file. ToolkitScriptManager manages the page lifecycle processing to minimize the impact of piggybacking, but can't eliminate it all. However, using CombineScriptsHandlerUrl with a dedicated IHttpHandler doesn't involve any such overhead and helps keep things as efficient and streamlined as possible. The host page doesn't get reinvoked and the dedicated IHttpHandler does no more than it needs to.
  • Using CombineScriptsHandlerUrl won't interfere with URL rewriting. Customers using URL rewriting with their web sites pointed out that the piggybacking approach to combined script generation might require them to revise their URL rewriting rules to account for the unexpected page requests with the special combined script request parameter. ToolkitScriptManager tries to be as easy to use as possible, so one of the nice things about setting the new CombineScriptsHandlerUrl property is that web site authors can choose whatever URL works best for them to be their combined script file handler, thereby avoiding conflict with existing URL rewriting rules.
  • Using CombineScriptsHandlerUrl makes it more likely that cached script files will be reused. When piggybacking combined script URLs, the presence of the host page's base URL in the combined script URL means that any cached script files generated by page A will not be usable by page B (which has a different base URL). Of course, once the user's browser caches the combined script files for pages A and B, the cached versions will be used and there is no server impact - but page B won't benefit from page A's cached file even if the combined script files are otherwise identical. When pages A and B both use the same set of extenders and CombineScriptsHandlerUrl is specified, the combined script file URL generated by pages A and B will be identical (because the combined script file handler base URL will be the same for both) and therefore the combined script file cached by the user's browser for page A will be automatically used for page B as well. For web sites with common extender usage patterns (such as a TextBoxWatermark'd search box in the corner of every page), the caching benefits of CombineScriptsHandlerUrl could be significant.
  • The URLs used to specify combined script files are considerably less verbose. Rather than including the full script name for every script in the combined script file (often upwards of 20 or 30 characters), the hexadecimal representation of their String.GetHashCode is used instead (8 or fewer characters). While the baseline combined script URL length has grown by a bit due to some other changes, by far the most significant source of URL length was the script names, so the new URLs are shorter and less likely to get long (even on pages with lots of extenders). This improvement applies whether CombineScriptsHandlerUrl is specified or not, so piggybacked URLs are shorter, too. Note: Because hash code collision is now possible (though extremely unlikely), there's a bit of code to detect that scenario and throw an informative exception. (Just change either of the script names slightly to resolve the collision.)

A handful of other fixes and improvements were made to ToolkitScriptManager for the 10618 release. Notably:

  • The CurrentUICulture is now embedded in the combined script URL so that changing the browser's culture while viewing a site will properly update the culture of the site's extenders.
  • ToolkitScriptManager's check for a script's eligibility to participate in script combining now includes a check for the WebResource attribute which is one of the things that ASP.NET's ScriptResourceHandler requires in order to serve an embedded resource. Consequently, an assembly's embedded resources without a corresponding WebResource attribute are no longer eligible for script combining (without needing to use the ExcludeScripts to explicitly remove them). This makes ToolkitScriptManager's behavior more consistent with that of ScriptResourceHandler.
  • The "magic" request parameter for the combined script file changed from "_scriptcombiner_" to "_TSM_HiddenField_"/"_TSM_CombinedScripts_". _TSM_CombinedScripts_ is simply a rename of _scriptcombiner_, while _TSM_HiddenField_ now specifies the HiddenField that's used for tracking which scripts have already been loaded by the browser. This ID is implicitly available when piggybacking, but is not available in the CombineScriptsHandlerUrl case, so it has become part of the URL. For completeness, the new form of the combined script URL is now:
    .../[Page.aspx|Handler.ashx]?_TSM_HiddenField_=HiddenFieldID&_TSM_CombinedScripts_=;Assembly1.dll Version=1:Culture:MVID1:ScriptName1Hash:ScriptName2Hash;Assembly2.dll Version=2:Culture:MVID2:ScriptName3Hash

If you're already using ToolkitScriptManager and want to start using CombineScriptsHandlerUrl, but don't want to have to modify a bunch of ASPX pages to add the new property, you can take advantage of the fact that ToolkitScriptManager is now decorated with the Themeable attribute and can be customized by a .skin file as part of ASP.NET's Theme/Skin support. Adding CombineScriptsHandlerUrl to all the pages of the Toolkit's SampleWebSite was easy - I just added a ToolkitScriptManager.skin file to the existing web site theme and used the code <ajaxToolkit:ToolkitScriptManager runat="server" CombineScriptsHandlerUrl="~/CombineScriptsHandler.ashx" /> to set CombineScriptsHandlerUrl for the entire site.

ToolkitScriptManager is a handy way to enhance a web site with the AJAX Control Toolkit and it's gotten even better with the 10618 release of the Toolkit. We think ToolkitScriptManager offers some pretty compelling enhancements and we use it for all the AJAX Control Toolkit's sample content. We encourage anyone who's interested to give ToolkitScriptManager a try and see how well it works for them. As always, if there are any problems, please let us know by posting a detailed description of the problem to the AJAX Control Toolkit support forum.

Happy script combining!!