ASP.NET VERSIONS: 3.5
On-demand XAML: Reloaded
Getting XAML and Code but Only When You Need It
By Dino Esposito
The breakthrough aspect of Silverlight 2.0 is the support it offers for a tailor-made version of the .NET Common Language Runtime (CLR). If this statement is not evocative enough for you, let me rephrase: Silverlight 2.0 allows you to host .NET managed code in any browser that supports plug-ins and on any supported platforms. Microsoft supplies Silverlight 2.0 for Windows and Mac; the Mono group will have available a Linux edition of Silverlight by the time the product ships in the summer of 2008.
The user interface of a Silverlight 2.0 page is expressed as a WPF user control and persisted to an XAML file. The plug-in XAML parser recognizes nearly all elements of the full WPF syntax. The correspondence is not on 100% of the elements, but refers to a compatible subset (maybe not all elements, but certainly all key functionalities except 3D).
It comes as a foregone conclusion that if any content is to be downloaded on demand, that content can t simply be limited to an XAML text file. At the very minimum, it will come with a companion code-behind class. To deal with this need, Microsoft formalized a new package format the XAP package. A Silverlight 2.0 plug-in now points to an XAP URL rather than to an XAML resource.
Internally, the XAP package contains a manifest file and all the assemblies that make up the document. The XAML source code is attached as a resource to one of the assemblies. The manifest file lists the contents of the package-constituent assemblies and additional auxiliary resources, such as images and XML files. The list of packaged assemblies includes the assembly that contains the code-behind class, plus any other auxiliary assembly that is referenced in the project. In Beta 1, for instance, the manifest also lists a couple of system assemblies that contain some common controls. Past Beta 1, however, these extra assemblies will become part of the plug-in setup. The XAP package is merely a renamed ZIP archive. You can try it out yourself by simply renaming an XAP resource to ZIP, then using any program to snoop (see Figure 1).
Figure 1: Looking into the content of an XAP package.
Designing for On-demand
An XAML document that accepts on-demand content should contain one or more placeholder elements. In ASP.NET, you typically use an ad hoc control as a placeholder the PlaceHolder server control. No such element exists in WPF, but you can use an empty stack panel instead:
Any document-wide event can trigger the additional download. Once downloaded, the data is massaged into an XAML subtree and appended to the overall UI tree. Most of the time, you might also want to cache at least the generated subtree to avoid both additional roundtrips and any extra work to make the downloaded content really usable. Here s a simple pattern you can implement in any event handler that triggers the on-demand feature:
Any content available on demand should be organized in WPF user controls. Quite simply, given the default behavior of Visual Studio 2008, this means that you create multiple Silverlight applications: one for each piece that has to be downloaded separately. From an XAML perspective, each separate download adds a user control to the main tree.
var downloader = plugin.createObject("downloader");
The WebClient Class
An extremely simple class, WebClient is essentially an object-oriented wrapper for an HTTP GET call but you can t use it to post data if not through the query string. The class has an asynchronous programming interface and features a couple of executors: the DownloadStringAsync and OpenReadAsync methods:
Uri endpoint = new Uri(resourceURL);
WebClient wc = new WebClient();
The OpenReadAsync method has the same signature as DownloadStringAsync, but fires the OpenReadCompleted event when done. Figure 2 shows a handler for the event that extracts an assembly and instantiates the contained XAML code-behind class.
private void OnOpenReadCompleted(object sender,
// Local variables
String assembly = "extrastuff.dll";
String className = "Samples.ExtraStuffPage";
if (e.Error != null)
// Load a particular assembly from XAP
Assembly a = LoadAssemblyFromXAP(assembly, e.Result);
// Get an instance of the XAML object
UserControl page = a.CreateInstance(className) as
private Assembly LoadAssemblyFromXAP(string assemblyName,
Uri assemblyUri = new Uri(assemblyName,
StreamResourceInfo xapSri = new
StreamResourceInfo assemblySri =
AssemblyPart assemblyPart = new AssemblyPart();
Assembly a = assemblyPart.Load(assemblySri.Stream);
Figure 2: Extracting and instantiating a downloaded XAML subtree.
The OpenReadCompletedEventArgs data structure exposes the downloaded content as a stream through its Result property:
Stream xap = e.Result as Stream;
The stream contains all assemblies referenced in the manifest file. Figure 3 shows a sample XAML manifest file that is the root dictionary of an XAP package.
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" EntryPointAssembly="DynamicStuff" EntryPointType="DynamicStuff.App" RuntimeVersion="2.0.30226.2"> :
Figure 3: An XAP manifest file.
The next step requires that you extract the parts of the stream in which you re interested. In particular, you extract the assembly that contains the code-behind class for the chunk of XAML you want to add to the current tree.
The StreamResourceInfo class allows you to identify the various assemblies in the package. Each of these constituent assemblies is known as an assembly part and is represented with an instance of a new class: AssemblyPart.
The assembly is identified with a URI and extracted from the returned stream using the global GetResourceStream method on the Application class:
Uri uriAsm = new Uri(assembly, UriKind.Relative);
StreamResourceInfo sriMain = new
StreamResourceInfo sriAsm =
To finally get a reference to an assembly class, use the AssemblyPart class and its Load method. The Load method takes the stream with the assembly bits as read from the XAP stream:
AssemblyPart assemblyPart = new AssemblyPart();
Assembly a = assemblyPart.Load(sriAsm.Stream);
You re pretty much done at this point. The only thing that remains to do is to instantiate the class with XAML and code:
UserControl page = a.CreateInstance(className)
The CreateInstance method on the Assembly class simply takes the fully qualified name of the class and returns an instance of it. Because this class represents an XAML page, it inherits from UserControl.
Updating the Tree Dynamically
The user interface of the currently displayed page is an XAML tree and is rooted in a UserControl object. As the effect of the download, you now have a second UserControl that can be appended everywhere in the existing tree. The trick is using the Children property that any UI element in XAML exposes:
Likewise, by using the Remove method on the Children property, you can detach any XAML subtree from display. The detached subtree will remain in memory until it goes out of scope. However, you might want to persist it to the Silverlight local storage and read it back at a later time to save a few round trips and processing power over time. The XamlWriter and XamlReader classes are there to help you with just this task. The former saves a Silverlight tree down to an XAML string. The latter reads it back to an XAML object that can be attached to the existing UI.
For the most part, programming Silverlight applications is a matter of making asynchronous calls to the Web server to get configuration data, settings, preferences, and, of course, extra pieces of user interface and related code. The larger the application, the smaller its network footprint. Deferred loading is a key technique to optimize the use of the bandwidth while keeping the client machine as lean and mean as possible. In Silverlight 2.0, a downloadable package is a zipped archive that includes at least one assembly. This assembly contains the compiled version of the class that acts as the code-behind back-end of the XAML user interface. No XAML stream is downloaded explicitly because the XAML is embedded into the assembly as a resource. The plug-in parser takes care of extracting and processing it from within the browser. As a developer, you only need to get acquainted with the XAP package and the API to deal with it. To install what you download, create an instance of the XAML root class. That s it and it works.
Dino Esposito is an architect at IDesign and specializes mainly in ASP.NET, AJAX, and RIA solutions. Dino is the author of Programming ASP.NET 3.5 Core Reference (Microsoft Press, 2008). He also wrote Introducing ASP.NET AJAX, also for Microsoft Press. Late-breaking news is available at http://weblogs.asp.net/despos.