Silverlight 3 Navigation: Navigating to Pages in dynamically-loaded assemblies


In my last post on the new navigation feature in Silverlight, I explored how you can navigate to pages in assemblies/projects referenced by your main application.  Almost immediately, a number of you asked how you can navigate to pages in assemblies that have been dynamically loaded, allowing you to delay downloading certain pieces of your application until users request them.  This allows you to reduce the initial size (and thereby load/download time) of your application to something more appropriate for your users and avoid using bandwidth to download components to users’ machines that they may never actually user/encounter.

Out of the box, Silverlight 3’s navigation feature only allows navigating to Pages in assemblies referenced by your application project and packaged in your XAP.  There are a number of technical reasons behind this that I know we’d like to explore and hopefully address in future versions, but I won’t go into the details here.  There is, however, one exception to this rule that prevents us from navigating to Pages in dynamically-loaded assemblies: you can load Pages with no code behind that live in such assemblies.

After a few months of thinking about this problem in the back of my mind, it finally clicked for me: this exception is the way in!  All of the other workarounds I was able to think of required Pages located in the XAP that would load their contents dynamically and didn’t really take advantage of the Navigation feature at all.

Anyway, I think I finally found a reasonably good workaround, and while it’s a little hacky-er than I would’ve liked, it doesn’t fundamentally change the feel of using Silverlight navigation.  Here’s the gist of my strategy:

  • Load an assembly containing a Page dynamically
  • Include in this assembly a “shim” Page with no code behind.  In its XAML, reference the real page with code behind, and forward any navigation-related information (events, title, query strings, etc.) to that Page
  • When that page is navigated to, replace the Frame’s content with the real page.
  • From the application, navigate to the “shim” page in the dynamically-loaded assembly

Ok, I know that sounds a little roundabout, but I think I’ve made it pretty easy to accomplish for a developer now.  More importantly – it works!

Let me go into a little more detail…

The Library

I’ve created a tiny little assembly that I’ve called “DynamicNavigation”.  It contains two classes, both of which are made for use in XAML:

  • DynamicPage: A descendant of the Page class that allows the Shim to communicate with it.  If I did my job correctly, for 99% of cases, you can navigate directly to this page (rather than its Shim) if you haven’t dynamically loaded the assembly.  There shouldn’t really be any new public interface (except for some “new” properties that hide the original Page properties, since they aren’t virtual).
  • DynamicPageShim:  Another descendant of the Page class, meant to be used in XAML with no code behind.  This class will forward any navigation events, etc., to the DynamicPage it takes as its content.  The goal here was to minimize the XAML so that the additional size due to these files is small.  There may be further optimizations I can do, but it’s getting pretty close :)

The Developer Experience

Ok, that’s all great, but what do I have to do to use it?

Well, instead of 2 files (<PageName>.xaml and <PageName>.xaml.cs/vb), creating a Page now takes 3 files:

  • <PageName>.dyn.xaml:
<dyn:DynamicPageShim xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:dyn="clr-namespace:DynamicNavigation;assembly=DynamicNavigation"
           xmlns:my="clr-namespace:DynamicallyLoadedLibrary;assembly=DynamicallyLoadedLibrary">
    <my:PageName />
</dyn:DynamicPageShim>

I’ve underlined two important lines here.  First, “assembly=DynamicallyLoadedLibrary” – normally, this would be unnecessary in an xmlns definition, since I can refer to the library in which the XAML file exists without the “assembly=” syntax.  For the same technical reasons that we normally can’t load a Page in a dynamically-loaded assembly, this name needs to be fully specified, since it allows the XAML loader to find the dynamically-loaded assembly in which your DynamicPage resides.  Next, you can see that this Shim takes an instance of the DynamicPage as its content.  This allows it to forward navigation-related information to the Page and ensure that the Frame has the correct content.

  • <PageName>.xaml
<dyn:DynamicPage x:Class="DynamicallyLoadedLibrary.CSDynamicPage"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:dyn="clr-namespace:DynamicNavigation;assembly=DynamicNavigation"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           mc:Ignorable="d"
           d:DesignWidth="640" d:DesignHeight="480"
           Title="Page Title">
    <Grid x:Name="LayoutRoot">

       ...

    </Grid>
</dyn:DynamicPage>

As you can see, there’s really nothing special about the DynamicPage except that the open/close tags are of type DynamicPage rather than Page.  Gotta love it!

  • <PageName>.xaml.cs/vb

I won’t show an example here, but there’s really nothing special about this.  You’ll develop exactly as though the DynamicPage were a real Page.  The only thing to watch out for: please don’t cast the DynamicPage to a Page, since this will cause it to refer to hidden properties that may not be set.  Again, for 99% of cases, this is probably not an issue, but it bears a little bit of consideration.

The last piece of the puzzle is actually navigating to the page.  Well, it’s pretty straightforward: navigate just as we did in my other post (using “;component” pack URI syntax).  The only difference: instead of navigating to <PageName>.xaml, you’ll navigate to <PageName>.dyn.xaml.

And that’s it!  The rest should just work!

So, without further ado, I’ve got some tools for you!

The Goods

The download above is a zip containing 3 things:

  1. DynamicNavigation.dll – an assembly you should reference in both your application project (and include in your xap) as well as any assemblies you wish to dynamically load (so that you can include the XAML files above)
  2. DynamicPageCS.zip – this is a Visual Studio item template that will help get you started.  Drop this zip (without extracting) in “<My Documents>\Visual Studio 2008\Templates\Item Templates\Visual C#”.  Now, if you open Visual Studio and a C# Silverlight 3 project, right-click the project (in the solution explorer) and choose “Add new…”.  Select “Silverlight Dynamic Page” and choose a name for your page.  The item template will create the 3 files for you.  Open up “<PageName>.xaml” and start editing!
  3. DynamicPageVB.zip – I didn’t forget about you VB guys!  Follow the same instructions above (but placing the item template in the VB folder instead of the C# one), and you should be set to go!

I’m sure there are some bugs and issues with what I did (for example, the item templates don’t add a reference to DynamicNavigation.dll to your project yet… I hope to fix this once I figure out how!).  Let me know if something’s causing you problems, and I’ll see if I can work it out in my (not so) copious free time :)

The Sample

You know me – I can’t leave you without a live sample of this thing in action!  Take a look here (for your viewing pleasure):

Enjoy!  You may even find a cameo from a previous post in there!

The Tips

When using my DynamicNavigation tools, there are a few things you should keep in mind.  Hopefully this list will get smaller going forward, but in the meantime, these are some things to remember:

  • It’s still up to you to load the assemblies dynamically into your AppDomain before attempting to navigate to them.  A super-simple example of how to do this can be found in the code-behind of my MainPage.xaml.  You can also use tools like Prism to accomplish something similar.  There are definitely some tricks that would make this feel more on-demand than what I’ve done in the sample, but that’s a topic for a future post! :)
  • Along those lines, please make sure any assemblies your dynamically-loaded assembly is dependent upon are loaded before trying to navigate.  I don’t do anything to try to resolve those dependencies.
  • Try to avoid casting your DynamicPage to its parent type – Page.  If you do, you’ll find some properties aren’t set to your desired values (such as Title, NavigationCacheMode, etc.).
  • My libraries should replace Frame’s Content with the intended page, so feel free to bind into Frame.Content just as you would otherwise.  You may find that Frame.Content does change twice during a navigation rather than just once, but otherwise, you should get the same events/virtual methods called/etc. on your Page.
  • Expect Caching and Title to still be honored properly on your page!
  • Once you’ve created your <PageName>.dyn.xaml file, you should rarely, if ever, need to modify it.  It’s just a shim, and any other work you’d like to do should be possible directly through the DynamicPage files (.xaml and .xaml.cs/vb)

I tried to keep the library as small as possible.  Anything I build on top of this, I intend to put in a separate library, so that you’re not forced to take additional overhead for the simplest of cases, where you’ve already done your dynamic loading and just want to navigate to pages in those libraries.

The Conclusion

My hope is that this will get you started down the road to navigating to dynamically-loaded assemblies.  I know it’s a bit obtuse, and still harder to do than it ought to be, but what I’d like to do going forward is take this development and create an SL3 library that will make it really simple to download assemblies from your server on-demand during navigation (and perhaps devise some other nifty features).

Please, let me know what you think.  Should I spend more time on these things?  How valuable is using navigation in this way to you?  What kinds of scenarios would you still like to see enabled?

If I’ve confused the heck out of you, I sincerely apologize.  I know I found myself struggling to describe this adequately, so please, ask questions!  I’m happy to share my thoughts/experiences.

Comment away!

P.S. I’m finally on Twitter (at least partially to my dismay :) ).  You can find me at @depoll

 

Update: Julian Biddle has posted a wholly different strategy that would allow you to divide your Silverlight application across multiple XAPs on his blog: http://anoriginalidea.wordpress.com/2009/07/22/how-to-build-huge-dynamic-cross-platform-silverlight-business-applications/.  Take a look – it’s an interesting read/approach :)

, , ,

  1. #1 by Dimaz Pramudya on July 15, 2009 - 10:29 pm

    Awesome Dave.
    I read an article about this earlier today and now you’ve got the whole solution!

    Thanks for sharing this.
    Next question is.. Does Silverlight 3 provide cache mechanism for Dynamic Assembly? Perhaps caching it into IsolatedStorage or something?

    Dimaz

    • #2 by david.poll on July 16, 2009 - 12:31 am

      So, I don’t think so out of the box. That said, I think WebClient uses the browser’s cache (https://silverlight.net/forums/p/99108/226314.aspx), so some of this might be automatic.

      Right now I’m working on a nice little declarative dynamic assembly loader and an extension for the Frame class that would use. I’m hoping to make it smart about IsoStore and even OOB (i.e. when you try to install the app, it would download and store the assemblies, etc.), as well as downloading dependent assemblies. If I can get something good together, I’ll blog it :)

  2. #3 by Mike Greenway on July 16, 2009 - 2:54 pm

    Works great! thank you so much.

    I have a hard time understanding why MS isn’t solving this problem, right now. Web people arn’t use to waiting for pages to load, they don’t know it SL and worth the wait, they leave. I see people saying ” this is the best SL app”, I go to the sight and sure enough, the first thing I do is wait, and wait and wait.

    MS need to make this a simple declaration, which download pages so marked and puts them in ISO store and then navigation looks there first. Perfect!

    Thank You so much for sharing you work.

    Mike Greenway

    • #4 by david.poll on July 17, 2009 - 12:02 am

      Mike,

      I’m definitely with you about the sentiment. We’re definitely spending some time thinking on this (no promises, of course). In the meantime, I’m experimenting with something that works very much like you’re suggesting (I’ll definitely be blogging it — I’ve already got a prototype working where navigation automatically loads an assembly from the web when you try to navigate to a page in another assembly, now I just want to add things like IsoStore, updating, offlining, and reading from XAPs or Zips rather than only dlls (most of these are reasonably easy to implement now, I just need to sit and do the work :) ).

  3. #5 by Jon on July 20, 2009 - 11:48 am

    Great! it works on my local dev box, when I deploy app to iis 6, the silverlight can not download the dll, so what should I be changed/added the new MIME type on the IIS, thanks for your time.

    • #6 by david.poll on July 20, 2009 - 12:00 pm

      Honestly, I haven’t tried deploying to IIS6. Personally, I’m using IIS7, on which it worked out of the box. I imagine you’d need MIME types for DLLs on your machine, since that’s what’s being downloaded. If that doesn’t work, I can ask around to someone who’s more of an IIS expert :)

  4. #7 by mitkodi on July 20, 2009 - 3:02 pm

    Thank you very much Dave for all your ideas. In this case I use a little bit different solution (but I not tested it enough so far :( ) for navigation to dynamically loaded views. I use an empty navigation Page let’s call it PagePlaceholder which resides in the “main” Silverligh applicaition (said “main” because I build dynamically loadable views into separate Silverlight applications to be packed as xap files, so I can benefit from its smaller size). In hyperlink buttons corresponding to dynamically loadable views I set NavigationTarget to something like: /DPage/xapFileName/viewClassName and add to the navigation Frame a UriMapping like the following one:
    Next in PagePlaceholder’s OnNavigatedTo event handler I load (if not already loaded) the xap file (and all its assemblies) which name is equal to NavigationContext.QueryString["xap"] and set PagePlaceholder.Content to a newly created instance of type NavigationContext.QueryString["class"].
    In this way I achieve the following: load external assemblies only when needed, minify downloading time due to xap compression, showing a progress indicator and a message (as original PagePlaceholder’s content) while downloading a view.

  5. #8 by mitkodi on July 20, 2009 - 3:05 pm

    Sorry, UriMapping was removed from my previous post: Uri=”/DPage/{xapFileName}/{viewClassName}” MappedUri=”/Views/PagePlaceholder.xaml?class={viewClassName}&xap={xapFileName}”

    • #9 by david.poll on July 20, 2009 - 7:32 pm

      Yeah, I spent a bunch of time thinking about options along these lines. In this case, there were a few obstacles:
      1) I wanted to avoid using the UriMapper for this, since it would make using other UriMapper strategies more difficult (though I suppose I could have done it behind-the-scenes)
      2) I wanted things to work as though the Page were being navigated to directly. If an error occurs in the constructor, it should halt navigation at the right time, rather than successfully navigating to the host page and then waiting for the real page to load. This can create stray history entries, etc.
      3) Sometimes the issue is that the XAP has already been loaded by other tools like Prism, and my hope was to work transparently/automatically with those.

      There were a few other reasons I went this route, but yours is certainly valid as well — there is nothing that will prevent you from dynamically loading assemblies within a page once you’ve navigated and then hosting that content, as you’ve suggested, and for some this is a viable option :)

  6. #10 by Mike Greenway on July 28, 2009 - 7:06 am

    Today I compiled and ran your code on my machine. while looking at the dynamicly loaded c# page (and thinking how cool it is), I copy the url, open firefox 3, pasted the url and recived a page not found error. same thing when I open another IE. what going on?
    Mike Greenway

    • #11 by david.poll on July 28, 2009 - 9:47 am

      Mike,

      You should take a look at my most recent post: http://www.davidpoll.com/2009/07/20/on-demand-loading-of-assemblies-with-silverlight-navigation/

      The issue is that I have a button that loads the assemblies that those pages are on into the app domain. As a result, if you try to navigate directly there from outside of the application, the assembly hasn’t been downloaded yet — hence the error navigating to the page. In my more recent post, I look at how to resolve that by adding on-demand loading of assemblies to my derived version of the Frame control. This way, when the Frame is asked to navigate to a page and the page hasn’t been downloaded yet, the Frame will automatically go fetch it and then (once it has been loaded into the application) navigate to that page — eliminating the error you see.

      It’s entirely expected behavior :)

  7. #12 by Mike Greenway on July 28, 2009 - 7:10 am

    Oh, I was wondering, have you looked at the way MEF solves this problem? is it aplicable to sl3 nav aplications?

    Mike Greenway

  8. #15 by Roy on November 24, 2009 - 10:26 am

    Great Stuff, thanks for sharing
    I’m now using this style of Frame Navigation but using a generic MVVM.
    Thanks for putting me on the right track.

  9. #16 by Sam on January 20, 2010 - 1:08 pm

    Excuse my ignorance…but how is this different than what Microsoft’s Prism is trying to accomplish?

    • #17 by david.poll on January 29, 2010 - 12:39 am

      This isn’t meant to be a framework for building general componentized applications. This particular post is meant to show how you can load pages in assemblies that Prism has loaded for you.

  10. #18 by Ruslan Urban on January 27, 2010 - 8:39 am

    David,
    Do you know if the limitation of loading pages from external libraries (dynamically loaded modules) still exists in Silverlight 4? Do you have an example for SL4?

    • #19 by david.poll on January 29, 2010 - 12:38 am

      Ruslan,

      Strictly speaking, the limitation still exists. That said, it is now possible to produce a reusable custom ContentLoader that no longer has this limitation. I’m soon going to blog a solution that does some things with pages in dynamically-loaded assemblies using the SL4 feature.

      -David

  11. #20 by Sven on February 11, 2010 - 4:37 am

    Good article, lame constraint :(

(will not be published)