The blog of dlaa.me

You voted lots, we fixed lots [AJAX Control Toolkit release!]

Last night we published the 10920 release of the AJAX Control Toolkit. This release continued our trend of focusing on the most popular bugs and work items identified by the user community in the support forum and online issue tracker. A number of popular issues got fixed in this release, addressing nearly 1000 user votes!

The release notes from the sample web site detail the improvements:

General fixes:

  • Controls with Embedded styles (Calendar, Tabs and Slider): Toolkit controls no longer need explicit style references when loaded asynchronously. For example, if a Calendar control is placed inside an UpdatePanel and made visible on an UpdatePanel postback, the embedded styles are now loaded properly.
  • PopupBehavior positioning (AutoComplete, Calendar, DropDown, HoverMenu, ListSearch, PopupControl and ValidatorCallout): PopupBehavior now respects the position of its parent element even when the browser window is very narrow or the parent element is close the window edge.
  • Focusing extended controls (Accordion, CollapsiblePanel, DropShadow, Tabs): Pages that use Toolkit controls which re-parent DOM elements can use a workaround to focus a specific element on page load. The new method Utility.SetFocusOnLoad ensures that the desired control receives focus.

Control specific fixes:

  • Calendar: Property to specify the position of Calendar, a default date feature that allows the calendar to start out with a selected date, and a consistent show, hide and focus story that makes the Calendar user experience more intuitive.
  • ModalPopup: Ability to disable repositioning of the ModalPopup in response to window resize and scroll.
  • ConfirmButton: ModalPopup functionality now supported in addition to the regular windows alert dialog.
  • MaskedEdit: Extended Textbox no longer uses Invariant culture if no CultureName is specified and falls back to the Page Culture.
  • AutoComplete: Allow users to associate additional data with the AutoComplete suggestions.
  • Slider: Slider can be easily customized using its various CSS properties.

As with the previous release, we have published "source" and "no-source" versions for .NET 2.0/Visual Studio 2005 as well as for .NET 3.5/Visual Studio 2008 (still in Beta). Unique to the 3.5/2008 versions are the following:

Features:

  • JavaScript IntelliSense support: We have added reference tags to all Toolkit JavaScript files that enables you to take advantage of new features in Visual Studio 2008 Beta 2. With the multi-targeting support in this Visual Studio Beta, IntelliSense will be available for the ASP.NET AJAX 1.0 flavor of the Toolkit as well. This article discusses the reference tag feature in detail.
  • Extender designer support: Enhanced designer support for Toolkit controls using the new "Add Extender" user interface.

One thing we'd hoped to include with this release didn't quite make it in: our new automated testing framework. This framework is based on a different approach than our current framework - one that makes it easy to add additional test cases and leverage existing ones across new scenarios. The new testing framework has already dramatically improved our test coverage, helped identify new issues, and made fixing existing issues less risky!

But we've been iterating on the new framework for the past couple of weeks and faced the usual decision when it came time to finalize this release: slip or ship. We slipped our release date a little in the hopes that we'd be able to include the new framework with this release, but eventually decided not to delay all the great new Toolkit code any longer. We wanted our users to take advantage of the new bits ASAP - so stay tuned for more on the new testing framework in a future release!

As always, it's easy to sample any of the controls (no install required). You can also browse the project web site, download the latest Toolkit, and start creating your own controls and/or contributing to the project!

If you have any feedback, please share it with us on the support forum!

Bringing a bit of HTML to Silverlight [HtmlTextBlock makes rich text display easy!]

Lately I've seen a few people wanting to display rich text in a Silverlight application, but having no way to do so easily. Most recently, I saw Tim Heuer bump into this when displaying RSS content in a neat demo of his. The basic problem is that Silverlight's primary text display object, TextBlock, does not natively have a way to display HTML - and that's the format most rich text is in these days. It seemed that if only there were a TextBlock that took HTML as input and built up a collection of Run and LineBreak objects corresponding to that HTML, things would be easier...

So I wrote HtmlTextBlock, a "plug-compatible" replacement for TextBlock that knows how to take simple HTML (technically XHTML, see the notes below) and display it in a manner that fairly closely approximates how a web browser does. A picture is worth a thousand words, so here's what HtmlTextBlock looks like in action:

HtmlTextBlock Demonstration Page

You can click here (or on the image above) to experiment with HtmlTextBlock in your own browser in an interactive demo page. As usual, I've made the complete source code available, so click here to download the source code and play around with it yourself! (To build the project, you'll want to use Visual Studio 2008 Beta 2 and the latest Silverlight Tools.)

Notes:

  • HtmlTextBlock supports the following HTML elements: <A>, <B>, <BR>, <EM>, <I>, <P>, <STRONG>, and <U>. Attributes are not supported with the exception of the <A> element's HREF attribute (see below).
  • I initially planned to have HtmlTextBlock derive from TextBlock and simply override its Text property. However, TextBlock is sealed, so that wasn't going to work. The next obvious approach was to have HtmlTextBlock be a Control with a TextBlock inside it, so that's what I've done here. My goal of "plug-compatibility" (i.e., the ability to easily replace TextBlock with HtmlTextBlock) meant that I needed to manually implement the various TextBlock properties on HtmlTextBlock and pass them through to the underlying TextBlock. So HtmlTextBlock doesn't actually derive from TextBlock, but it behaves as though it did.
  • Having HtmlTextBlock use TextBlock internally and represent the HTML markup with Run and LineBreak elements is nice because it means that just about everything people already know about TextBlock automatically applies to HtmlTextBlock And I get correct word wrapping behavior for free. :) However, the glitch is that the Run element does not support the MouseLeftButton* family of events, making the handling of <A> link elements difficult. Because HtmlTextBlock can't really tell when the user clicks on a link, it displays the URL of each link so the user can type it in themselves (and typing is necessary because TextBlock doesn't support select+copy either). I can think of a few reasons why Run might not support mouse events, but in this case it would be convenient if it did. :)
  • HtmlTextBlock uses an XmlReader to parse its input when creating the Run and LineBreak elements for display. This makes the parsing implementation simple and robust, but breaks down when the input is not XHTML (such as invalid HTML or valid HTML where empty elements don't have a trailing '/' (ex: "<br>" instead of "<br />")). In the event of a parsing error, HtmlTextBlock's default behavior is to display the input text as-is, just like TextBlock always does. Unfortunately, non-XHTML is pretty common, so certain scenarios may benefit from a custom HTML parser implementation that is more flexible in this regard.
  • HTML rendering is covered by a detailed specification, but some of the rules are not what one might expect. Specifically, the rules for handling spacing and whitespace (' ', '\n', '\t', etc.) can be tricky to get right. I've made some attempt to ensure that HtmlTextBlock follows the rules where possible and convenient, but it was not my goal to achieve 100% compliance in this area. Playing around in the demo application, HtmlTextBlock should pretty closely match the browser's HTML rendering of valid input, but if you know the rules, it's not too difficult to trigger whitespace rendering differences.
  • Silverlight's default font, "Portable User Interface"/"Lucida Sans Unicode"/"Lucida Grande" doesn't seem to render bold or italic text any differently than normal text. This always surprises people the first time they try to figure out why their text isn't bold/italic. I don't know why this is myself, but I'd guess it relates to keeping the download size of Silverlight to a minimum.
  • The demo page makes use of JavaScript -> C# function calls. Hooking this up in code is surprisingly easy, though I did run into a small glitch. If the sample text is deleted entirely (leaving a blank text box) in IE, the JS -> C# call originates from the JS side as it should, but never makes it through to the C# side. Looking at the call to SetText in the debugger, textAreaSampleText.value is "", so this should work in IE just like it does in Firefox - but it didn't for me. The simple workaround was to pass textAreaSampleText.value + "" instead and then things worked in IE, too.

HtmlTextBlock is obviously nothing like a complete HTML rendering engine [that's what web browsers are for! :) ]. However, if you want to add simple support for rich text to your Silverlight application without a lot of work, HtmlTextBlock may be just the thing for you!

Time for a little fun and games (Silverlight helps play Sudoku!)

I'm not much of a Sudoku player, but I was recently in the company of some and got an idea for a Silverlight application to help solve Sudoku puzzles. There are already plenty of fine Sudoku programs out there, so I didn't set out to write "the world's best Sudoku program". Rather, this was an opportunity to explore some aspects of Silverlight that I haven't shown off yet such as audio, keyboard input, control embedding, and some more animation.

Sudoku is a surprisingly complex game and its puzzles range from very easy to quite difficult. Naturally, there are a number of different techniques that can be used to solve a Sudoku puzzle. Most of the techniques rely on keeping track of what the valid candidates for each cell are. Keeping track of all the candidates is too much for most people to do in their heads, so the common approach is to maintain a collection of pencilmarks. Keeping the pencilmarks up to date is a simple, monotonous task that involves no creativity whatsoever - and is perfect for a computer!

The Silverlight Sudoku Helper displays a Sudoku puzzle, automatically tracks the valid candidates, and leaves the creative problem solving fun to the user. It also prevents illegal moves and plays some fun sounds. :) The application looks like this:

Silverlight Sudoku Helper

You can click here (or on the image above) to play a game of Sudoku in your browser. As usual, I've made the complete source code available, so click here to download the source code and play around with it yourself! (To build the project, you'll want to use Visual Studio 2008 Beta 2 and the latest Silverlight Tools.)

Notes:

  • Directions for how to use the application can be found at the bottom of the Silverlight Sudoku Helper web page.
  • The sample boards from Wikipedia and Sudopedia are included to make it easy to get started and are believed to be free of copyright restrictions.
  • I came up with the "nearly complete" sample board myself and hereby release it to the public without copyright restrictions. :)
  • There is no mechanism to automatically import boards from other sources because most of the sources I came across made a point of restricting the use of the boards they offered in one way or another. That's why it's easy to create a blank board and type in any board you want (hint: use the Shift key when typing to bold the given values).
  • The audio files were taken from the %windir%\Media directory, renamed slightly, and converted to the WMA file format for use by Silverlight. They are not included in the source code package to keep the download size small (if you download the sample code and get an error 1001/DownloadError when viewing the application, it's probably because the audio files are missing). Feel free to substitute your own favorite sounds!
  • The key handling is done on KeyUp instead of KeyDown because Silverlight does not generate KeyDown events for the arrow keys. If/when this is addressed, the key handling could be done on KeyDown instead as the more natural place (where it should automatically support holding down a key to repeat it).

I didn't know much about Sudoku when I started this project and it turned out to be a more involved game than I'd thought! I hope the Silverlight Sudoku Helper is fun and educational for others as well!

A new Silverlight version has been released! [Samples updated for the 1.1 Refresh]

With today's release of the Silverlight 1.0 RC and 1.1 Refresh, I've updated the Silverlight samples I own to run on the new bits. Specifically, I've uploaded the migrated source code and recompiled binaries for the team's Silverlight Airlines Demo, my Simple XPS Viewer Demo, and my Silverlight Surface Demo. Because the Silverlight 1.1 Refresh replaces the earlier 1.1 Alpha, the existing source and demo URLs remain the same and simply point to the new content.

The 1.1 Refresh's breaking changes from the 1.1 Alpha build we've all been using before today are documented in the SDK. The migration of the these samples was pretty simple and required very little code change. For the benefit of others migrating Silverlight apps, here are the specific incompatibilities I encountered along with the fix for each:

  • Silverlight.js content updated and namespace changed from Sys.Silverlight to Silverlight - use new version of Silverlight.js, change CreateSilverlight.js to use the new namespace, and specify version "1.1" in the call to createObject(Ex)
  • Downloader.Open API changed to remove third parameter ("async") because all downloads are now asynchronous - change call sites to pass only two parameters since they were already passing true for async
  • Assignment of DoubleAnimation to new Storyboard's Children property now throws an exception - use a Control class and InitializeFromXaml to load the Storyboard and Animation from a XAML resource
  • Visibility enum no longer contains .Hidden value - use .Collapsed instead
  • OnResize is now function pointer-based instead of string-based - pass function pointer instead

Additionally, a security-related change to the Downloader object now blocks access to file:// URLs. This means that the XPS Viewer and Surface demos will only be able to access their resources (images, files, etc.) when the host page is loaded from a web server. Specifically, loading Default.html from Explorer or by hitting F5 to run the project in Visual Studio will now result in an error message like the following:

Silverlight error message
ErrorCode: 1001
ErrorType: DownloadError
Message: AG_E_UNKNOWN_ERROR

To re-enable debugging for the XPS Viewer and Surface demos, I recommend the following steps which add a web site project to Visual Studio and access the demo page via the included Visual Studio Development Web Server (which uses http:// URLs and works fine). While there are other ways to work around the file:// restriction, I'm suggesting this approach because it automatically references the project files, doesn't require creating additional directories or copies of project files, and doesn't require getting involved with IIS. To enable debugging:

  1. From the File menu in Visual Studio 2008, click Open, Project/Solution...
  2. Select the .csproj file for the demo you want
  3. Click OK
  4. From the View menu, click Solution Explorer
  5. Right-click the Solution node at the top
  6. Click Add, Existing Web Site...
  7. Select the same directory the .csproj file is in
  8. Click OK
  9. Click No when asked to upgrade the web site
  10. Right-click the new Web Site Project node
  11. Click Property Pages
  12. Click Start Options in the left column
  13. Check Silverlight in the Debuggers section at the bottom right
  14. Click OK
  15. Right-click the new Web Site Project node
  16. Click Set as StartUp Project
  17. Press F5 to Run
  18. Click OK to enable debugging in web.config

Okay, enough with the boring details - go get the Silverlight 1.1 Refresh and have some fun with it! :)

A new Framework deserves a new Toolkit [AJAX Control Toolkit updated for .NET 3.5 Beta 2!]

Earlier today Microsoft announced Beta 2 of Visual Studio 2008 and the .NET Framework 3.5. As usual, Scott Guthrie has a bunch of reasons why the new stuff is cool. My team has one more reason to add to the list:

We've just updated the 10618 Toolkit release with Beta 2 versions of the AJAX Control Toolkit that work seamlessly with VS 2008 and .NET 3.5!

The Toolkit's new "Framework3.5" downloads contain the latest 10618 Toolkit code in a VS 2008 Beta 2 solution/project. What we've done is make a handful of tweaks to better integrate with the new VS 2008 web designer enhancements for ASP.NET AJAX extenders. As you'd expect, the Toolkit controls work the same as before - but the development experience with VS 2008 is better than ever.

Download Visual Studio 2008 with the .NET Framework 3.5, the .NET 3.5 Beta 2 Toolkit, and find out for yourself!

And if you have any feedback, please share it with us on the support forum.

An easy way to keep your windows where you want them [Releasing WindowPlacementTool with source code!]

I wrote WindowPlacementTool in December of 2000 to solve a problem I had after beginning to use Terminal Services/Remote Desktop regularly. I made WindowPlacementTool available internally in 2001. Last week someone asked about getting access the source code to make some customizations and I figured I'd post the tool and its source here for anyone to use.

Download WindowPlacementTool and its source code by clicking here.

Details:

Summary
=======

If you're picky about the layout of the windows on your desktop or if you
connect to your machine with Terminal Services at differing resolutions, you're
probably annoyed by having to re-layout your windows on a regular basis.  It
seems like something (or someone!) is always coming along and messing with your
layout.  But now that's a problem of the past; WindowPlacementTool can do all
the work for you!  Just run it once to capture the layout you like, and then
run it again whenever you need to restore that layout.  And because you can
save multiple layouts, switching resolutions is a breeze.  Yep, it's that easy!


Command Line Help
=================

WindowPlacementTool
Copyright (c) 2000 by David Anson (DavidAns@Microsoft.com)

[-h | -help | -?]
        This help screen

[-c | -capture] [Capture_file_name.txt]
        Capture the current window positions to a file (or standard output if
        no file name is given)

[Restore_file_name_1.txt] [Restore_file_name_2.txt] ...
        Restore the window positions from the data previously captured in the
        specified file(s) (or standard input if no file name is given)


Example Setup
=============

[Layout your windows however you'd like them]

[Capture the current layout to a file]
C:\Temp>WindowPlacementTool.exe -c 800x600.txt

[Optional: Edit the file to remove any programs you don't care about]
C:\Temp>notepad 800x600.txt

[Optional: Create a shortcut on your desktop for easy access to this layout]
[Here, the shortcut would run "WindowPlacementTool.exe C:\Temp\800x600.txt"]


Example Use
===========

[Run the shortcut you created above or run WindowPlacementTool manually]
C:\Temp>WindowPlacementTool.exe 800x600.txt


Notes
=====

* WindowPlacementTool saves the RESTORED locations of windows.  If a window is
  currently maximized or minimized, its restored location will be adjusted, but
  the window will not be un-maximized or un-minimized by WindowPlacementTool.
* I have placed a shortcut to a layout for each resolution I use on my taskbar
  so that it's always available when I need it.

Additional notes:

  • When restoring window positions, the window class name must match the saved value exactly, but the window title comparison is just a substring match. This makes it easy to target windows that change their window title depending on the current document. (Example: "Untitled - Notepad" and "File.txt - Notepad" will both match the window title string "Notepad".)
  • The code for WindowPlacementTool was written many years ago and may not follow conventional coding standards. Feel free to reformat it in your favorite editor. :)
  • I've made only the necessary modifications to get the original code compiling successfully under Visual Studio 2005 SP1. I specifically did NOT spend time resolving the four new compiler warnings.
  • While I've always tried to take security and correctness seriously, the original code was written well before security was as big an issue as it is today. Recent modifications to Microsoft's CRT have deprecated some functions in favor of safer alternatives. This is the source of 3 of the 4 compiler warnings; read more about security enhancements in the CRT for additional details. The 4th compiler warning is due to improved CRT warnings associated with the increasing popularity of 64-bit computing (specifically compiler warning C4267). My code is typically error and warning free at warning level 4, but it's hard to be immune to future enhancements to the compiler/CRT. :)
  • Parts of the original code (ASSERT, VERIFY, ARRAY_LENGTH, WideString/AnsiString, _sntprintfz) used a helper library I wrote - I've pulled the relevant bits of that library into the main CPP file to remove the external dependency. Strsafe.h wasn't around at the time, but some of the code in this helper library of mine was already doing similar things. For example, the *z string functions improved upon the * versions by preventing buffer overflows and guaranteeing null-termination of the target string (i.e., strcpyz vs. strcpy).
  • While the source code compiles under VS 2005, the EXE included in the ZIP archive contains the bits as they were compiled back in 2000 with whatever compiler was current at the time.

WindowPlacementTool has served me well over the years - I hope others find it useful and/or educational!

Powerful log file analysis for everyone [Releasing TextAnalysisTool.NET!]

A number of years ago, the product team I was on spent a lot of team analyzing large log files. These log files contained thousands of lines of output tracing what the code was doing, what its current state was, and gobs of other diagnostic information. Typically, we were only interested in a handful of lines - but had no idea which ones at first. Often one would start by searching for a generic error message, get some information from that, search for some more specific information, obtain more context, and continue on in that manner until the problem was identified. It was usually the case that interesting lines were spread across the entire file and could only really be understood when viewed together - but gathering them all could be inconvenient. Different people had different tricks and tools to make different aspects of the search more efficient, but nothing really addressed the end-to-end scenario and I decided I'd try to come up with something better.

TextAnalysisTool was first released to coworkers in July of 2000 as a native C++ application written from scratch. It went through a few revisions over the next year and a half and served myself and others well during that time. Later, as the .NET Framework became popular, I decided it would be a good learning exercise to rewrite TextAnalysisTool to run on .NET as a way to learn the Framework and make some architectural improvements to the application. TextAnalysisTool.NET was released in February of 2003 as a fully managed .NET 1.0 C# application with the same functionality of the C++ application it replaced. TextAnalysisTool.NET has gone through a few revisions since then and has slowly made its way across parts of the company. (It's always neat to get an email from someone in a group I had no idea was using TextAnalysisTool.NET!) TextAnalysisTool.NET is popular enough among its users that I started getting requests to make it available outside the company so that customers could use it to help with investigations.

The effort of getting something posted to Microsoft.com seemed overwhelming at the time, so TextAnalysisTool.NET stayed internal until now. With the latest request, I realized my blog would be a great way to help internal groups and customers by making TextAnalysisTool.NET available to the public!

TextAnalysisTool.NET Demonstration

You can download the latest version of TextAnalysisTool.NET by clicking here (or on the image above).

In the above demonstration of identifying the errors and warnings from sample build output, note how the use of regular expression text filters and selective hiding of surrounding content make it easy to zoom in on the interesting parts of the file - and then zoom out to get context.

Additional information can be found in the TextAnalysisTool.NET.txt file that's included in the ZIP download (or from within the application via Help | Documentation). The first section of that file is a tutorial and the second section gives a more detailed overview of TextAnalysisTool.NET (excerpted below). The download also includes a ReadMe.txt with release notes and a few other things worth reading.

The Problem: For those times when you have to analyze a large amount of textual data, picking out the relevant line(s) of interest can be quite difficult. Standard text editors usually provide a generic "find" function, but the limitations of that simple approach quickly become apparent (e.g., when it is necessary to compare two or more widely separated lines). Some more sophisticated editors do better by allowing you to "bookmark" lines of interest; this can be a big help, but is often not enough.

The Solution: TextAnalysisTool.NET - a program designed from the start to excel at viewing, searching, and navigating large files quickly and efficiently. TextAnalysisTool.NET provides a view of the file that you can easily manipulate (through the use of various filters) to display exactly the information you need - as you need it.

Filters: Before displaying the lines of a file, TextAnalysisTool.NET passes the lines of that file through a set of user-defined filters, dimming or hiding all lines that do not satisfy any of the filters. Filters can select only the lines that contain a sub-string, those that have been marked with a particular marker type, or those that match a regular expression. A color can be associated with each filter so lines matching a particular filter stand out and so lines matching different filters can be easily distinguished. In addition to the normal "including" filters that isolate lines of text you DO want to see, there are also "excluding" filters that can be used to suppress lines you do NOT want to see. Excluding filters are configured just like including filters but are processed afterward and remove all matching lines from the set. Excluding filters allow you to easily refine your search even further.

Markers: Markers are another way that TextAnalysisTool.NET makes it easy to navigate a file; you can mark any line with one or more of eight different marker types. Once lines have been marked, you can quickly navigate between similarly marked lines - or add a "marked by" filter to view only those lines.

Find: TextAnalysisTool.NET also provides a flexible "find" function that allows you to search for text anywhere within a file. This text can be a literal string or a regular expression, so it's easy to find a specific line. If you decide to turn a find string into a filter, the history feature of both dialogs makes it easy.

Summary: TextAnalysisTool.NET was written with speed and ease of use in mind throughout. It saves you time by allowing you to save and load filter sets; it lets you import text by opening a file, dragging-and-dropping a file or text from another application, or by pasting text from the clipboard; and it allows you to share the results of your filters by copying lines to the clipboard or by saving the current lines to a file. TextAnalysisTool.NET supports files encoded with ANSI, UTF-8, Unicode, and big-endian Unicode and is designed to handle large files efficiently.

I maintain a TODO list with a variety of user requests, but I thought I'd see what kind of feedback I got from releasing TextAnalysisTool.NET to the public before I decide where to go with the next release. I welcome suggestions - and problem reports - so please share them with me if you've got any!

I hope you find TextAnalysisTool.NET useful as I have!

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!!

Tweaks and improvements by popular demand [AJAX Control Toolkit update!]

A short while ago we made available the 10618 release of the AJAX Control Toolkit. This release addresses a handful of user-impacting issues introduced by changes in the recent 10606 release and identified by the user community in the support forum and online issue tracker. Significant changes always have the risk of introducing problems so we do our best to find and fix them all before releasing. But for things that manage to sneak through, a targeted follow-up release is often a good way to fix annoyances quickly.

The release notes from the sample web site detail the improvements in the new release:

General fixes:

  • Tabs: Resolved NamingContainer issues so that FindControl works as expected in Tabs.
  • ToolkitScriptManager: Shorter combined script URLs and new HTTP handler support for generation of combined script files.
  • Dependencies: Removed explicit reference to VsWebSite.Interop.dll and stdole.dll. They will not be automatically included in the web configuration files by Visual Studio.
  • FilteredTextBox: Navigation, Control and Delete keys work fine in all browsers.
  • Localization: Turkish, Dutch, and Traditional and Simplified Chinese language support added.

As always, it's easy to sample any of the controls right now (no install required). You can also browse the project web site, download the latest Toolkit, and start creating your own controls and/or contributing to the project!

If you have any feedback, please share it with us on the support forum!

PS - Last week I blogged an overview of the ToolkitScriptManager introduced in the 10606 release. ToolkitScriptManager has gotten even better in this release, and I'll be writing more about it later this week!

Script combining made easy [Overview of the AJAX Control Toolkit's ToolkitScriptManager]

Note: Recent versions of ASP.NET AJAX include the ScriptManager.CompositeScript property which performs much the same functionality as discussed here. More information is available in the article Combining Client Scripts into a Composite Script.

 

Introduction

The 10606 release of the AJAX Control Toolkit includes ToolkitScriptManager, a new class that derives from the ASP.NET AJAX ScriptManager and performs automatic script combining.

What is meant by "script combining" and why is it desirable?

ASP.NET AJAX Behaviors are typically implemented by JavaScript (JS) files that are downloaded by the browser as part of the web page's content/resources (like CSS files, images, etc.). Each JS file typically contains a single Behavior, so if a web page uses lots of Behaviors, it's going to download lots of Behavior JS files. The cost to download a single JS file is fairly minimal, but when there are many of them on a page, the serialized nature of Behaviors (later ones may depend on earlier ones and can't be loaded until the earlier ones have finished) means that it may take a bit of time for all the JS a page needs to download to the user's browser. The time in question is typically on the order of milliseconds, but every little bit helps when you're looking to give users the best possible experience!

Script combining is beneficial because fewer JS files means fewer request/response operations by the browser - which translates directly into quicker page load times for users and less load on the web server. Furthermore, there will be less network traffic because the HTTP headers associated with each unnecessary request/response operation don't need to be transmitted (saving around 750 bytes for each combined script).

At the extreme, one could combine all (~40) the Behaviors in the Toolkit into a single, monolithic JS file (either manually or as part of the build process) and always send that file to the browser. While there are certain benefits to this, we chose not to do so for two main reasons: 1) the ASP.NET AJAX framework the Toolkit builds upon is an object-oriented framework and it's beneficial to maintain the mental/physical separation that comes from keeping each behavior isolated and 2) any page that used any part of the Toolkit would be forced to download the entire set of scripts in the Toolkit.

ToolkitScriptManager gives us the best of both worlds by combining exactly the relevant JS files used by each page into one file so the browser downloads only what's necessary for each page. ToolkitScriptManager's script combining is more than a simple concatenation of all the script files in the Toolkit; it's a dynamic merge of only the scripts that are actually being used by a page each time it's loaded by the browser. If a page has an ASP.NET AJAX UpdatePanel on it and additional scripts need to be sent as part of an async postback, then ToolkitScriptManager will automatically generate a combined script file containing only those scripts that the browser hasn't already downloaded.

ToolkitScriptManager automatically compresses the combined script file if the browser indicates it supports compression - achieving slightly better compression in the process because most adaptive dictionary-based compression techniques (like HTTP's GZIP and Deflate) tend to compress data better in one chunk than in multiple chunks (because the dictionary doesn't keep getting reset). The combined script file is cached and reused by the browser just like the corresponding script files would have been if ToolkitScriptManager weren't being used.

Basically, script combining effortlessly creates faster loading web pages - and happier users by extension!

How do I use it?

Simple: Just replace <asp:ScriptManager ... /> with <ajaxToolkit:ToolkitScriptManager ... /> in your ASPX page and you're done! (Of course, if you're not using the default namespaces "asp" and "ajaxToolkit", you'll need to substitute your own namespaces.) The scripts in the AJAX Control Toolkit are already enabled for combining, so it's really that easy!

ToolkitScriptManager derives from ScriptManager, so it can be substituted trivially. Configuration-wise, it exposes a single new property beyond what ScriptManager already has: bool CombineScripts. The default value of "true" means that scripts will be automatically combined - specifying the value "false" disables the combining. (Alternatively, just switch back to ScriptManager for the same result.)

How do I enable combining for my custom Behavior's scripts?

As a security precaution to prevent malicious users from taking advantage of ToolkitScriptManager to access embedded resources from unrelated DLLs, the new assembly-level ScriptCombine attribute must be used to indicate that a particular assembly/script is allowed to take part in the script combining process. By default, none of the scripts in an assembly without the ScriptCombine attribute will take part in script combining. Adding the ScriptCombine attribute to an assembly indicates that all of its scripts can take part in script combining and ToolkitScriptManager will automatically include the relevant ones when generating a combined script file. For finer control over individual scripts in an assembly, the ScriptCombine attribute exposes an ExcludeScripts property and an IncludeScripts property - both are comma-delimited lists of individual script files. As stated, when neither property is specified, the default behavior is that all scripts are combinable. Once the IncludeScripts property is set, only the scripts explicitly specified by it are combinable. The ExcludeScripts property excludes any listed scripts from combining (whether or not IncludeScripts is set). Of course, most people will simply add the ScriptCombine attribute to their assembly and the default behavior does what they want. (As mentioned above, Toolkit scripts are already enabled for script combining.)

How do scripts actually get combined? [Technical]

Script combining is a two-stage process.

The first stage takes place during the normal ASP.NET page lifecycle when ToolkitScriptManager overrides ScriptManager's OnLoad method to initialize its state and its OnResolveScriptReference method to find out when script references are being resolved. The initialization code in OnLoad consists of adding a HiddenField to the page and using it to track which scripts have already been loaded by the browser. The handling of OnResolveScriptReferences is a little more involved: the script reference is checked for combinability (i.e., does the assembly's ScriptCombine attribute allow the script to take part in script combining) and the script reference is changed to point to the URL of a combined script file. In this manner, all scripts that are part of the same combined script file get the same URL and the ScriptManager class outputs that URL to the page exactly once. Notably, because scripts may have a strict ordering, the presence of an uncombinable script in the middle of combinable scripts will result in two combined script files being generated (the first consisting of the scripts coming before the uncombinable script and the second consisting of the scripts after it).

The URL of the combined script file is currently of the form: .../Page.aspx?_scriptcombiner_=;Assembly1.dll Version=1:MVID1:Script.Name.1.js:Script.Name.2.js;Assembly2.dll Version=2:MVID1:Script.Name.3.js. What this means is that the ASPX page itself is referenced with the special request parameter "_scriptcombiner_" and a semicolon-delimited list of assemblies with a colon-delimited list of the required scripts from each of them. The strong name of the assembly is used to avoid potential confusion if multiple versions of an assembly are present and the ModuleVersionID (MVID) is used to ensure that any changes to the assembly itself automatically invalidate all combined script files that reference it. In this manner, recompiling one of the assemblies contributing to a combined script file will cause the new scripts to be downloaded by the browser next time the page is loaded.

The second stage of script combining takes place when the page is referenced with the "_scriptcombiner_" request parameter. ToolkitScriptManager overrides OnInit (one of the first parts of the page lifecycle) and uses that opportunity to generate the combined script file based on the value of the request parameter, outputs the combined script file to the browser, and stops further processing of the page lifecycle. Of note, the cache settings of the combined script file are set to the same values that the individual script files would have had if ToolkitScriptManager weren't being used and the combined script file is automatically compressed according to the browser's wishes. Similarly, any localized script resources for a script file that is in the process of being combined are loaded and sent to the browser as part of the combined script file. After all combined scripts are output, ToolkitScriptManager appends a small bit of script to the end of the file to update the page's HiddenField with the scripts that have just been added. In this manner, any additional scripts added during an async postback are automatically tracked by the page and subsequent async postbacks will know exactly which scripts have already been loaded by the browser.

Why piggyback a request parameter on the same page instead of using an IHttpHandler? [Technical]

ScriptManager uses ScriptResourceHandler (an IHttpHandler) to serve (uncombined) scripts, so it's natural to wonder why ToolkitScriptManager wouldn't do the same. The reason is that the AjaxControlToolkit DLL is often run in partial trust scenarios where it couldn't add such a handler to the system itself - and because we don't want people to have to modify their web.config file just to enable script combining. By making use of the same page for serving combined script files, ToolkitScriptManager offers a seamless experience that's simple to configure, simple to manage, and that works even for folks who don't have control over the web server that's hosting their content.

Are there any tradeoffs when switching to ToolkitScriptManager?

There are no significant tradeoffs that we know of, but there are a couple of implications it's good to be aware of. For one, the current combined script URL format is currently pretty verbose and can lead to unusually long URLs. While this hasn't been a problem so far, it will be easy to change the format in the future (with no impact to users) and we're already considering ways of doing so. Another thing to be aware of is that reusing the page to serve the combined script file means that there is some additional server processing that happens before/during the OnInit stage of the page lifecycle when processing a combined script file. (Though the additional work here is offset by the savings of not having to serve multiple JS files.) Again, this hasn't been an issue, but it's something to keep in mind if things behave differently after adding ToolkitScriptManager to a page.

So just how risky is it to switch to ToolkitScriptManager?

That's a loaded question [ :) ], but it's informative to note that all AJAX Control Toolkit sample pages (including the sample web site, automated tests, manual tests, etc.) have been converted over to use ToolkitScriptManager with only one issue: The Slider's SliderBehavior.js script uses a fairly obscure feature enabled by the PerformSubstitution property of the WebResource attribute that allows <%= WebResource/ScriptResource %> tags to be embedded in JS files and get resolved before the script is sent to the browser. This behavior isn't currently supported by ToolkitScriptManager (it will throw an informative Exception if it detects the presence of this construct), so the ExcludeScripts property of the ScriptCombine attribute on the AjaxControlToolkit DLL has been used to exclude the SliderBehavior.js file from being combined.

Summary

ToolkitScriptManager works seamlessly in every page of the AJAX Control Toolkit, so we encourage folks to give it a try if they're interested in the benefits it offers! As always, if you encounter any problems, please let us know by posting a detailed description of the problem to the AJAX Control Toolkit support forum.

Happy script combining!!