Integrating Prism v4 Region Navigation with Silverlight Frame Navigation

Updated 10/14/2010

This blog post was updated on 10/14/2010 and requires Prism 4 or later.

The code download was updated and the Prismv4FrameRegionNavigation project, FrameRegionNavigationService was updated, adding support for the NavigationFailed and Navigating events.

Updated 2/9/2011

I have published a related blog post that shows how to implement cancelling of the navigation request when using the Silverlight Frame Navigation API that can be read here.

Introduction

This article covers integrating Prism v4 Region Navigation with Silverlight 4 Frame Navigation. Integration is not directly supported by the Prism v4 Library. The included download provides the required classes to integrate the two navigation API's.

The below image pictures the demo application; notice the user friendly, deep link unmapped Uri in the address bar. The right ListBox lists an Item view was opened and subsequently navigated away from.

This included application demonstrates how to integrate Prism Region Navigation with Silverlight Frame Navigation. Additionally, we'll examine implementing Non-Linear Navigation in this configuration.

Prerequisites

A general knowledge of Prism regions, modules, MEF, Silverlight Frame Navigation and the Silverlight UriMapper is required to understand this article and demo application.

Before proceeding, please read the Prism v4 Region Navigation Pipeline article. Information presented in that article will not be repeated here.

Integrating Navigation API's

The demo application includes the Prismv4FrameRegionNavigation assembly. This assembly provides the required classes to integrate the two navigation API's such as a content loader, region adapter, journal, navigation service and region behavior. For the remainder of the article, I'll refer to the Prismv4FrameRegionNavigation assembly as, "the assembly."

 <navigation:Frame 
    x:Name="ContentFrame" 
    Style="{StaticResource ContentFrameStyle}" 
    Source="/HomeView" 
    Navigated="ContentFrame_Navigated" 
    NavigationFailed="ContentFrame_NavigationFailed"
    prism:RegionManager.RegionName="MainContentRegion"
    >

    <navigation:Frame.ContentLoader>
        <prism_Regions:FrameContentLoader RegionName="MainContentRegion"/>
    </navigation:Frame.ContentLoader>

    <navigation:Frame.UriMapper>
        <uriMapper:UriMapper>
                        
         <!--Default applicaiton mapper-->
         <uriMapper:UriMapping Uri="" MappedUri="/ThePhoneCompany.Views.HomeView"/>
                       
         <!--Used to add a new record-->
         <uriMapper:UriMapping Uri="/{moduleName}/{pageName}/add" MappedUri="ThePhoneCompany.{moduleName}.Views.{pageName}?key=0"/>
                        
         <!--Used to edit a record-->
         <uriMapper:UriMapping Uri="/{moduleName}/{pageName}/{key}" MappedUri="ThePhoneCompany.{moduleName}.Views.{pageName}?key={key}"/>

         <!--Used to view a page-->
         <uriMapper:UriMapping Uri="/{moduleName}/{pageName}" MappedUri="ThePhoneCompany.{moduleName}.Views.{pageName}"/>
                        
         <!--Used to navigate to a page in the Shell-->
         <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/ThePhoneCompany.Views.{pageName}"/>

        </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>

The RegionManager.RegionName attached property is attached to the Frame control. In your past Prism projects you probably added this attached property to a ContentControl, ItemsControl or TabControl. The assembly provides the region adapter and behavior which enables the Prism Region to be attached to the Frame control.

The included FrameContentLoader is used in the above XAML snippet. This content loader is a replacement for the default Silverlight ContentLoader. Notice the required RegionName property on the FrameContentLoader that matches the RegionName in the Frame RegionManager.RegionName attached property.

The assembly classes route all Region Navigation API requests to the Frame. This was done so that all navigation requests, regardless of origin, are processed uniformly by the Frame. This also ensures that the Frame's UriMapper can handle mapping user friendly Uri's to application MappedUri's.

A glance at the above UriMappings shows that this scheme supports multiple modules that can use the same UriMappings.

By default, the Region Navigation API's use an object's short type name to look it up in the container for creation and when iterating a region's contents to determine if the object can handle the navigation request.

I have elected to use full type names to identify objects in the container for navigation purposes. I also did this in the Prism v4 Region Navigation Pipeline demo application.

The ThePhoneCompany.Infrastructure.FullTypeNameRegionNavigationContentLoader class is used to change the default strategy from short type names to full type names, for determining which objects in the region are candidates to be checked if they are the navigation target. This the same class used in the Prism v4 Region Navigation Pipeline demo application.

User friendly Uri's are not only used in the address bar, but also by the application RequestNavigate method calls.  The below code snippet shows the CategoryViewModel CloseExecute method, calling RequestNavigate from code.  The InventoryHomeView string constant keeps the magic strings out of the code.

 public const String InventoryHomeView = "/Inventory/InventoryView";

void CloseExecute() {
    _keepAlive = false;
    _regionManager.RequestNavigate(RegionNames.MainContentRegion, Constants.InventoryHomeView);
}

Using friendly Uri’s also allows HyperlinkButtons to initiate navigation without additional code other than setting the NavigateUri property in XAML. In the below XAML snippet from the  InventoryView.xaml ListBox DataTemplate.  You can see how simple it is to data bind the ItemID property to set the NavigateUri property.

 <DataTemplate>
    <Grid Loaded="lbDataItemsGridItemTemplate_Loaded">

      ...
      
      <HyperlinkButton 
        Grid.Column="3" Margin="7,0,0,0" Content="edit" 
        NavigateUri="{Binding Path=ItemID, StringFormat=/Inventory/ItemView/\{0\}}" />
    </Grid>
</DataTemplate>

In-fact all but two navigation requests are initiated in XAML and not code. The two RequestNavigate method calls are in the ItemViewModel and CategoryViewModel CloseExecute methods.

Errors that are thrown during navigation requests will be bubbled up to the Frame and the Frame will raise the NavigationFailed event.

In addition to address bar deep linking and the Frame handling navigation errors, navigating against a Frame from XAML is a very good benefit of this implementation.

Developers can modify the Prismv4FrameRegionNavigation assembly's classes to meet the specific needs of their applications.

Lessons Learned

Assembly Loading on Application Start Up

In Silverlight, Prism Modules can be created using one of the Visual Studio Silverlight Applications project templates or a Silverlight Class Library project template.

The reason you use one of the Silverlight Application project templates is for packing. When the Silverlight Application is compiled, it will be packed in its own XAP. Packaging the assembly in its own XAP provides the developer a number of options for when the XAP is actually downloaded and loaded into the Silverlight application domain. If your Silverlight application consists of several XAPs, your initial load time can be decreased, since you will only have to get the XAP that contains the shell download and display the shell.

The reason you use the Silverlight Class Library project template is to provide code separation and promote reuse across solutions. Another reason to use this project template type is to package the assembly with the main Silverlight application; in other words, this assembly will be part of the Silverlight application's XAP.

So, what does this have to do with start up?

The Navigation API's require that the container can create the target object. Meaning, if you are using MEF or an IOC container, the Navigation API will call into the container requesting the target object by name. If the container can't create the object, the container will throw and the navigation request will stop.

If your solution is packaged in several XAPs, and the target of the navigation is in a XAP that has not been downloaded or is in the process of downloading, the container will throw an exception when the target object is requested.

Now let's look at two real-world scenarios that could cause a problem.

Please note, these issues are not caused by using the Navigation API, but are a side-effect of the asynchronous downloading of assemblies and the timing of accessing those assemblies before they are loaded and ready for use.

  • Deep linking – the user opens their browser; enters a deep link Url and presses enter. If your application is broken down into multiple XAPs and the target of the deep link is not in the Silverlight application XAP it is possible that the user will get an error because the target XAP has not yet been downloaded.
  • Main application attempts to navigate to a target that has not yet been loaded – this can happen even if your application is not navigating using a deep link. The main application has loaded and contains a button that navigates to class in module that is still loading. If the user clicks that button, an error will occur because the target is still loading.

To solve these problems you can: 

  • Package your assemblies in the main Silverlight application XAP.

  • Intercept the navigation request, verify the assembly is loaded then proceed with the navigation request. If the assembly is not loaded or is in the process of loading, you can wait until the assembly loads, and then proceed with the request.

    • MEF Catalogs have a Changed event that you can hook to determine the status of assembly loading.

Silverlight Applications and MEF

There is a known issue when referencing the Prism MefExtensions assembly from multiple Silverlight assemblies. Your main Silverlight application should reference the assembly and set Copy Local to True.

All other assemblies that reference the Prism MefExtensions assembly must set Copy Local to False as pictured below.

If you forget to change the Copy Local property value to False, an exception will be thrown during the bootstrapping process. However, the message will indicate that you need to set Copy Local to False.

Non-Linear Navigation Implementation

In addition to Region Navigation integration, this application implements Non-Linear Navigation features similar to the Prism v4 Region Navigation Pipeline demo application.

This application demonstrates using the metadata by surfacing it in four locations as pictured below.

The Application metadata property is used to aggregate the count of the views in the region from the Inventory module. See the top inventory-(3) button text.  Each time the Frame is navigated the Navigated event is raised and this event handler is invoked.

 void ContentFrame_Navigated(Object sender, System.Windows.Navigation.NavigationEventArgs e) {

. . .
    //Posting to the dispatcher because the remove item from region does not get executed before this gets called.
    //Delaying the execution by posting this, enables the Inventory Hyperlink button to have the correct count displayed.
    this.Dispatcher.BeginInvoke((Action)delegate {
        this.hlbInventory.Content = MakeLabelWithCountForApplicationSuite(Constants.Inventory, Constants.Inventory.ToLower());
    });
}

The below method is called from the above code and returns a formatted string that will be used as the Hyperlink’s content.

 String MakeLabelWithCountForApplicationSuite(String applicaitonName, String labelText) {
    Int32 count = 0;

    foreach (var item in this.RegionManager.Regions[RegionNames.MainContentRegion].Views) {
        var fwe = item as FrameworkElement;
        if (fwe != null) {
            var nonLinearNavigationObject = fwe.DataContext as INonLinearNavigationObject;
            if (nonLinearNavigationObject != null && nonLinearNavigationObject.Application == applicaitonName) {
                count += 1;
            }
        }
    }

    if (count == 0)
        return labelText;
    else
        return String.Format("{0}-({1})", labelText, count);
}

The InventoryViewModel creates the text for two inventory function buttons, Item-(1) and Category-(2), see in the above screen shot. This is accomplished by matching the types in the region with the type that each of the button provides selection for.

 String MakeLabelWithCountForApplicationView(Type viewType, String labelText) {
    Int32 count = 0;

    foreach(var item in _regionManager.Regions[RegionNames.MainContentRegion].Views) {
        if(item.GetType() == viewType) {
            count += 1;
        }
    }

    if(count == 0)
        return labelText;
    else
        return String.Format("{0}-({1})", labelText, count);
}

The open items for the selected inventory function are listed in the right side ListBox. This information comes from the region views metadata.  The below code snippet  checks which inventory function is active, then iterates over the views in the region looking for matching types; when found adds the metadata to the list for display in the right side ListBox.

 void UpdateActiveDataItems() {
    try {
        if(ItemIsChecked)
            ActiveDataItems.Source = this.GetActiveDataItems(typeof(ItemView));
        else if(CategoryIsChecked)
            ActiveDataItems.Source = this.GetActiveDataItems(typeof(CategoryView));
    } catch(Exception) { }
}

IList<NonLinearNavigationMetadata> GetActiveDataItems(Type viewType) {
    var list = new List<NonLinearNavigationMetadata>();
    foreach(var item in _regionManager.Regions[RegionNames.MainContentRegion].Views) {
        if(item.GetType() == viewType) {
            var fwe = item as FrameworkElement;
            if(fwe != null) {
                var nonLinearNavigationObject = fwe.DataContext as INonLinearNavigationObject;
                if(nonLinearNavigationObject != null) {
                    list.Add(new NonLinearNavigationMetadata(nonLinearNavigationObject));
                }
            }
        }
    }
    return list;
}

As you can see, metadata is at the heart of implementing Non-Linear Navigation. Surfacing the metadata in creative ways makes it very easy for end users to use your software applications.

Video

The demo application video can be viewed here.

Note: You can view the high resolution version of the .wmv video file by:

  • Clicking on the video link
  • Log into Vimeo

After logging in, you'll be given access to the high resolution video download. There is no cost associated with this.

Download and Video

The link to download the demo application is located at the bottom of this article.

Requirements: you must download Prism 4 or later and run the RegisterPrismBinaries.bat batch file. The Prism v4 Readme covers this file in detail. If you do not want to run this batch file, you'll need to remove and re-add the references to the Prism assemblies.

Comments

Microsoft values your opinion about our products, guidance, documentation and samples.

Thank you for your feedback and have a great day,

Karl Shifflett

Patterns & Practices Prism Team

SilverlightPrismv4IntegratedNavigationUpdated-10-14-2010.zip