WCF RIA Services Silverlight Business Application – Using ASP.NET SiteMap for Navigation

Note: This blog post examples is based on the WCF RIA Services PDC Beta and WCF RIA Services VS 2010 Preview, changes to the framework may happen before it hits RTM.

This blog post will be about using the ASP.NET SiteMap together with WCF RIA Services and the Navigation feature added to Silverlight. I assume you are familiar to how the Navigation feature will work when reading this blog post, even if you don’t know you may find this blog post interesting.

The examples in this blog post uses the Silverlight Business Application template shipped with WCF RIA Services.

The Navigation feature in Silverlight uses by default the name of a View to show, so I decided to use the same in the web.sitemap file. The Business Application by default will have two hardcoded hyperlinks at the top of the screen, the Home and About. I decided to reuse the same Views and names. What I did first was to add a Site Map to the web project that hosts the Silverlight app. Here is the web.sitemap:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="Root" title="Root"  description="">
      <siteMapNode url="Home" title="Home"  description="">
        <siteMapNode url="Test" title="Test" description=""/>
      </siteMapNode>
        <siteMapNode url="About" title="About"  description="" />
    </siteMapNode>
</siteMap>


I decided to add two additional Views one for the SiteMap root node and a sub View to the Home View. The url attribute of the SiteMapNode is the name of the .xaml located in the View folder of the Silvelright Business Application project. I removed the Home and About Hyperlink from the MainPage.xaml’s LinkStackPanel. I also added a Grid and into it a TreeView control. Here is how the MainPage.xaml will look like now:

<UserControl ...>

  <Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}">

    <Border x:Name="ContentBorder" Style="{StaticResource ContentBorderStyle}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.25*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <controls:TreeView Grid.Column="0" x:Name="navigationTree"/>
                
                <navigation:Frame
Grid.Column="1"
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> </Grid> </Border> <Grid Style="{StaticResource NavigationOuterGridStyle}"> <Grid x:Name="NavigationGrid" Style="{StaticResource NavigationGridStyle}"> <Border x:Name="BrandingBorder" Style="{StaticResource BrandingBorderStyle}"> <StackPanel x:Name="BrandingStackPanel" Style="{StaticResource BrandingStackPanelStyle}"> <ContentControl Style="{StaticResource LogoIcon}"/> <TextBlock x:Name="ApplicationNameTextBlock"
Style="{StaticResource ApplicationNameStyle}" Text="{Binding ApplicationStrings.ApplicationName, Source={StaticResource ResourceWrapper}}"/> </StackPanel> </Border> <Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}"> <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}"/> </Border> </Grid> <Border x:Name="loginContainer" Style="{StaticResource LoginContainerStyle}"></Border> </Grid> </Grid> </UserControl>


Note: I added a TreeView only to fill it with the whole SiteMap.

image

ASP.NET’s SiteMap feature uses SiteMapNode classes, the SiteMapNode class don’t have the KeyAttribute which WCF RIA Services needs, so I created my own SiteMapNode, I named it SiteMapData which holds the basic information of the SIteMap, like the Url, the Name to be displayed and ChildNodes:

public class SiteMapData
{
   private IEnumerable<SiteMapData> _childNodes = null;

   [Key]
   public string View { get; set; }

   public string ParentView { get; set; }

   public string Name { get; set; }

   [Include]
   [Association("SiteMap", "View", "ParentView")]
   public IEnumerable<SiteMapData> ChildNodes
   {
      get { return _childNodes; }
      set { _childNodes = value; }
    }
}


The following is the SiteMapService which has one single Query method returning a list of SiteMapData:

[EnableClientAccess()]
public class SiteMapService : DomainService
{
   public IEnumerable<SiteMapData> GetSiteMap()
   {
      var siteMapDataNodes = new List<SiteMapData>();

       var siteMapDataNode = new SiteMapData()
           { Name = SiteMap.RootNode.Title, View = SiteMap.RootNode.Url, ParentView = "/" };
        
       siteMapDataNode.ChildNodes = GetNodesRecursive(SiteMap.RootNode);

       siteMapDataNodes.Add(siteMapDataNode);

       return siteMapDataNodes;
   }

   private List<SiteMapData> GetNodesRecursive(SiteMapNode siteMapNode)
   {
      var siteMapNodes = new List<SiteMapData>();

       if (siteMapNode.HasChildNodes)
       {
          foreach (SiteMapNode node in siteMapNode.ChildNodes)
          {
             var siteMapDataNode = new SiteMapData()
                 { Name = node.Title, View = node.Url, ParentView = siteMapNode.Url };
                    
             siteMapDataNode.ChildNodes = GetNodesRecursive(node);
                    
             siteMapNodes.Add(siteMapDataNode);
           }
       }
   
       return siteMapNodes;
    }
}

I guess you can figure out what the code does, it uses the ASP.Net SiteMap API to get the SiteMap and map it to the SiteMapData entity.

The following code is added to the MainPage.xaml.cs and will get the SiteMap from the SiteMapService and dynamically fill a TreeView and add the navigation hyperlinks to the page.

Note: I didn’t use any templates, just code to show the concept and not a perfect code. You can simply use templates and other controls instead.

public partial class MainPage : UserControl
{
    public MainPage()
    {
         InitializeComponent();
         this.loginContainer.Child = new LoginStatus();

         var siteMapContext = new SiteMapContext();

         siteMapContext.Load<SiteMapData>(siteMapContext.GetSiteMapQuery(), lo =>
                  {
                     if (!lo.HasError)
                     {
                         navigationTree.Items.Add(FillTreeView(lo.Entities).First());

                         foreach ( var link in GetNavigationBarHyperLinks(
lo.Entities.First().ChildNodes)) LinksStackPanel.Children.Add(link); } }, null); } private IEnumerable<TreeViewItem> FillTreeView(
IEnumerable<SiteMapData> entities) { var treeViewItems = new List<TreeViewItem>(); foreach (SiteMapData siteMapData in entities) { var treeViewItem = new TreeViewItem(); treeViewItem.Header = CreateTreeViewItemLink(siteMapData); if (siteMapData.ChildNodes != null || siteMapData.ChildNodes.Count > 0) foreach (var node in FillTreeView(siteMapData.ChildNodes)) treeViewItem.Items.Add(node); treeViewItems.Add(treeViewItem); } return treeViewItems; } private IEnumerable<HyperlinkButton> GetNavigationBarHyperLinks(
IEnumerable<SiteMapData> entities) { var hyperLinks = new List<HyperlinkButton>(); foreach (SiteMapData siteMapData in entities) hyperLinks.Add(CreateNavigationLink(siteMapData)); return hyperLinks; } private static HyperlinkButton CreateTreeViewItemLink(SiteMapData siteMapData) { var hyperlinkButton = new HyperlinkButton(); hyperlinkButton.NavigateUri = new Uri(siteMapData.View, UriKind.Relative); hyperlinkButton.TargetName = "ContentFrame"; hyperlinkButton.Content = siteMapData.Name; return hyperlinkButton; } private static HyperlinkButton CreateNavigationLink(SiteMapData siteMapData) { var hyperlinkButton = new HyperlinkButton(); hyperlinkButton.Style = App.Current.Resources["LinkStyle"] as Style; hyperlinkButton.NavigateUri = new Uri(siteMapData.View, UriKind.Relative); hyperlinkButton.TargetName = "ContentFrame"; hyperlinkButton.Content = siteMapData.Name; return hyperlinkButton; } ... }

I hope you find this blog post interesting.

If you want to know when I publish a new blog post, you can follow me on twitter: http://www.twitter.com/fredrikn

3 Comments

  • Nice example Fredrik. I recently had a situation when I wanted this exact behavior and ended up doing it a different way. Maybe I´ll rethink my implementation and go with your solution. Thanks!


  • @Johan Leino:


    I don't think you need to change your solution, your solution works fine. I just wanted to show how SiteMap can be used in a Silverlight app, the problem with my solution is the access over the network. But it will only take place when the app first starts.

  • Can i know where to download the code?
    Because i have done the similar kind of project but when i deploy it i am getting method not found from the service and after that i am getting unable to connect to Sql Sever?

    can you please help me the deployment steps and the web.config details please.

Comments have been disabled for this content.