As those of you who’ve been reading my blog may know, I’ve been spending a bit of time with some enhancements to navigation in Silverlight surrounding the use of dynamically-loaded assemblies.  I’ve still got a bunch of things I’d like to try to implement, but in the meantime I thought I’d share what I’ve got so far.

Now that I have a mechanism for navigating to Pages in dynamically-loaded assemblies in Silverlight, the question arises: how can I improve the user and developer experience around partitioning applications in this way?

In particular, this is the flow I’d like to be able to create:

  • Developer knows the set of Pages his user will be able to navigate to, and partitions them into assemblies that he will load at runtime
  • Furthermore, the developer knows the dependencies those assemblies have on other dynamically-loaded assemblies (e.g. multiple assemblies contain Pages that use DataGrid, but System.Windows.Controls.Data should be shared/downloaded when the first of those assemblies is downloaded)
  • The user is presented with some sort of navigation UI (e.g. HyperlinkButtons, buttons that call Frame.Navigate(), etc.)
  • When the user requests a page in an assembly that has not yet been downloaded, it should be automatically downloaded, and navigation with the Frame should only complete once the Page can be loaded.

With this on-demand loading behavior, deep-linking into applications with dynamically-loaded assemblies can be re-enabled (you’ll notice if you try to deep-link into my sample from my previous post about dynamically-loaded assemblies, you get an exception, since the assembly hasn’t been loaded yet).

I’m developing some utilities that will make this flow easier to accomplish.  Please note that at this point all of the API, etc. is subject to change, and I’m definitely not making any assurances around bugs, quality, or anything of the sort, but I thought I’d share what I’ve been working on and start getting some feedback from you folks!

Anyhoo, let’s take this step by step:

Dividing an application into multiple assemblies

This is fairly straightforward, and something I’m sure most of you have been doing for a long time.  For our purposes, there are a number of things to consider:

  • Pages in these assemblies that will be dynamically loaded should use the DynamicPage and DynamicPageShim (or some other workaround you may have come up with) described in my earlier post
  • Any dynamically-loaded assemblies need to be in a place accessibly using the WebRequest APIs in Silverlight.  I usually put them in my ClientBin folder, and I used a post-build task to copy any dlls for those assemblies into the correct folder (feel free to see my Sample project for an example of this)
  • The libraries I’ve built support loading DLLs as well as XAP and ZIP files with dlls in them. Just using DLLs is probably the easiest way to go, but using ZIP or XAP files will keep the transfer time/size small.
  • Remember to take careful note of any dependencies between the libraries.  You’ll need to make sure they’re all loaded into your application before attempting to use the assemblies.

One thing I might do in the future is (attempt to) make a project template that will create a XAP class library that places the XAP in the ClientBin folder for you.  I’m still experimenting with how to do this, but it’s still worth noting that using XAPs can be problematic if you’re sharing libraries (VS will automatically package shared assemblies into your XAP, so you might end up with copies of System.Windows.Controls.Navigation.dll in 3 or 4 XAPs, and since they’re all packaged up with the rest of the assemblies, you’ll end up downloading the same DLL multiple times.

Describing how to load assemblies dynamically

I’ve created a little utility that I call “DynamicLoader,” which allows you to declare, in XAML, where your dynamic DLLs can be downloaded from and their interdependencies.  In my App.xaml, I specify the various libraries that may be loaded dynamically (incidentally, some of these libraries are also in my XAP, and the loader will use the ones built into the XAP rather than re-downloading them automatically).  I also describe the various interdependencies between my libraries.  Some are unnecessary (because the dependencies are already in the XAP), but I chose to be explicit for some of the more complex ones to remove all doubt.  An example (from my sample project) can be seen below.

<load:DynamicLoader x:Key="loader">
    <load:AssemblyDescription AssemblyName="DynamicallyLoadedLibrary"
                              Location="./DynamicallyLoadedLibrary.dll" />
    <load:AssemblyDescription AssemblyName="DynamicallyLoadedLibraryVB"
                              Location="./DynamicallyLoadedLibraryVB.dll" />
    <load:AssemblyDescription AssemblyName="LargeDynamicallyLoadedLibrary"
                              Location="./LargeDynamicallyLoadedLibrary.dll">
        <load:Dependency AssemblyName="System.Windows.Controls" />
        <load:Dependency AssemblyName="System.Windows.Controls.Data" />
        <load:Dependency AssemblyName="System.Windows.Controls.Data.DataForm.Toolkit" />
        <load:Dependency AssemblyName="System.Windows.Controls.Data.Input" />
        <load:Dependency AssemblyName="System.Windows.Controls.DataVisualization.Toolkit" />
        <load:Dependency AssemblyName="System.Windows.Controls.Navigation" />
        <load:Dependency AssemblyName="System.Windows.Data" />
        <load:Dependency AssemblyName="System.ComponentModel.DataAnnotations" />
    </load:AssemblyDescription>
    <load:AssemblyDescription AssemblyName="System.Windows.Controls"
                              Location="./System.Windows.Controls.dll" />
    <load:AssemblyDescription AssemblyName="System.Windows.Controls.Data"
                              Location="./System.Windows.Controls.Data.dll">
        <load:Dependency AssemblyName="System.ComponentModel.DataAnnotations" />
        <load:Dependency AssemblyName="System.Windows.Controls.Data.Input" />
        <load:Dependency AssemblyName="System.Windows.Data" />
    </load:AssemblyDescription>
    <load:AssemblyDescription AssemblyName="System.Windows.Controls.Data.DataForm.Toolkit"
                              Location="./System.Windows.Controls.Data.DataForm.Toolkit.dll">
        <load:Dependency AssemblyName="System.ComponentModel.DataAnnotations" />
        <load:Dependency AssemblyName="System.Windows.Data" />
    </load:AssemblyDescription>
    <load:AssemblyDescription AssemblyName="System.Windows.Controls.Data.Input"
                              Location="./System.Windows.Controls.Data.Input.dll">
        <load:Dependency AssemblyName="System.ComponentModel.DataAnnotations" />
    </load:AssemblyDescription>
    <load:AssemblyDescription AssemblyName="System.Windows.Controls.DataVisualization.Toolkit"
                              Location="./System.Windows.Controls.DataVisualization.Toolkit.dll" />
    <load:AssemblyDescription AssemblyName="System.Windows.Controls.Navigation"
                              Location="./System.Windows.Controls.Navigation.dll" />
    <load:AssemblyDescription AssemblyName="System.Windows.Data"
                              Location="./System.Windows.Data.dll" />
    <load:AssemblyDescription AssemblyName="System.ComponentModel.DataAnnotations"
                              Location="System.ComponentModel.DataAnnotations.dll" />
</load:DynamicLoader>

The Location specified in each AssemblyDescription is a URI.  In this case, all of my dlls are in my ClientBin directory, so they all begin with “./”, but these could be any URI that can be reached by the Silverlight application.  Furthermore, if you wish to use the new ClientHttp networking stack to do the downloading, you can choose to do so (as part of the AssemblyDescription).

The loader will asynchronously load any requested library (by name), and will only fire Loaded events once all of each assembly’s dependencies have been loaded.  This allows us to know when it’s safe to try to navigate to a Page in a dynamically-loaded assembly.

Creating Navigation UI

Nothing too special to share here.  HyperlinkButtons use the exact same URI scheme as before (for navigating to pages in referenced/dynamically-loaded assemblies).  Here’s what my XAML for my navigation bar looks like:

<Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}">
    <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}">

        <HyperlinkButton x:Name="Link1" Style="{StaticResource LinkStyle}"
                     NavigateUri="/Home" TargetName="ContentFrame" Content="home"/>

        <Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/>

        <HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}"
                     NavigateUri="/About" TargetName="ContentFrame" Content="about"/>

        <Rectangle x:Name="Divider2" Style="{StaticResource DividerStyle}"/>

        <HyperlinkButton x:Name="csharp" Style="{StaticResource LinkStyle}" IsEnabled="True"
                     NavigateUri="/DynamicallyLoadedLibrary;component/CSDynamicPage.dyn.xaml" TargetName="ContentFrame" Content="C# Class Library"/>

        <Rectangle x:Name="Divider3" Style="{StaticResource DividerStyle}"/>

        <HyperlinkButton x:Name="vb" Style="{StaticResource LinkStyle}" IsEnabled="True"
                     NavigateUri="/DynamicallyLoadedLibraryVB;component/VBDynamicPage.dyn.xaml" TargetName="ContentFrame" Content="VB Class Library"/>

        <Rectangle x:Name="Divider4" Style="{StaticResource DividerStyle}"/>

        <HyperlinkButton x:Name="large" Style="{StaticResource LinkStyle}" IsEnabled="True"
                     NavigateUri="/LargeDynamicallyLoadedLibrary;component/LargeDynamicPage.dyn.xaml" TargetName="ContentFrame" Content="Large Class Library"/>
    </StackPanel>
</Border>

Again, nothing too scary here – the XAML remains identical to its form without the dynamically-loaded libraries.

Adding on-demand navigation

The other important piece of this puzzle is a Frame control that uses the loader to ensure that dynamically-loaded libraries are loaded into the AppDomain before attempting to navigate to pages within those libraries.  I’ve provided a derived Frame class (DynamicNavigation.Utilities.Controls.Frame) that does precisely this.  As with my DynamicPage class, you should expect a few quirks, since the Frame class from which it derives doesn’t have any virtual methods.  Don’t cast my Frame to a System.Windows.Controls.Frame if you don’t want to get a bunch of extra events for Loading, Loaded, etc.  Also, since I couldn’t replace the NavigationService, if you subscribe to events on that service, you’ll see a large number of extra events being raised.  Neither of these eccentricities should hamper functionality, but you may see some unexpected activity if you watch those events!

My Frame class takes an DynamicLoader and delegates all loading of dynamic libraries to it.  Here’s what my XAML declaration of the Frame looks like (plus or minus some extraneous attributes):

<dnav:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
            DynamicLoader="{StaticResource loader}"
            Source="/Home">
    <dnav:Frame.UriMapper>
        <uriMapper:UriMapper>
            <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
            <uriMapper:UriMapping Uri="/{a};component/{p}" MappedUri="/{a};component/{p}" />
            <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
        </uriMapper:UriMapper>
    </dnav:Frame.UriMapper>
</dnav:Frame>

And that’s it!  With my custom Frame class, it take *zero* code to get dynamic loading up and running – only XAML is necessary.  Attach an Activity control to the loader, and you’re hot to trot!

In Conclusion

Well, there you have it!  I’d like to remind folks that none of the APIs here are intended to be concrete, and I make no guarantees of quality – I’m just trying to demonstrate how it can be done, and if the interest is there, I’ll keep investing some time in exploring this.  I’ve got a bunch of ideas for where I can go from here, things I’d like to change/improve, and I look forward to playing some more with these prototypes.

Without further ado, here are the links I’m sure you’re all waiting for:

What’s next?

Well, I’ve got a lot of ideas, and I’d love to hear some from you all.  Here are some of mine (not making any promises – I’m just experimenting with these):

  • Cache to IsoStore – downloaded libraries could be stored in Isolated Storage and loaded from there if they’re already on the machine
  • Install – When the application is taken out of browser, automatically download the assemblies to Isolated Storage and load from there, allowing the entire application to be run offline
  • URI scheme/custom loading – I’d love to be able to just specify the whole deal in a URI to a page, such as “pack://http:,,www.davidpoll.com,Samples,ClientBin,MyLibrary.dll;component/Page2.dyn.xaml” and not need the big manifest with assembly name/location mappings and dependencies.
  • Dependencies for particular URIs – allows the Frame control to pre-load assemblies before a particular page is loaded, even if the page is a local one (thanks, Austin, for this idea!)

Anyway, I’ve got a thousand other ideas racing through my mind right now, but those are just a few.  What do you think?  Is there anything you dislike about my approach or anything you particularly like?  Speak up!