• How To: Integrate a Prism v2 application with the Silverlight 3 Navigation Framework

    Published by on April 12th, 2009 8:15 pm under Composite WPF & SL, Navigation Framework, Prism v2, Silverlight, Silverlight 3

    11 Comments

    The Silverlight 3 Beta release introduces a lot of new features and enhancements into the framework. One of them is Navigation, which allows us to easily implement navigation between UserControls / Pages, interact with the Browser History journal and define Uri mapping to map complex Uris with more friendly ones. To learn more about the Navigation Framework you can check the Navigation Framework video by Tim Heuer.

    The purpose of this post is to provide an overview of the things that should be taken into account to use the Silverlight 3 Navigation Framework in a Prism v2 application, as well as the limitations / issues that you could come up with during the integration. I used the Remote Modularity QuickStart (shipped with the Composite Application Guidance for WPF and Silverlight – February 2009) as the base application and modified it to provide navigation among modules. As you can see in the image bellow, the original QS (in the left) shows all the modules at once and the updated one (in the right) provides navigation links to see each module page separately.

    Remote Modularity QS using Navigation Framework

    System Requirements

    To be able to open the source code that accompanies this post and follow its guidance, you will need the following:

    Note: Once you install the Silverlight 3 Beta Tools for Visual Studio, your development environment will be a Silverlight 3 Beta environment.  Visual Studio 2008 SP1 does not support multi-targeting for Silverlight applications so you will be unable to develop Silverlight 2 applications once these tools are installed.

    Steps

    The following are the general steps that I performed to add navigation functionality to the Remote Modularity QuickStart, but they could also be reused in other Prism v2 applications:

    1. Add a reference to the System.Windows.Controls.Navigation assembly in the Shell project. This assembly contains the Frame and Page controls needed to navigate and the classes that allow the mapping of the Uris.
    2. Add a Frame control to the Shell of the Silverlight application. The Frame control is the container for pages. It allows you to navigate to a Uri and exposes a bunch of useful events related to navigation.
      <UserControl x:Class="RemoteModuleLoading.Shell"
         
          xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation">
          …
          <StackPanel>
              <ItemsControl cal:RegionManager.RegionName="LinkRegion">
                  <ItemsControl.ItemsPanel>
                      <ItemsPanelTemplate>
                          <StackPanel Orientation="Horizontal"/>
                      </ItemsPanelTemplate>
                  </ItemsControl.ItemsPanel>
                  <ItemsControl.Items>
                      <HyperlinkButton Content="Home" Tag="Home"
                           cmd:Click.Command="{Binding NavigateCommand}"
                           cmd:Click.CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" />
                  </ItemsControl.Items>
              </ItemsControl>
              <navigation:Frame x:Name="MainFrame" Source="Home" />
          </StackPanel>
          …
      </UserControl>

    3. Add a UriMapper to the Application Resources in the App.xaml file.
      <Application
                   
                   xmlns:navcore="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation">
          <Application.Resources>
              <navcore:UriMapper x:Key="uriMapper">
                  <navcore:UriMapping Uri="Home" MappedUri="/Views/Home.xaml" />
              </navcore:UriMapper>
          </Application.Resources>
      </Application>
    4. Add UriMappings inside the Initialize method of the Module class of each module. The UriMapping class allows us to display a more user friendly Url in the browser. Defining this mapping inside each module avoids having them hard-coded inside the Shell project.
      public void Initialize()
      {
          AddModuleUriMappings();
      
          this.regionManager.Regions["LinkRegion"].Add(new ModuleZLink());
      }
      
      private static void AddModuleUriMappings()
      {
          UriMapper mapper = (UriMapper)Application.Current.Resources["uriMapper"];
          mapper.UriMappings.Add(new UriMapping()
                              {
                                  Uri = new Uri("ModuleZ", UriKind.Relative),
                                  MappedUri = new Uri("/ModuleZ;component/DefaultViewZ.xaml",
                                                      UriKind.Relative)
                              });
      }

    5. For each module, add an UserControl containing an HyperlinkButton which points to the Uri previously mapped. This UserControl is added to the LinkRegion of the Shell when the module is initialized.
      <UserControl x:Class="ModuleZ.ModuleZLink"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation">
      
          <HyperlinkButton Content="Module Z" Tag="ModuleZ"
                           cal:Click.Command="{Binding NavigateCommand}"
                           cal:Click.CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" />
      
      </UserControl>

      Note: The value of the Tag property of the HyperlinkButton control is the Uri that will be used for navigate. Also notice that I’ve bound the Click event of the control to a NavigateCommand

      For more information related to commands, see Commands article in Prism v2 documentation.

    6. Create a global CompositeCommand to handle the navigation within the frame. In this way, all the links previously added will use the frame control defined in the Shell to perform the navigation when they are clicked.
      public Shell(IUnityContainer container, CommandProxy command)
      {
          InitializeComponent();
      
          this.container = container;
          command.NavigateCommand.RegisterCommand(new DelegateCommand<string>(this.OnNavigate));
      
          this.MainFrame.Navigated += new NavigatedEventHandler(this.MainFrame_Navigated);
          this.DataContext = command;
      }
      
      private void OnNavigate(string url)
      {
          this.MainFrame.Navigate(new Uri(url, UriKind.Relative));
      }

    7. Add a handler to the Navigated event of the frame control to perform dependency injection in the pages. This requires all the pages to use property injection instead of constructor injection.
      private readonly IUnityContainer container;
      
      private void MainFrame_Navigated(object sender, NavigationEventArgs e)
      {
          if (e.Content != null)
          {
              this.container.BuildUp(e.Content.GetType(), e.Content);
          }
      }

    You can download the final solution from the Download section bellow.

    Limitations / Issues when modules are loaded dynamically

    When the modules are statically loaded, all the above steps work without problems. All the assemblies are declared in the AppManifest.xaml file of the Silverlight application and deployed / loaded before the application starts. Therefore, all the types and resources defined in the modules are available for navigation.

    However, if the modules are dynamically loaded like in the Remote Modularity QuickStart, they are not included in the AppManifest.xaml file because their assemblies are deployed on demand by the CAL. This causes some problems when trying to crate an instance of a page class during the navigation. The exception you get says that the type specified in the x:Class attribute of the XAML could not be found in any loaded assembly. I’ve tried the following things to bypass this limitation without success:

    1. Modified the XapModuleTypeLoader class of the CAL to add all the AssemblyParts of the modules to the Deployment.Current.Parts collection. This caused no effect.
    2. Tried to add the assembly name to the x:Class attribute of each page. However, I was no able to specify the assembly in the x:Class attribute using the syntax x:Class=”namespace.classname;assembly=assemblyname” like the official Silverlight documentation explains. I got a compilation error saying that the value of the x:Class attribute is not a valid fully qualified class name.

    Finally I opted for a workaround and found the following two possible ones:

    1. Remove code behind classes in module’s pages: If your pages do not contain any x:Class attribute the navigation will work smoothly. You can take advantage of all the features available in Silverlight 3 (like Bindings, Commands, StaticResources, etc) to avoid having a code behind class.
    2. Use a Placeholder page in the shell module to host module pages: If your pages are complex and you cannot avoid having code behind classes, you can create a page in the Shell module to which all modules can navigate. This placeholder page will receive as a QueryString parameter the type of the view that should contain, resolve it in the application Container and add it to its Content property. This logic should be placed in the Loaded event of the page.
      // Loaded event of the placeholder page
      private void ModulePlaceHolderPage_Loaded(object sender, RoutedEventArgs e)
      {
          string type = this.NavigationContext.QueryString["viewType"];
          UIElement moduleView = null;
      
          if ((type != null) && (Type.GetType(type) != null))
          {
              Type viewType = Type.GetType(type, false);
              moduleView = this.Container.Resolve(viewType) as UIElement;
          }
      
          // Show in the content place holder the module view
          this.Content = (moduleView != null) ? moduleView :
                                                new TextBlock()
                                                {
                                                    Foreground = new SolidColorBrush(Colors.Red),
                                                    TextWrapping = TextWrapping.Wrap,
                                                    Text = String.Format(CultureInfo.CurrentUICulture,
                                                                         "View {0} was not found.", type)
                                                };
          this.Title = this.NavigationContext.QueryString["title"];
      }

      Each module should also define its own Uri mappings to point the placeholder page adding the right QueryString parameters.

      private static Uri CreateModuleUri()
      {
          string queryString = String.Format("viewType={0}&title={1}",
                                             typeof(DefaultViewY).AssemblyQualifiedName,   // View type
                                             "Module Y - RemoteModuleLoading Quickstart"); // Title
      
          // I added the base Uri of the placeholder page inside the Shell project.
          string modulePlaceHolderUri = Application.Current.Resources["placeHolderUri"] as string;
      
          return new Uri(String.Format("{0}?{1}", modulePlaceHolderUri, queryString), UriKind.Relative);
      }

    Download

    Download the C# source code from here. The file contains the following:

    • CAL folder: contains the source code of the Composite Application Library shipped with the Composite Application Guidance for WPF and Silverlight – February 2009.
    • LIB folder: contains assemblies required by the Composite Application Library.
    • RemoteModularityQuickstart folder: contains the updated Remote Modularity Quickstart to use navigation functionality with the Silverlight 3 Navigation Framework using workarounds described previously.
    • StaticModularityQuickstart folder: contains the updated Remote Modularity Quickstart to use navigation functionality with the Silverlight 3 Navigation Framework and modified to statically load modules.
    Note: This code is provided “AS IS” with no warranties, and confers no rights.

    Feedback is always appreciated!

    Mariano

    kick it on DotNetKicks.com

    • Ben

      This sort of works …( quite nice btw) but it has issues when any views contain regions.

      this.container.BuildUp(e.Content.GetType(), e.Content);

      Im not sure but this will create multiple views with the same region which will go pop…

    • http://weblogs.asp.net/alexeyzakharov/ Alexey Zakharov

      Nice. But what should we do if we have more than two modules per page?

    • http://www.miguelmadero.com Miguel Madero

      This is really good. I’ll try it later. I’m thinking of a couple of issues, hopefully there’ll be a quick workaround, but I wanted to mention this since probably other ppl already run into this.

      I think it would be better to rely on regions instead of Frames since that will allow us to compose the UI instead of just navigating to a specific view. By the other hand we want to take advantage of the history and URI navigation.

      This could be solved loading a page that will set itself as a specific region based on some of the querystring parameters, then Prism would take care of showing the registered views in there.

      That way, we could use the Command to navigate, the user could use the back and forward buttons or directly type a specific URL, but that would cause different views to be loaded in the corresponding region and properly instantiated using Constructor Injection.

    • Peter

      If you try to run the sample code using SL3 Final, you need to add UriMapper=”{StaticResource uriMapper}” to the navigation:Frame element in Shell.xaml.

      See also http://silverlight.net/forums/p/108580/247680.aspx

    • leandro

      Excellent article Mariano, congrats!

      Do you know how we can get deep linking enable in it?

      Keep up with the good work

    • http://www.talentsfromindia.com/silverlight-developer-programmer.html Ryan Watson

      A resourceful article, the navigation and prism integration are great concept.

About

Mariano Converti Profile Picture
Mariano Converti

Map