On-demand XAML: Reloaded

Getting XAML and Code — but Only When You Need It

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 3.5

 

On-demand XAML: Reloaded

Getting XAML and Code but Only When You Need It

 

By Dino Esposito

 

In the April 2008 issue of asp.netPRO I talked about a common issue for the fast-growing community of Silverlight developers: deferred loading of XAML content (see On-demand XAML). I discussed how to embed XAML content as a data island in the host HTML page, and touched on the use of the Downloader object from inline JavaScript code. You typically employ the Downloader object to download a new XAML file from the server with the precise intent of updating the user interface dynamically.

 

So what s the purpose of this month s article? Am I here simply repurposing content I just presented a few months back? The (hopefully obvious) answer is no. You probably know that Silverlight 2.0 is in Beta (and may have shipped by the time you read this). The content of the previous article is still valid, but it mostly refers to Silverlight 1.0. It is also valid if you upgrade to Silverlight 2.0 and program it using JavaScript. This article, instead, reloads the topic of getting content on demand, but specifically targets using managed code and Silverlight 2.0.

 

Silverlight 2.0

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.

 

Built-in support for the CLR and managed code means that developers can say goodbye to JavaScript at least to the script that is normally used to implement the client-side logic of a page. The user interface of the plug-in is largely determined by the XAML file it downloads; the service provided by the plug-in, in the context of the hosting page, is fully determined by the code-behind class that decorates and accompanies the XAML file. In Silverlight 2.0, much like in a fully-fledged WPF application, you create an XAML document and corroborate it with a code-behind class that gathers event handlers and helper routines, and exposes public properties and methods.

 

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:

 

if (!IsDownloaded())

   DownloadExtraStuff();

else 

   AddExtraStuff();

 

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.

 

Let s take a look at the tools you use to set up an on-demand feature. In the first place, you need a managed object that connects to a remote URL and brings data down to the client browser. In the April column I discussed the Downloader object you use from JavaScript. In Silverlight 2.0, that object is still available to JavaScript callers, but can t be used from C# or VB. Here s how you would use it:

 

var downloader = plugin.createObject("downloader");

downloader.addEventListener("completed", onCompleted);

downloader.open("GET", "../xaml/ellipses.xaml");

downloader.send();

 

In Silverlight 2.0, JavaScript s Downloader object is a wrapper for a new managed class the WebClient client. Defined in the system.net namespace and compiled in the System.Windows assembly, the WebClient class is currently available only to Silverlight developers. For the most part, the .NET Framework in Silverlight is a subset of the full framework. The WebClient class is an exception, but it will likely find its way in to the full .NET Framework in a future release.

 

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();

wc.DownloadStringCompleted +=

   new DownloadStringCompletedEventHandler(OnCompleted);

wc.DownloadStringAsync(endpoint);

 

As the name implies, the DownloadStringAsync method returns the response as a plain string. It is good to download raw XAML, XML configuration files, or perhaps JavaScript files. It is not as good to grab compiled code, such as an assembly or, worse yet, an assembly that contains viable resources to be extracted. This is where the OpenReadAsync method fits in.

 

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,

   OpenReadCompletedEventArgs e)

{

   // Local variables

   String assembly = "extrastuff.dll";

   String className = "Samples.ExtraStuffPage";

   if (e.Error != null)

       return;

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

      UserControl;

}

private Assembly LoadAssemblyFromXAP(string assemblyName,

   Stream xapStream)

{

   Uri assemblyUri = new Uri(assemblyName,

      UriKind.Relative);

   StreamResourceInfo xapSri = new

     StreamResourceInfo(xapStream, null);

   StreamResourceInfo assemblySri =

      Application.GetResourceStream(xapSri, assemblyUri);

   AssemblyPart assemblyPart = new AssemblyPart();

   Assembly a = assemblyPart.Load(assemblySri.Stream);

   return a;

}

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(xap, null);

StreamResourceInfo sriAsm =

   Application.GetResourceStream(sriMain, uriAsm);

 

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)

   as UserControl;

 

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:

 

this.Placeholder.Children.Add(page);

 

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.

 

Conclusion

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.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish