On-demand loading of assemblies with Silverlight Navigation

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!

49 thoughts on “On-demand loading of assemblies with Silverlight Navigation”

  1. Another Question:
    I explored the DynamicXAPLoading application and it also load the xap dynamically, should I also used the xaploader or the xaploadercontent, I am a little bit confused ..

    Please clarify …
    best

  2. Hi Dave;
    It’s nice to see one of SL PMs is actively looking into this important aspect of SL. Is SL4 going to have the functionality to build a large system, where the modules can by dynamically loaded by the framework rather us building it into each app?

    Thanks!

    1. Well, I can’t talk specifically about SL4, but I personally am really interested in seeing something like that happen, since I think it has huge amounts of potential and the scenarios for it are extremely compelling. At the very least, I’d like to keep exploring this area myself and see what comes out of it.

  3. Pingback: DotNetShoutout
  4. On-demand loading of assemblies with Silverlight Navigation « davidpoll.com…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…
    P.S.: Wanted to add great post!

  5. Thanks for this writeup. I was trying to figure out how to do this and this will save me a lot of time. I very much like the idea of using the local store as a cache.

    My own personal goal is to come up with a small login front end that once it is loaded starts a process in the background to download all of the various application assemblies into the local store.

    The background process will also dynamically build a menu of the items it has loaded and/or verfied that it already had the current version for.

    I may hold off on my little project and see what you end up with. You already have a good portion of what I need compelted! ;)

  6. FYI: I just read a blog post from Brad Abrams and he is showing an example of using MEF to dynamically load an object from a XAP file. I am still trying to wrap my head around MEF, and was just wondering if you have already looked down that road as a possible solution?

    1. ;) Thankfully, I know Brad reasonably well (he was one of the folks at Microsoft who interviewed me for my original internship in 2006), and we had a 2-tweet exchange on this on Twitter. I saw his post, and intend — at the very least — to explore this route. My hope is that if you want to use MEF to dynamically load your assemblies, you can just use the technique I described in my earlier post for navigating to those pages.

      Regardless, I’m going to play with it a bit in the coming weeks to see if any ideas fall out of it :)

  7. The way we are doing is:

    1. On Application start, go through from all .xap files and create .myXap file while removing any common dlls between xap file.

    2. download .myXap file on demand and maintain a list of dlls downloaded.

    3. Read the Manifest file and see which dll is missing and download those.

    Once the dlls are in the memory you can do whatever you want.

  8. Love the idea…
    Would like to see this as part of a UI composition framework (future versions of CAL or similar) so that we dont have to hard refer the dlls and xamls as you are doing it. A level of dependency abstraction would be nice.

    1. Yep, I’m definitely thinking about ways to do this that don’t require you to put the DynamicLoader directly on the Frame (e.g. the Frame would just delegate to a “loader” that would do the dynamic loading on-demand, and could use composition frameworks like MEF or Prism to drive the loading.

      In the meantime, this feels like a quick and easy solution :)

  9. works great!, just another question, if I want to set linkbutton’s NavigateURL on the code-bind, which code do I need to put? the following code did not work, thanks

    linkbutton3.NavigateUri = new Uri(@”/Library3;component/startpage.dyn.xaml”);

  10. I got another question but not relate to navigation frame directly. On the silverlight 3 OOB (out-of-Browser) app, it CANNOT open new html window which can be opened if app runs on IE, the error messsage is “The DOM/scripting bridge is disabled.”, Does mean OOB not to support any DOM bridge? Any way to open new html windows from OOB? Thanks and have a nice day!

    System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(“http://www.microsoft.com”), “_newWindow”, “toolbar=1,menubar=1,resizable=1,scrollbars=1,top=0,left=0″);

  11. Jon – you must use a linkbuttun and use the TargetName=”_blank” in OOB to get it to launch a new HTML Window. You cannot actively open a new window using a DOM Bridge since it is dissabled from the OOB Chrome.

    David – Excellent post. I am downloading source now to see what you did and see how it differs from my approaches. I just wish the Navigation Framework in SL3 was more extensible so we could plug in our own Navigation Logic without the UriMapper.

    1. Are you doing this through the built-in facility in SL3? If so, it’s great — it just does something different. In this case, I don’t want to user to have to wait for everything to download before seeing the application for the first time (which he would have to do with assembly caching built into SL3).

      1. You still have to load assemblies dinamically, but assembly caching takes care of the dependencies.

        Let’s say you have MainPage.xaml in Main.dll and RarePage.xaml in Rare.dll. Both dlls depend on DataGrid.dll. With assembly caching your ClientBin contains Main.xap, Rare.xap and DataGrid.zip. DataGrid.dll lives only in DataGrid.zip and is not repeated in Main.xap or Rare.xap. When your application loads, only Main.xap and DataGrid.zip are downloaded. If RarePage.xaml is needed, Rare.xap is downloaded, which tells us to download DataGrid.zip. You have to inspect the application manifest from Rare.xap and download the dependencies manually. Since DataGrid.zip was already downloaded as a dependency of Main.xap, the browser handles the cached data without downloading the bits again. Loading the already loaded assembly is a no op.

        Visit with fiddler the URL I posted earlier to see this in action.

      2. Hi David,

        We combine both: SL3 assembly cache with on-demand xap loading.

        With that, we can both take advantage of client side cache and great first time download experience (just a xap is downladed, all the rest are not until they are required).

        Please let us know if that’s what you were looking for, and if we can give you some tips.

  12. Great posts on the topic, David.
    I have two SL3 applications and wanted to launch the second one from the first. It works perfect but unfortunately all the App.Resources from second application are not loaded. Any ideas how to do that?

    1. The only thing I can think of is to manually merge the resource dictionary from the second app into the first app. Do they need to run in the same SL plugin?

      I’m still exploring some possibilities here — in the meantime, I think the only choice is to manually load in the app resources when you download/use the external XAP/dll.

  13. Hi David,

    We combine both: SL3 assembly cache with on-demand xap loading.

    With that, we can both take advantage of client side cache and great first time download experience (just a xap is downladed, all the rest are not until they are required).

    Please let us know if that’s what you were looking for, and if we can give you some tips.

  14. David,
    I attended Block’s MEF session at PDC09, during which he mentioned Dynamic XAP support in the SL4.0 Toolkit. Does your work relate to that?

    Can you point me to more information?

    Thanks
    Rob

  15. Hi David,

    I just wanted to comment that I love the direction you’re heading in with this stuff. I’m already using your dynamic pages in my apps, and I’m going to start working today on the new xaml, declarative stuff you’ve provided. Keep up the great work – it’s REALLY appreciated!!! :)

    regards,
    Patrick

  16. Hi David, My problem is that im using a 3rd party control for my web application and the dll for this control is quite heavy at 3 meg. Can i use your suite to dynamically load this dll for the application?

    Thanks

  17. Where do we put codebehind and events for the dynamicpages? I tried adding a button and click event to the delayloaded page, but can’t figure it out. I cannot access the button due to the shim model

    1. Please take a look at the sample source. In the DynamicallyLoadedLibrary project, for example, there are the following files:

      CSDynamicPage.dyn.xaml
      CSDynamicPage.xaml
      CSDynamicPage.xaml.cs

      You’ll put the UI for your page in CSDynamicPage.xaml, and the code-behind for that file is CSDynamicPage.xaml.cs. You shouldn’t be modifying the CSDynamicPage.dyn.xaml file at all.

  18. Hi Dave,

    Appreciate your efforts here. How do I share styles loaded in my main assembly with the dynamically-loaded ones? Is it possible?

    Do you have a simple sample of just loading .dlls on demand?

    Thx

    Helen

    1. The easiest way to do this is to put styles in your App.xaml. In your other projects, add a link to the App.xaml file in your main project, and set the build action not to build it into the XAP. This should allow you to work with your pages that use resources in the App.xaml in the designers.

      As for just loading dlls on demand, I don’t have a super-simple sample, but you can take a look at my old post from SL3: http://www.davidpoll.com/2009/07/20/on-demand-loading-of-assemblies-with-silverlight-navigation/

      That post does allow you to load directly from a dll, if I recall correctly. It’s a different model because there was no INavigationContentLoader, but the mechanism is otherwise the same, and you could modify the XapContentLoader I wrote to also work with dlls as targets.

  19. I am finding that if I use the VS development web server this all works fine, but as soon as I use IIS (6) the Loader cannot seem to resolve the URLs. Any ideas?

    Thx

    Helen

    1. I haven’t seen this type of behavior myself. In fact, if you look at my sample application, it’s doing a cross-domain load from one of my other servers (that’s actually a linux server running php). The server type shouldn’t matter… Could it be that you’re missing a MIME type for .xap files on your IIS 6 server?

      -David

  20. I have noticed with IIS6 that sometimes when you set mime types at the root level, they don’t get propagated down to child virtual directories (this got me several times in the past), but both .xap and .xaml mime types are set. Do I need to add .dll types? I tried but it didn’t seem to make a difference.

    My clientaccesspolicy file allows every porn site on the planet in (i.e. it is wide open). Fiddler just gives me a 500 error.

    Any other suggestions as to how I can debug this?

    Thanks a bunch

    Helen

  21. Hi David.
    I am trying to use the shim pages sample for authentication. How does the UriPattern Looklike?
    Below is what io have but isnt working for me.

    1. Unfortunately, at the moment, you can’t post XML in my comments section. I’ll try to fix this going forward. In the meantime, you can e-mail me at david at depoll dot com, and I’ll try to help you out.

  22. Sam Kea :
    Hi David.
    I am trying to use the shim pages sample for authentication. How does the UriPattern Looklike?
    Below is what io have but isnt working for me.

  23. David, thank you so much. You have no idea how long I struggled with the Silverlight-navigation+dynamic-loading problem.

    My question is, how much of the framework can I hve dynamically loaded, or is it a waste of time. For example, System.Windows.zip, System.Core.zip, mscorlib.zip, System.Net.zip? At some point I figure that there is no point in trying to delay loading of the kernel, but where is that point? Many thanks, Hyacinth.

    1. Hyacinth,

      Really, you’re not going to get anything out of dynamically loading anything that’s part of the runtime (e.g. Mscorlib). The only libraries that will be able to take advantage of this technique are those that are otherwise getting packaged into your XAP. Everything else is already local on users’ machines, meaning you already avoid paying the download cost for them — and they’re likely to be loaded anyway just because they’re so core to the runtime itself.

Leave a Reply