Nov 9 2009

Prism V2 - View Discovery Composition Quick Start using MVVM

Category:Bil@l @ 09:01

Before I started learning Prism V2, I was working with Silverlight & Model-View-ViewModel (MVVM) design pattern.

I read the Prism documentation and other articles/blog posts online and noticed that most of them are using other design patterns like PresentationModel and others. Even the Quick Starts that ship with the Prism V2 are all written in a design pattern different from MVVM.

I decided to implement one of the Quick Starts in MVVM, I might be doing the rest of Quick Starts in MVVM if time allows. It will be a good practice on both Prism and MVVM.

The View Discovery Composition quick start in action:



This blog post handles the View Discovery Composition Quick Start in Silverlight using MVVM. Through out the article I will be using the Prism Quick Start Kit by David Hill (Prism Quick Start Kit)


Create VS 2008 Solution

  1. Create a new Blank Solution inside VS 2008.
  2. Add the following projects:
    1. Prism Silverlight Shell project name it UIComposition.
    2. Silverlight Class Library name it UIComposition.Infrastructure
    3. ASP.NET Web Application name it Web
    4. Add a Folder it to the solution name it Modules
      1. To the Modules folder add the following projects:
        1. Prism Silverlight Module project name it UIComposition.Modules.Employee

The following snapshot shows the structure of each of the above created projects


Each of the above projects to be explained in details below.


UIComposition Shell

This project is the Prism Shell project. It contains the Bootstrapper and catalogues to load in the application.



Bootstrapper is a Prism-specific class that connects the Composite Application Library (Prism) to the Silverlight application. It has a major role in the life cycle of the Prism where it does the following:

  1. Creates the UnityContainer
  2. Configures the container with the default system Type mappings:
    1. ILoggerFacade –> TextLogger
    2. IServiceLocator –> UnityServiceLocatorAdapter
    3. IModuleInitializer –> ModuleInitializer
    4. IModuleManager –> ModuleManager
    5. RegionAdapterMappings –> RegionAdapterMappings
    6. IRegionManager –> RegionManager
    7. IEventAggregator –> EventAggregator
    8. IRegionViewRegistry –> RegionViewRegistry
    9. IRegionBehaviorFactory –> RegionBehaviorFactory
  3. Configures region adapters, currently the following region adapters are available: (You can write your own one even)
    1. TabControl –> TabControlRegionAdapter (only for Silverlight)
    2. Selector –> SelectorRegionAdapter
    3. ItemsControl –> ItemsControlRegionAdapter
    4. ContentControl –> ContentControlRegionAdapter
  4. Configures default region behaviors
  5. Registers Prism specific exception types
  6. Create the Shell
  7. Initializes the modules

The above was a summary on the major functionalities of the Bootstrapper. For each Shell application a single Bootstrapper is required. The major methods to override in this project are the following:

   1: /// <summary>
   2: /// Add any specific Type mapping for the application
   3: /// </summary>
   4: protected override void ConfigureContainer()
   5: {
   6:     // Register the DataService to be used by other modules
   7:     this.Container.RegisterType<IDataService, DataService>();
   9:     base.ConfigureContainer();
  10: }

Inside the ConfigureContainer, a call to the base ConfigureContainer method is performed so that all the system-level Types get mapped. In addition, developer n add her own mappings as shown above where the IDataService interface is mapped to the DataService concrete implementation. This is a technique I learned from Telerik Sales Dashboard, where the DataService class plays the role of a Web service proxy class and gets registered by the Container so that any module can make use of the DataService and retrieve data.

   1: /// <summary>
   2: /// Setup all modules to be loaded
   3: /// either at start-up time or on-demand
   4: /// </summary>
   5: /// <returns>An instance of ModuleCatalog populated with modules to be loaded</returns>
   6: protected override IModuleCatalog GetModuleCatalog()
   7: {
   8:     return ModuleCatalog.CreateFromXaml(
   9:                 new Uri("/UIComposition;component/ModuleCatalog.xaml", UriKind.Relative));
  10: }

Another method to override is the GetModuleCatalog() method. This method returns a ModuleCatalog populated with all modules to be loaded on startup time and on demand. The method above reads from an XAML file that lists the modules to be used and loaded in the application. The ModuleCatalog.xaml file contains the following modules to load:

   1: <prism:ModuleCatalog xmlns=""
   2:     xmlns:x=""
   3:     xmlns:sys="clr-namespace:System;assembly=mscorlib"
   4:     xmlns:prism="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">
   6:     <prism:ModuleInfo Ref="UIComposition.Modules.Employee.xap"
   7:                       ModuleName="EmployeeModule"
   8:                       ModuleType="UIComposition.Modules.Employee.ModuleInit, UIComposition.Modules.Employee, Version="
   9:                       InitializationMode="WhenAvailable"/>
  10: </prism:ModuleCatalog>

There is only one module we are loading which is the UIComposition.Modules.Employee.xap module. As you can notice, each XAP can represent a single or more modules. Also notice the InitializationMode which is set to WhenAvailable meaning that, when the application starts this module has to be loaded and initialized. Modules can be loaded on demand, hopefully in another blog post I will show a modular pluggable application loading modules on demand.

Finally, the CreateShell() method is to be explored:

   1: /// <summary>
   2: /// Creates the main shell of the app
   3: /// </summary>
   4: /// <returns>A DependencyObject representing application master page</returns>
   5: protected override DependencyObject CreateShell()
   6: {
   7:     // Use the container to create an instance of the shell.
   8:     ShellView view = Container.Resolve<ShellView>();
  10:     // Display the Shell as the root visual for the application.
  11:     Application.Current.RootVisual = view;
  13:     return view;
  14: }

The main page or master page or template or Shell of the application is created at this moment and set as a value to the Application.Current.RootVisual which is usually set from the App.xaml.cs class at the start up of the application. This means that any class that descends from the DependencyObject can be used as a Shell.

This ends discussing the Bootstrapper class. Now discussion handles the ShellView.xaml user control.



   1: <UserControl x:Class="UIComposition.Views.ShellView"
   2:     ...
   3:     xmlns:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation">
   5:     <Grid>
   6: ...
   7:                 <ItemsControl cal:RegionManager.RegionName="MainRegion"
   8:                               Width="Auto"
   9:                               Height="Auto"
  10:                               HorizontalAlignment="Stretch"
  11:                               VerticalAlignment="Stretch" />
  12:    ...
  13:     </Grid>
  15: </UserControl>

There are two important things to note about here:

   1: xmlns:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"

The markup above is importing the Rergions namespace so that we can make use of the RegionManager to decorate regions inside the ShellView.

   1: <ItemsControl cal:RegionManager.RegionName="MainRegion" ... />

The code is decorating the ItemsControl with a region name of “MainRegion”. A region is simply a placeholder that is used to place content Injected or Discoverd at run time.

The code-behind of the ShellView is as follows:

   1: public partial class ShellView : UserControl
   2: {
   3:     public ShellView(ShellViewModel viewModel)
   4:     {
   5:         InitializeComponent();
   7:         // Set the ViewModel as this View's data context.
   8:         this.DataContext = viewModel;
   9:     }
  10: }

The ShellView is a normal UserControl that has a constructor taking as input parameter an object of type ShellViewModel class. This is the place where we inject an instance of the ViewModel (ShellViewModel) and set it as the DataContext of the View (ShellView).

There is nothing important to mention about the ShellViewModel at this stage, later on we will discuss the ViewModel in more details for other Views.



This project is a Silverlight Class Library that is used usually in a Prism application to hold common classes/events/delegates/exceptions that can be used by all modules and shell in the application.



This quick start includes a single interface the IDataService.

   1: /// <summary>
   2: /// Data service interface.
   3: /// </summary>
   4: public interface IDataService
   5: {
   6:     Employees GetEmployees();
   7: }

The IDataService interface contains a single method, GetEmployees() that returns a collection of EmployeeItem objects, Employees collection.



In the Models folder there are two main classes: EmpoyeeItem and Employees classes representing a single employee class and a collection of employees that are needed both to populate the DataGrid  later on.



A single event has been included in this project, the EmployeeItemSelectedEvent.

   1: /// <summary>
   2: /// Event holding as payload the employee selected
   3: /// </summary>
   4: public class EmployeeItemSelectedEvent : CompositePresentationEvent<EmployeeItem>
   5: {
   7: }

The event inherits from CompositePresentationEvent<T> class. The EmployeeItemSelectedEvent holds as payload an EmployeeItem. Later on you will see the use of this event.


The UIComposition.Infrastructure class library can hold any thing that is common to modules which is better to be placed in a single place.


UIComposition.Modules.Employee Modules

This is a Prism Module that have been created using the Prism Quick Start Kit. This module according to the View Discovery Composition quick start displays a list of employees, selecting an employee displays a TabControl having one Tab item as a general info about the employee and the other Tab item listing projects this specific selected employee belongs to.

The main class in this module is the ModuleInit.cs class. This class inherits from IModule which every module should inherit from.

   1: public class ModuleInit : IModule
   2: {
   3:     private readonly IUnityContainer _container;
   4:     private readonly IRegionManager _regionManager;
   5:     private EmployeesController _controller;
   7:     public ModuleInit(IUnityContainer container, IRegionManager regionManager)
   8:     {
   9:         _container = container;
  10:         _regionManager = regionManager;
  11:     }
  13:     #region IModule Members
  15:     public void Initialize()
  16:     {
  17:         // Display the EmployeeView in the "MainRegion" region.
  18:         _regionManager.RegisterViewWithRegion(RegionNames.MainRegion, () => _container.Resolve<EmployeeView>());
  19:         //_regionManager.Regions[RegionNames.MainRegion].Add(() => _container.Resolve<EmployeeView>());
  21:         // Display the EmployeeListView in the "SelectionRegion" region.
  22:         _regionManager.RegisterViewWithRegion(RegionNames.SelectionRegion, () => _container.Resolve<EmployeesListView>());
  24:         // Register the IEmployeesDetailsView interface
  25:         _container.RegisterType<IEmployeesDetailsView, EmployeesDetailsViewViewModel>(new ContainerControlledLifetimeManager());
  27:         // Create the controller.
  28:         _controller = _container.Resolve<EmployeesController>();
  29:     }
  31:     #endregion
  32: }

This module registers the EmployeeView into the MainRegion that has been previously defined in the ShellView above.

The EmployeeView as you will notice in a while contains two main regions: SelectionRegion and DetailsRegion as shown in the code snippet below. The Initialize() method also registers the EmployeeListView to the SelectionRegion.

   1: <StackPanel>
   2:     <ContentControl x:Name="SelectionPanel"
   3:                     cal:RegionManager.RegionName="SelectionRegion" />
   5:     <ContentControl cal:RegionManager.RegionName="DetailsRegion" />
   6: </StackPanel>

The idea is not simpler, we have the MainRegion that will hold the EmployeeView and inside the EmployeeView there are two regions, one of them is the SelectionRegion and the EmployeeListView is bound to it.

Next the Initialize() method registers with the Container an instance of the EmployeesDetailsViewViewModel class. More on this class will be looked at in the coming sections. Just notice for the time being that the EmployeesDetailsViewViewModel has been registered as a Singleton object using ContainerControlledLifetimeManager().

Finally, the Initialize() method create a new instance of the EmployeesController class using the Container.Resolve() method.


Creating the EmployeesController instance does the following:

   1: public class EmployeesController
   2: {
   3:     private readonly IUnityContainer _container;
   4:     private readonly IRegionManager _regionManager;
   5:     private readonly IEventAggregator _eventAggregator;
   7:     public EmployeesController(IUnityContainer container,
   8:                         IRegionManager regionManager,
   9:                         IEventAggregator eventAggregator)
  10:     {
  11:         _container = container;
  12:         _regionManager = regionManager;
  13:         _eventAggregator = eventAggregator;
  15:         // Subscribe to the EmployeeItemSelectedEvent
  16:         _eventAggregator.GetEvent<EmployeeItemSelectedEvent>().Subscribe(DisplayEmployeeDetails, true);
  17:     }
  18: }

The above is the constructor of the EmployeesController class. The EmployeesController subscribes to the EmployeeItemSelected event and fires the DisplayEmployeeDetails() when the EmployeeItemSelected event is published.


The EmployeesListView is as follows:

   1: <StackPanel>
   2:     <TextBlock Width="Auto"
   3:                Height="Auto"
   4:                Text="Select Employee:"
   5:                Padding="0,0,0,0"
   6:                FontWeight="Bold"
   7:                Margin="0,0,0,0"
   8:                HorizontalAlignment="Left"
   9:                VerticalAlignment="Top" />
  10:     <Controls:DataGrid x:Name="EmployeesList"
  11:                        ItemsSource="{Binding Path=Employees}"
  12:                        SelectedItem="{Binding Path=SelectedEmployee, Mode=TwoWay}"
  13:                        AutomationProperties.AutomationId="EmployeesListGrid"
  14:                        AutoGenerateColumns="False" IsReadOnly="True">
  15:         <Controls:DataGrid.Columns>
  16:             <Controls:DataGridTextColumn Header="First Name"
  17:                                          Binding="{Binding Path=FirstName}" />
  18:             <Controls:DataGridTextColumn Header="Last Name"
  19:                                          Binding="{Binding Path=LastName}" />
  20:         </Controls:DataGrid.Columns>
  21:     </Controls:DataGrid>
  22: </StackPanel>

The View is very simple, displaying a Grid where a user can select an employee record from.

Notice that we have two main bindings:

  1. Employees: This contains all employees to be loaded into the Grid
  2. SelectedEmployee: This contains the employee selected on the Grid

The code-behind of the above view is as follows:

   1: public partial class EmployeesListView : UserControl
   2: {
   3:     public EmployeesListView(EmployeesListViewViewModel viewModel)
   4:     {
   5:         InitializeComponent();
   7:         // Set the ViewModel as this View's data context.
   8:         this.DataContext = viewModel;
   9:     }
  10: }

All what the constructor does is, set the control’s DataContext to the passed in ViewModel. Therefore, the two binding properties on the EmployeesListView are placed on the ViewModel that is set as a DataContext.

   1: public class EmployeesListViewViewModel : INotifyPropertyChanged
   2: {
   3:     private readonly IEventAggregator _eventAggregator;
   5:     public EmployeesListViewViewModel(IDataService dataService, IEventAggregator eventAggregator)
   6:     {
   7:         _eventAggregator = eventAggregator;
   9:         // Use the data service to load the model.
  10:         Employees = dataService.GetEmployees();
  11:     }
  13:     #region View Databinding Properties
  14:     public Employees Employees { get; private set; }
  16:     private EmployeeItem employee;
  17:     public EmployeeItem SelectedEmployee 
  18:     {
  19:         get { return this.employee; }
  20:         set 
  21:         { 
  22:             this.employee = value;
  23:             NotifyPropertyChanged("SelectedEmployee");
  25:             // Fire an event to signal a change 
  26:             // in the employee selected
  27:             if (this.employee != null)
  28:                 _eventAggregator.GetEvent<EmployeeItemSelectedEvent>().Publish(this.SelectedEmployee);
  29:         }
  30:     }
  31:     #endregion
  33:     #region INotifyPropertyChanged Members
  34:     public event PropertyChangedEventHandler PropertyChanged;
  36:     private void NotifyPropertyChanged(string propertyName)
  37:     {
  38:         if (PropertyChanged != null)
  39:         {
  40:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  41:         }
  42:     }
  43:     #endregion
  44: }

Few hints on the above code:

  1. The Employees property is being populated inside the constructor by calling the GetEmployees() method on a data service
  2. In the set part of the SelectedEmployee property, the EmployeeItemSelectedEvent is being published to notify all subscribes that a new employee has been selected.

Before diving into the code that will be fired to show the details of an employee, we are left with two main components to explain:



   1: <StackPanel Margin="0,5,0,5">
   2:     <Controls:TabControl AutomationProperties.AutomationId="DetailsTabControl"
   3:                          Regions:RegionManager.RegionName="TabRegion"
   4:                          Width="Auto"
   5:                          Height="Auto"
   6:                          Margin="0,5,0,0"
   7:                          HorizontalAlignment="Stretch">
   8:         <Regions:TabControlRegionAdapter.ItemContainerStyle>
   9:             <Style TargetType="Controls:TabItem">
  10:                 <Setter Property="HeaderTemplate">
  11:                     <Setter.Value>
  12:                         <DataTemplate>
  13:                             <TextBlock Text="{Binding Path=HeaderInfo}" />
  14:                         </DataTemplate>
  15:                     </Setter.Value>
  16:                 </Setter>
  17:             </Style>
  18:         </Regions:TabControlRegionAdapter.ItemContainerStyle>
  19:         <Controls:TabItem Header="{Binding Path=HeaderInfo}">
  20:             <Grid  DataContext="{Binding SelectedEmployee}"
  21:                    Width="Auto"
  22:                    Height="Auto">
  23:                 <Grid.ColumnDefinitions>
  24:                     <ColumnDefinition Width=".5*" />
  25:                     <ColumnDefinition Width=".5*" />
  26:                 </Grid.ColumnDefinitions>
  27:                 <StackPanel Grid.Column="0"
  28:                             Orientation="Vertical">
  29:                     <TextBlock Text="First Name:"
  30:                                HorizontalAlignment="Left"
  31:                                Padding="0,5,5,5"
  32:                                FontWeight="Bold" />
  33:                     <TextBox AutomationProperties.AutomationId="FirstNameTextBox"
  34:                              Text="{Binding Path=FirstName}"
  35:                              Height="Auto"
  36:                              Width="Auto"
  37:                              HorizontalAlignment="Stretch"
  38:                              Margin="0,5,100,5"></TextBox>
  39:                     <TextBlock Text="Description:"
  40:                                HorizontalAlignment="Left"
  41:                                Padding="0,5,5,5"
  42:                                FontWeight="Bold" />
  43:                     <TextBox AutomationProperties.AutomationId="DescriptionTextBox"
  44:                              Text="{Binding Path=Description}"
  45:                              Height="Auto"
  46:                              Width="Auto"
  47:                              HorizontalAlignment="Stretch"
  48:                              Margin="0,5,100,5"></TextBox>
  49:                 </StackPanel>
  50:                 <StackPanel Grid.Column="1">
  51:                     <TextBlock Text="Last Name:"
  52:                                HorizontalAlignment="Left"
  53:                                Padding="0,5,5,5"
  54:                                FontWeight="Bold" />
  55:                     <TextBox AutomationProperties.AutomationId="LastNameTextBox"
  56:                              Text="{Binding Path=LastName}"
  57:                              Height="Auto"
  58:                              Width="Auto"
  59:                              HorizontalAlignment="Stretch"
  60:                              Margin="0,5,100,5"></TextBox>
  61:                 </StackPanel>
  62:             </Grid>
  63:         </Controls:TabItem>
  64:     </Controls:TabControl>
  65: </StackPanel>

The EmployeesDetailsView displays a TabControl with a single TabItem to display details of an Employee.

The code-behind binds the EmployeesDetailsView’s DataContext to the EmployeesDetailsViewViewModel object, which has the following details:

   1: public EmployeesDetailsViewViewModel(IDataService dataService, IEventAggregator eventAggregator)
   2: {
   3:     _eventAggregator = eventAggregator;
   4: }
   6: #region View Databinding Properties
   7: private EmployeeItem employee;
   8: public EmployeeItem SelectedEmployee
   9: {
  10:     get { return this.employee; }
  11:     set
  12:     {
  13:         this.employee = value;
  14:         NotifyPropertyChanged("SelectedEmployee");
  15:     }
  16: }
  18: /// <summary>
  19: /// Header title of the Tab
  20: /// </summary>
  21: public string HeaderInfo
  22: {
  23:     get
  24:     {
  25:         return "General Info";
  26:     }
  27: }
  28: #endregion
  30: #region IEmployeesDetailsView Members
  32: /// <summary>
  33: /// Injects the employee selected
  34: /// </summary>
  35: /// <param name="employee"></param>
  36: public void SetSelectedEmployee(EmployeeItem employee)
  37: {
  38:     if (employee != null)
  39:         this.SelectedEmployee = employee;
  40: }
  42: #endregion
  43:     }

The TabItem is bound to the SelectedEmployee property on the ViewModel class.

In addition, this class implements the IEmployeesDetailsView interface which contains a single method, SetSelectedEmployee() method.


It was mentioned above that the EmployeesController subscribed to the EmployeesItemSelectedEvent hence, when an employee is selected on the Grid, the DisplayEmployeeDetails() method located in the EmployeesController fires.

   1: private void DisplayEmployeeDetails(EmployeeItem employee)
   2: {
   3:     IRegion detailsRegion = _regionManager.Regions[RegionNames.DetailsRegion];
   4:     object existingView = detailsRegion.GetView(employee.Id.ToString(CultureInfo.InvariantCulture));
   6:     // the view does not exist yet. Create it and push it into the region
   7:     IEmployeesDetailsView detailsViewModel = this._container.Resolve<IEmployeesDetailsView>();
   8:     detailsViewModel.SetSelectedEmployee(employee);
  10:     // See if the view already exists in the region. 
  11:     if (existingView == null)
  12:     {
  13:         // Create the View
  14:         existingView = _container.Resolve<EmployeesDetailsView>();
  16:         // Add the view into the details region
  17:         detailsRegion.Add(existingView, employee.Id.ToString(CultureInfo.InvariantCulture), true);
  19:         // Activate the View
  20:         detailsRegion.Activate(existingView);
  21:     }
  22:     else
  24:         // The view
  23:     {
 already exists. Just show it. 
  25:         detailsRegion.Activate(existingView);
  26:     }    
  27: }

  1. Code starts by retrieving a reference to the DetailsRegion
  2. Get an employee’s view that has been already loaded into that region (if any)
  3. Gets a singleton instance of the EmployeesDetailsViewViewModel object return in the form of an instance of IEmployeesDetailsView interface
  4. Call the SetSelectedEmployee() method to fill in the selected employee on the ViewModel
  5. If the currently selected employee has no previously loaded EmployeeDetailsView then the following happens:
    1. Create a new instance of the EmployeeDetailsView. This view takes as input an interface of type IEmployeesDetailsView and hence in this case, the view will be injected the singleton object that is populated every time with the selected employee. I had to do this trick here of the singleton object since a requirement is to inject the EmployeesDetailsView, else if I could show the view from the beginning and empty, I would have only subscribed to the EmployeesItemSelectedEvent to handle setting the selected employee.
    2. Add the view (populated with the employee selected) into the DetailsRegion
    3. Activate the view
  6. Else, the code activates the existing instance of the EmployeesDetailsView


That was a detailed overview on how I converted the View Discovery Composition Quick Start into MVVM.

Hope you enjoyed and benefited from this post.


Download Link




Comments are closed