Silverlight 3 Navigation: Navigating to Pages in referenced assemblies


At long last, Silverlight 3 has arrived!  That, in and of itself, is worth a thousand blog posts, but since there’s a plethora of folks out there doing precisely that as I type, I’ll start right out with some content on one of the new features of Silverlight 3 – Navigation.

In this post, I’ll look at some basic navigation functionality in Silverlight 3.  Silverlight 3 navigation is based on navigating a Frame control to a particular Page control.  On top of allowing non-linear navigation throughout your Silverlight application, the Frame control will integrate with your browser’s history and address bar, allowing you to provide deep-links to Pages within your application and provide a more web-like experience within Silverlight applications.

Using Pages and Navigation in this way allows you to structure your application much like you would a web page, with various pages providing differing functionality.  Like web pages, Silverlight 3 navigation allows you to pass data through query strings, perform fragment navigation, and so on – great topics for another post :)

Basic navigation to pages in your main (application) project is simple – HyperlinkButtons can target a Frame, and specify the path to the Page’s XAML file, like so:

<navigation:Frame x:Name="ContentFrame" Source="/Views/Home.xaml">
</navigation:Frame>
<StackPanel>
    <HyperlinkButton NavigateUri="/Views/Home.xaml" TargetName="ContentFrame" Content="home"/>
    <HyperlinkButton NavigateUri="/Views/About.xaml" TargetName="ContentFrame" Content="about"/>
</StackPanel>

In this case, my Pages are “Home” and “About”, located in a Views folder within my Silverlight Application project.  Clicking on the links results in changes to the address bar in my web browser, as you can see in the image below.  The path in the hyperlink is displayed after the hash mark (#) in the URL.  I could send someone this link to bring them right back to the specified page in my application.

Address bar after navigatingThis navigation functionality is very valuable in and of itself, but what happens if I want to put my pages in a separate assembly, ripe for reuse in other applications?  You’ll notice that the URIs for the Pages were relative to the application project (i.e. “/Views/Home.xaml” represents “<ApplicationProject>/Views/Home.xaml”).  This actually comes from the short form of the pack URI syntax in WPF (note: the full syntax is not supported in Silverlight, just a narrow subset).  If you want to access Pages in referenced assemblies, you can do so using the same “short” syntax: “/<ReferencedAssemblyName>;component/<PathToPage>/<PageName>.xaml”

With this syntax, I can place (and reference) pages in a class library that will be referenced by my Silverlight application, allowing me to create a structure like the one in the image below (a simplified example, I know, but it does illustrate the point!).

Solution structure showing /Pages/PageInLibrary.xaml in a class library called PageClassLibraryFor this project structure, my hyperlinks look like this:

<navigation:Frame x:Name="ContentFrame" Source="/Views/Home.xaml">
</navigation:Frame>
<StackPanel x:Name="LinksStackPanel">
    <HyperlinkButton NavigateUri="/Views/Home.xaml" TargetName="ContentFrame" Content="home"/>
    <HyperlinkButton NavigateUri="/Views/About.xaml" TargetName="ContentFrame" Content="about"/>
    <HyperlinkButton NavigateUri="/PageClassLibrary;component/Pages/PageInLibrary.xaml" TargetName="ContentFrame" Content="page in a class library"/>
</StackPanel>

Upon running and clicking on the “page in a class library” link, you’ll notice that the address bar shows a rather long and ugly URI:

http://www.davidpoll.com/Samples/MultiAssemblyNavigation/TestPage.html#/PageClassLibrary;component/Pages/PageInLibrary.xaml

Thankfully, we’ve supplied a great feature for the Frame control that helps you deal with ungainly URIs – the UriMapper.  UriMappers allow you to write code that takes a URI as input and produces a different URI as output.  The output URI will be used to locate the page, while the input URI is what the user will see.  In the SDK, we’ve supplied a default UriMapper that allows you to use some basic pattern matching to map URIs and keep your deep-links nice and tidy.

In my case, I’d be quite happy if I could get rid of the “/PageClassLibrary;component" that I had to add to my URI to reference Pages in a class library altogether.  Instead, I’d like my URI to look like this: “/Pages/PageInLibrary.xaml”.  To accomplish this, I add a UriMapper to my Frame:

<navigation:Frame Source="/Views/Home.xaml">
    <navigation:Frame.UriMapper>
        <uriMapper:UriMapper>
            <uriMapper:UriMapping Uri="/Pages/{path}" MappedUri="/PageClassLibrary;component/Pages/{path}" />
        </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>
<StackPanel>
    <HyperlinkButton NavigateUri="/Views/Home.xaml" TargetName="ContentFrame" Content="home"/>
    <HyperlinkButton NavigateUri="/Views/About.xaml" TargetName="ContentFrame" Content="about"/>
    <HyperlinkButton NavigateUri="/PageClassLibrary;component/Pages/PageInLibrary.xaml" TargetName="ContentFrame" Content="page in a class library"/>
    <HyperlinkButton NavigateUri="/Pages/PageInLibrary.xaml" TargetName="ContentFrame" Content="(mapped URI) page in a class library"/>
</StackPanel>

Now, users who navigate to the page are greeted with this, much “prettier” URL:

http://www.davidpoll.com/Samples/MultiAssemblyNavigation/TestPage.html#/Pages/PageInLibrary.xaml

And there you have it!  Dividing your pages across multiple assemblies doesn’t have to degrade user experience, and once you’re familiar with the style of URI that the navigation framework expects for such pages, it’s no more complex than standard navigation within your Silverlight application.

One last thought…

Before I leave you, I think it’s important to point something out: the Silverlight 3 SDK ships with a project template for Navigation for Visual Studio.  This template makes it really easy to get started with a styleable, Navigation-enabled Silverlight application, and does quite a lot for you, including specifying some default UriMappings:

<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
                  Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
    <navigation:Frame.UriMapper>
      <uriMapper:UriMapper>
        <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
        <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
      </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>

This is great – it makes the common case of having Pages within your “Views” folder in your application project really clean, allowing developers to reference “/Home” instead of “/Views/Home.xaml”.  However, if you look closely at the UriMappings, you’ll notice that “/{pageName}” will match our “/<AssemblyName>;component” syntax, mapping it to something else entirely, and preventing any attempts to link directly to such URIs.

There are a number of ways to address this issue, depending on your desired URI scheme, such as:

  • Delete the UriMappings entirely and go back to the full paths for all of the hyperlinks
  • Modify the existing UriMappings so that the “catch-all” doesn’t match the desired syntax
  • Add UriMappings for each referenced library before the catch-all mapping, like so:
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
                  Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
    <navigation:Frame.UriMapper>
      <uriMapper:UriMapper>
        <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
        <uriMapper:UriMapping Uri="/Pages/{path}" MappedUri="/PageClassLibrary;component/Pages/{path}" />
        <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
      </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>

  • Add a UriMapping to restore the original syntax before the catch-all mapping, like so:
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
                  Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
    <navigation:Frame.UriMapper>
      <uriMapper:UriMapper>
        <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
        <uriMapper:UriMapping Uri="/{assemblyName};component/{path}" MappedUri="/{assemblyName};component/{path}" />
        <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
      </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>

This list is certainly not exhaustive, but some combination of these options is likely to help you re-enable navigation to Pages in referenced assemblies.

If you’re still having trouble getting this to work, let me know, and I’ll try to help you troubleshoot!

Last, but not least…

You thought I might leave you without source and a link to the sample, didn’t you?!  Well, guess I showed you!  Oh, wait, I haven’t linked them yet.  Uhhh… pretend I didn’t say that :)

More posts will come now that Silverlight 3 is out!  I’ve been waiting with bated breath!  Thankfully, you folks aren’t around to suggest I use a mint :)

P.S.  Now that Silverlight 3 is out, I’ve updated all of my samples/source to work with Silverlight 3, the July 2009 Silverlight Toolkit, and .NET RIA Services July 2009 CTP.  You’ll find my Konami Code control and Activity control should both work out-of-the-box with Silverlight 3 RTM, and the rest only needed minor updates.  If folks are curious about my experiences updating those projects, let me know and I’ll do a short post.

, , , ,

  1. #1 by Dmitry Kadantsev on July 12, 2009 - 4:27 am

    Thanks, good article.

    There are also a lots of navigating technics known from SL 2 experience. E.g. dynamic creation of grid.children members in root page and simply usage of TabControl =)

    So every method has some advantages and disadv., but this basic navigation technic suits for most cases.

  2. #2 by Shoeb Syed on July 12, 2009 - 8:24 am

    Hi David,

    I was equally eager to get hands on Silverlight 3.0 Navigation framework since I first saw video on 3.0 beta. This post is really great as I never thought of possibility of keeping pages in different assemblies. Now that I know from this post, I wanted to know whether we can share the authentication across the assemblies.

    I am architecting College Automation Product using Silverlight 3.0. I am planning to use Navigation framework and RIA services. What I wanted to know is can User information shared across different class libraries keep pages (module specific) in separate libraries?

    Please advice.

    Thanks
    Shoeb

    • #3 by david.poll on July 12, 2009 - 12:24 pm

      Shoeb,

      I think the answer to your questions is yes, but with some caveats. In a single application (regardless of how many assemblies it’s split across), you should always be able to refer to the Application scope, which is where the Authentication/user information lives.

      In addition, the technique above specifically refers to *referenced* assemblies (i.e. assemblies that you did “add reference” to in your application project). Thus, if you’re hoping to dynamically download and load the external class libraries into your application (on-demand), you’re going to run into some problems, as the Navigation system currently does not support navigating to pages in dynamically loaded assemblies. I’m in the process of exploring some slick ways of working around this (and you can always load assemblies from the Pages themselves, but the Page would still need to live in a referenced assembly). When I come up with something, I’ll blog it :)

      In principle, though, for the simple case, you should indeed be able to share information across class libraries, just as you would in a standard SL application divided across multiple libraries. In the code above, I’ve even got a super-simple example of shared resources across these libraries, since I’ve got all of my styles declared in my application project and they’re referenced (through {StaticResource}) in the Page in the Class Library.

      -David

  3. #4 by Steve on July 12, 2009 - 11:03 am

    Very cool :) Thanks David.

  4. #5 by DigitalFox on July 12, 2009 - 12:03 pm

    Excellent! Thanks a lot. DF

  5. #6 by Fallon Massey on July 12, 2009 - 11:29 pm

    This would be even better if the pages were in a library on the server, and dynamically loaded when needed, instead of on loading.

    Still, thanks for the code.

    • #7 by david.poll on July 12, 2009 - 11:44 pm

      Yep, I agree that this is very powerful, although it’s not supported out of the box (in fact, you can’t — out of the box — navigate to Pages in assemblies not referenced by your application). I’m looking at some ways one might do this cleanly, and hope to blog some ideas in the near future.

      I’d love to hear what you consider to be requirements/scenarios for doing this. I’ve got a bunch of ideas, but if you’ve got something specific in mind, it’s excellent data for me :)

      • #8 by Dimaz Pramudya on July 13, 2009 - 3:05 am

        I’m trying to build a module-based Silverlight application and be able to load these modules on the fly depending on the security roles.

        Any idea on how to achieve this scenario?

        • #9 by david.poll on July 13, 2009 - 6:43 am

          For the moment, the best thing I can suggest is to have pages in your application that, upon navigation (handling OnNavigatedTo, for example), load the module and display the appropriate UI. In other words, while you’re navigating to Pages referenced in your application, the content of those pages can come from dynamically-loaded modules.

          As I said before, I’m looking at some ways to simplify this process with the SL3 bits, and will either post an example of my suggestion above or something more elegant :)

          Nonetheless, knowing your scenario is very valuable. In particular, the notion of loading the module based upon security role is an interesting boundary for that separation.

  6. #10 by Jonathan on July 13, 2009 - 3:18 am

    1. In SL3 beta I could drop into a stackpanel the following:

    but in the RC an error is returned – what is the new way to do this?
    2. Also is there a way to navigate to an aspx/html page in the silverlight web host?

    • #11 by david.poll on July 13, 2009 - 6:48 am

      I’m not sure your comment came out correctly. #1 looks like it’s missing some info :)

      #2, there’s not currently a supported way to host HTML within your Silverlight application, though some 3rd party control vendors have created controls to accomplish precisely this. For example: http://www.componentone.com/SuperProducts/HtmlHostSilverlight/

      I’ve never used it personally, so I can’t endorse it, but you may experiment with some of these offerings.

  7. #12 by Raghuraman on November 10, 2009 - 7:46 am

    David,

    Thanks for this Post.

    It makes for quick and neat reference for the uninitated.

    Regards

    Raghuraman

  8. #13 by Ashok Kumar on December 3, 2009 - 4:57 am

    Hi David,

    Thank you for the post. I would like to use the navigation like,
    Home>>Guitars>>Light Weight

    Will you please any sample to achieve like this.
    Thanks in advance for your help,
    Regards
    Ashok

  9. #14 by John Q. Public on February 19, 2010 - 11:29 am

    Great example, but for those of you that don’t want a ‘frame-based’ navigation where you have a bar at the top, but simply want a WPF style way of navigating between pages, where you can use the browser ‘Back’ button to go back, check out chapter 6 of MacDonald’s book Pro Silverlight 2 in C# 2008. It’s a different approach, without the frames/navigation bar, but it works fine.

(will not be published)