Navigation-driven Silverlight applications tend to share some common pieces of UI.  Traditionally, this has required sprinkling HyperlinkButtons throughout the application’s XAML.  For ASP.NET a number of controls intended to drive navigation exist.  These controls are driven by a sitemap, integrate well with authorization and roles (through sitemap trimming), and provide common user experiences around hierarchical application structures such as a TreeView-based list of hyperlinks and Breadcrumbs or navigation paths.  These controls provide the user with context as to where in the application/site they currently are as well as where within the application they can go.

In this post, I’ll introduce a few controls that attempt to mimic this behavior in a Navigation-driven Silverlight application.  I have added these controls to SLaB, and you’re welcome to use and modify them – or use them as examples for your own development – as you see fit.  The controls are:

  • TreeViewNavigator – a control which represents a sitemap as a TreeView

A TreeView Sitemap.

  • BreadCrumbNavigator – a control which represents your current location within the sitemap’s link hierarchy and allows navigation back up the hierarchy as well as to siblings of any node within the hierarchy

A BreadCrumb sitemap.

If you’ve been following the evolution of my sample projects, you may have noticed some navigation UI that’s not built into the default navigation application template and has controls that look like the ones I’ve described above.  Surprise, surprise! :)  You can see this in action here.

How does this type of UI work?

At its core, these controls have the same common set of functionality:

  • Rendering UI based on a sitemap
  • Keeping UI synchronized with the current page within the application
  • Trimming the sitemap based upon the roles the current user belongs to and the metadata in the sitemap

The goal of these controls is to allow the navigation structure of an application to be exposed to a user in a declarative fashion, much as one can do using ASP.NET sitemaps.  The API for the controls above uses Sitemaps that can be specified in XAML, but follow the same general structure as in ASP.NET.

For example, the sitemaps displayed above are produced by the following XAML:

    <SLaB:Sitemap x:Key="Sitemap"
                  Title="DavidPoll.com"
                  Description="My homepage.  Check it out and see what it's all about!">
        <SLaB:SitemapNode TargetName="ContentFrame"
                          Title="Home"
                          Description="The home page."
                          Uri="/Views/Home.xaml" />
        <SLaB:SitemapNode Title="SLaB Features"
                          Description="Demonstrations of Silverlight and Beyond features.">
            <SLaB:SitemapNode Title="Navigation">
                <SLaB:SitemapNode Title="Local Pages">
                    <SLaB:SitemapNode TargetName="ContentFrame"
                                      Title="About"
                                      Description="The about page."
                                      Roles="Foo"
                                      Uri="/Views/About.xaml" />
                    <SLaB:SitemapNode TargetName="ContentFrame"
                                      Title="A broken link"
                                      Uri="/Views/NonExistent.xaml" />
                </SLaB:SitemapNode>
                <SLaB:SitemapNode Title="On-Demand Xaps"
                                  Description="Pages in Xaps that will be loaded on-demand."
                                  TargetName="ContentFrame"
                                  Uri="/Views/SitemapPage.xaml?sitemapname=OnDemandSitemap">
                    <SLaB:SitemapNode Title="This Domain">
                        <SLaB:SitemapNode TargetName="ContentFrame"
                                          Title="Page in a big xap"
                                          Uri="pack://siteoforigin:,,SecondaryXap.xap/SecondaryXap;component/Page1.xaml" />
                        <SLaB:SitemapNode TargetName="ContentFrame"
                                          Title="Awesome Page"
                                          Uri="pack://siteoforigin:,,TernaryXap.xap/TernaryXap;component/AwesomePage.xaml" />
                        <SLaB:SitemapNode TargetName="ContentFrame"
                                          Title="Penguins (mapped Uri + metadata)"
                                          Uri="/remote/TernaryXap/AwesomePage.xaml?Site=http://www.davidpoll.com&amp;First Name=David&amp;Last Name=Poll&amp;Title=Penguins!&amp;Please rate..." />
                    </SLaB:SitemapNode>
                    <SLaB:SitemapNode Title="Cross-Domain">
                        <SLaB:SitemapNode TargetName="ContentFrame"
                                          Title="http://open.depoll.com Page"
                                          Uri="pack://http:,,open.depoll.com,SimpleApplication,SimpleApplication.xap/SimpleApplication;component/Depoll.xaml?Source=http://open.depoll.com&amp;File=wildlife.wmv" />
                    </SLaB:SitemapNode>
                </SLaB:SitemapNode>
                <SLaB:SitemapNode Title="Printing"
                                  Description="Pages that demonstrate printing utilities that simplify pagination of data and printing of complex data sets."
                                  TargetName="ContentFrame"
                                  Uri="/Views/SitemapPage.xaml?sitemapname=PrintingSitemap">
                    <SLaB:SitemapNode Title="Collection Printing (DataGrid)"
                                      Uri="pack://siteoforigin:,,ScratchPrintingProject.xap/ScratchPrintingProject;component/PrintingPage.xaml"
                                      TargetName="ContentFrame" />
                    <SLaB:SitemapNode Title="Collection Printing (Template-based)"
                                      Uri="pack://siteoforigin:,,ScratchPrintingProject.xap/ScratchPrintingProject;component/ItemTemplatePrinting.xaml"
                                      TargetName="ContentFrame" />
                    <SLaB:SitemapNode Title="Pre-defined page printing (Template-based)"
                                      Uri="pack://siteoforigin:,,ScratchPrintingProject.xap/ScratchPrintingProject;component/PredefinedPages.xaml"
                                      TargetName="ContentFrame" />
                </SLaB:SitemapNode>
                <SLaB:SitemapNode Title="Useful XAML Tools">
                    <SLaB:SitemapNode TargetName="ContentFrame"
                                      Title="Demo (simple QueryString)"
                                      Uri="/Views/ObservableDictionaryDemo.xaml?a=b&amp;c=d&amp;e=f&amp;g=h" />
                    <SLaB:SitemapNode TargetName="ContentFrame"
                                      Title="Demo (more complex QueryString)"
                                      Uri="/Views/ObservableDictionaryDemo.xaml?a=b&amp;Name=David Eitan Poll&amp;Url=http://www.davidpoll.com&amp;No Value&amp;Order=Dictionary" />
                </SLaB:SitemapNode>
            </SLaB:SitemapNode>
            <SLaB:SitemapNode Title="DavidPoll.com">
                <SLaB:SitemapNode TargetName="_blank"
                                  Title="Home Page"
                                  Uri="http://www.davidpoll.com" />
                <SLaB:SitemapNode TargetName="_blank"
                                  Title="Navigation Posts"
                                  Uri="http://www.davidpoll.com/tag/navigation/" />
                <SLaB:SitemapNode TargetName="_blank"
                                  Title="SLaB Posts"
                                  Uri="http://www.davidpoll.com/tag/silverlight-and-beyond-slab/" />
                <SLaB:SitemapNode TargetName="_blank"
                                  Title="SLaB Download Page"
                                  Uri="http://www.davidpoll.com/downloads-and-samples/#SLaB" />
            </SLaB:SitemapNode>
        </SLaB:SitemapNode>
    </SLaB:Sitemap>

You’ll note that the “About” link in the Sitemap above (bold/italic above) is missing from the TreeView.  This is because the sitemaps do principal-based trimming of the sitemaps, ensuring that users only see the links they’re authorized to see. 

In addition, the controls above stay in sync with the current page the user is viewing.

To get all of this functionality, there are three primary properties to set on the navigation controls (which derive from the Navigator abstract base class for this common functionality):

  • Sitemap – usually, this is set to a Sitemap that is defined in resources somewhere, and may be shared across multiple navigation controls.
  • CurrentSource – if the navigation control needs to stay in sync with the user’s current location (which is not always the case – e.g. on Error/404-ish pages), bind it to the CurrentSource of the Frame that it will be navigating
  • Principal – if the navigation control should trim the sitemap based upon the User’s authorization, bind the Principal to be that of the current user.  In the case of RIA Services, this can be done through the WebContext

Ultimately, using these controls just requires some simple XAML.  For the TreeViewNavigator:

<SLaB:TreeViewNavigator CurrentSource="{Binding ElementName=ContentFrame, Path=CurrentSource}"
                        Principal="{Binding User, Source={StaticResource WebContext}}"
                        Sitemap="{StaticResource Sitemap}" />

And for the BreadCrumbNavigator:

<SLaB:BreadCrumbNavigator CurrentSource="{Binding CurrentSource, ElementName=ContentFrame}"
                          Principal="{Binding User, Source={StaticResource WebContext}}"
                          Sitemap="{StaticResource Sitemap}" />

What can I customize?

These controls are made to work with any ISitemap, which is, at its core, a container for a collection of ISitemapNodes.  You can provide custom implementations of these, customizing your sitemaps to your heart’s content!  For example, you might make sitemaps and sitemap nodes which:

  • Retrieve their data from an ASP.NET xml-based sitemap file
  • Authorize users for access to nodes based upon more than just roles
  • Check authorization based on metadata on the page type itself, or by using a NavigationAuthorizer from the AuthContentLoader library
  • Import one sitemap into another (I’ve actually provided an implementation of this in SLaB so that sitemaps and sub-sitemaps can be used)

Furthermore, the controls themselves are look-less, and you should be able to completely re-template them, customizing how hyperlinks are displayed, how much of the tree is expanded, and so on.  If there’s something I’m missing, let me know!

So, can I see it in action?

Of course!  You know I never leave you without a demo!  In fact, today I’ve got two for you!

First, the SLaB demo application itself uses these controls.  Click around and see how things behave.  You’ll notice the controls are the centerpiece of the navigation UI, but also make appearances throughout the application, such as on “category pages” that list only the links within a particular section of the site, and on error pages within the application, making it easier for users to get back to useful locations within the application.

The second demo application is meant to show role-driven sitemap trimming.  It uses WCF RIA Services to drive authentication and authorization, and shows and hides parts of the sitemap based upon the roles the user belongs to.  You can log in using the following credentials:

User: Test

Password: _Testing

Experiment with the application and what happens to the navigation controls as you log in and log out.  This also uses the AuthContentLoader from SLaB to perform additional authorization before actually loading any page.

Authorization-driven navigation controls 

The XAML for the sitemap in the application above shows how access can be restricted and how trimming takes effect:

<SLaB:Sitemap x:Key="Sitemap"
                Title="Scratch Business Application"
                Description="A sample RIA Services Business application that uses SLaB to represent its navigation and do authorization.">
    <SLaB:SitemapNode Title="Home"
                        Description="The home page for the application"
                        Uri="/Views/Home.xaml" />
    <SLaB:SitemapNode Title="Broken Link"
                        Description="A broken link"
                        Uri="/Views/NonExistentPage.xaml" />
    <SLaB:SitemapNode Title="Protected Pages (Non-Trimmed)"
                        Description="Pages protected by authorization">
        <SLaB:SitemapNode Title="About"
                            Description="The About page for the application"
                            Uri="/Views/About.xaml" />
        <SLaB:SitemapNode Title="Page for registered users"
                            Description="A page that can only be visited by registered users"
                            Uri="/Views/RegisteredUsersPage.xaml" />
    </SLaB:SitemapNode>
    <SLaB:SitemapNode Title="Protected Pages (Trimmed)"
                        Roles="Registered Users"
                        Description="Pages protected by authorization">
        <SLaB:SitemapNode Title="About"
                            Description="The About page for the application"
                            Uri="/Views/About.xaml?trimmed" />
        <SLaB:SitemapNode Title="Page for registered users"
                            Description="A page that can only be visited by registered users"
                            Uri="/Views/RegisteredUsersPage.xaml?trimmed" />
    </SLaB:SitemapNode>
    <SLaB:SitemapNode Title="Protected Pages (Leaf nodes trimmed)"
                        Description="Pages protected by authorization">
        <SLaB:SitemapNode Title="About"
                            Roles="Registered Users"
                            Description="The About page for the application"
                            Uri="/Views/About.xaml?leaftrimmed" />
        <SLaB:SitemapNode Title="Page for registered users"
                            Roles="Registered Users"
                            Description="A page that can only be visited by registered users"
                            Uri="/Views/RegisteredUsersPage.xaml?leaftrimmed" />
    </SLaB:SitemapNode>
    <SLaB:SitemapNode Title="DavidPoll.com"
                        Description="David Poll's homepage"
                        Uri="http://www.davidpoll.com"
                        TargetName="_blank" />
</SLaB:Sitemap>

Cool… but where are the bits?

Well, the good news is that you can get all of these controls in my Silverlight and Beyond (SLaB) libraries!  Give them a shot and let me know what you think.  What’s missing from these controls?  What other pieces of user experience are you looking for?  Are the behaviors of the TreeViewNavigator and BreadCrumbNavigator correct for your scenarios and desired UX?

With that said, here’s a summary of the links and access to the source!

  • Live Sample (source -- found in the SLaB v0.7 source under "ScratchApplication")
  • Live Sample using RIA Services for AuthN/AuthX (source

    SLaB v0.7 (includes source, a sample app, some tests, and binaries)

    • For the latest version, please check out SLaB on my Downloads and Samples page.
    • The v0.7 download of SLaB includes the following changes:
      • Added TryImportResourceDictionary that allows XAML resource dictionaries to be imported but fail quietly (so that if not all dependencies for a control are met, other controls in the library (that share the same generic.xaml) can still be used.
      • Added XamlDependencyAttribute, which ensures that Xaml-only assembly dependencies can be declared and appear as dependencies in the assembly metadata.
      • Other minor bugfixes
    • In the interim (since my last post with SLaB), I also produced the v0.6 version, which had the following changes:
      • Made CollectionPrinter work for controls like DataGrid when they auto-generate columns for generic collections (based on the type in IEnumerable<T>)
      • Added a utility method that allows you to get the MethodInfo for an arbitrary method, including private ones (from anywhere that the method is accessible)
      • Other minor bugfixes

    As always, I’d love to know what you think!