Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Prism, WPF, and Unity based modular application step by step

0.00/5 (No votes)
2 Dec 2013 1  
Starting modular application development using Prism, WPF, and Unity container.

Introduction

This article explains some simple steps to create a modular application using WPF and Prism.

Background

Third party DexExpress WPF control is used in this sample UI application. However, even without using any third party UI control, the steps below can be used to develop a modular UI application with WPF and Prism and by using regular WPF controls. The purpose of this example is to show the basic steps to start a modular application using Prism/Unity and not the usage of third party controls.

This article was posted back in February 2013 in my personal blog:

Using the code

Here we will explore a few basic steps for how we can start a modular application from scratch using Prism, WPF, and Unity container. There are often needs for modular applications, especially for large scale application development projects where each module performing its own specific tasks can be developed independently and later seamlessly incorporated within the modular application shell through a container. This also provides the capability to switch modules of similar functionality seamless for the application to bring up the desired one as configured. This article will not go into the details of demonstrating the various aspects and benefits of a modular application. Rather, this will just show a few basic steps of how we can take advantage of Prism and define regions in our UI where each module can provide service to independent regions within an application.

Step 1

In Visual Studio 2010, start a new WPF project. Remove the MainWindow.xaml file that was automatically created. Remove StartupUri="MainWindow.xaml" from the App.xaml file. 

Add the following references:   

Microsoft.Practices.Prism
Microsoft.Practices.Prism.Unity
Microsoft.Practices.Prism.UnityExtensions
Microsoft.Practices.Prism.ServiceLocation
Microsoft.Practices.Prism.Interactivity 

Create a class called Bootstrapper.cs and derive this from the UnityBootstrapper class. Create the main shell window by adding a new WPF window called PrismAppShell.xaml. Implement the abstract method CreateShell in the BootStrapper class.

Override these two methods in the Bootstrapper class:

InitializeModules
ConfigureModuleCatalog 

Inside App.xaml.cs within the OnStartup method, instantiate the BootStrapper object and call its Run().

App.xaml
<Application x:Class="Prism.ModuleExmaple.App"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
> 
<Application.Resources> 
</Application.Resources>
</Application>
App.xaml.cs
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        BootStrapper bootstrapper = new BootStrapper();
        bootstrapper.Run(); 
    } 
}

Implement a bootstrapper class which inherits from the UnityBootStrapper class and override the following three protected methods:

Bootstrapper.cs
public class BootStrapper : UnityBootstrapper
{
    protected override System.Windows.DependencyObject CreateShell()
    {
        return this.Container.Resolve<PrismAppShell>();
    }
    protected override void InitializeModules()
    {
        base.InitializeModules();
        App.Current.MainWindow = (PrismAppShell)this.Shell;
        App.Current.MainWindow.Show();
    }
    protected override void ConfigureModuleCatalog()
    {
        base.ConfigureModuleCatalog();
        this.ModuleCatalog.AddModule(null); // placeholder
    }
}

Create a shell window as show in the XAML below.

PrismAppShell.xaml
<Window x:Class="Prism.ModuleExmaple.PrismAppShell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="PrismAppShell" Height="300" Width="300">
    <Grid>
    </Grid>
</Window>

After this much, if you run the application from Visual Studio, an empty shell window will come up.

Step 2

Now let us divide the UI into regions and mark them with region names by updating the main shell PrismAppShell.xaml file as follows:

<Window x:Class="Prism.ModuleExmaple.PrismAppShell"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:prism="http://www.codeplex.com/prism"
      Title="PrismAppShell" Height="900" Width="1200">
    <Grid x:Name="LayoutRoot">
        <DockPanel LastChildFill="True" HorizontalAlignment="Stretch" 
                 Name="dockPanel1" VerticalAlignment="Stretch">
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" 
                    Background="#FFCCD4F8" Height="100">
                <ContentControl prism:RegionManager.RegionName="RibbonRegion"></ContentControl>
            </StackPanel>
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Background="#FFD9E1EF" Height="100">
                <ContentControl prism:RegionManager.RegionName="StatusRegion"></ContentControl>
            </StackPanel>
            <ScrollViewer>
                <StackPanel Orientation="Vertical" DockPanel.Dock="Left" Background="#FF50576F" Width="200">
                    <ContentControl prism:RegionManager.RegionName="TreeRegion"></ContentControl>
                </StackPanel>
            </ScrollViewer>
            <StackPanel Orientation="Vertical" DockPanel.Dock="Right" Background="#FF677BA7" Width="100">
                <ContentControl prism:RegionManager.RegionName="AlertRegion"></ContentControl>
            </StackPanel>
            <ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFC0DBF2">
                    <ContentControl prism:RegionManager.RegionName="BlotterRegion" 
                        HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ContentControl>
                </StackPanel>
            </ScrollViewer>
        </DockPanel>
    </Grid>
</Window>

If you run this application at this point, it is will display the application like below. Notice the regions are now defined in the UI (so that when we create a few modules, different modules can hook up to different UI regions here for corresponding service).

Step 3

Let us place some actual UI controls to replace the place holder stack panels (which we named for different UI regions).

In the shell XAML, we will use a ribbon control and define the RibbonRegion there. A TreeView control on the left for TreeRegion, a TabControl in the middle for BlotterRegion. Bottom and right (StatusRegion and AlertRegion). We will keep the region defined still within the content control of each of these new UI controls we will add.

<Window x:Class="PrismApp.Shell.PrismAppShell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="http://www.codeplex.com/prism"
    Title="PrismAppShell" Height="900" Width="1200"
    xmlns:my="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
    >
    <Window.Resources>
        <DataTemplate x:Key="crap">
            <StackPanel Width="100" Height="18"></StackPanel>
        </DataTemplate>
        <Style x:Key="TabItemStyleKey" TargetType="{x:Type TabItem}">
            <Setter Property="Header" Value="{Binding Path=DataContext.ViewTile}"></Setter>
            <Setter Property="HeaderTemplate" Value="{Binding Source=    {StaticResource crap}}"></Setter>
        </Style>
        <Style x:Key="TreeItemStyleKey" TargetType="{x:Type TreeViewItem}">
            <Setter Property="Height" Value="Auto"></Setter>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top" />
            <Setter Property="BorderThickness" Value="0" />
        </Style>
    </Window.Resources>
    <Grid x:Name="LayoutRoot">
        <DockPanel LastChildFill="True" HorizontalAlignment="Stretch" 
                Name="dockPanel1" VerticalAlignment="Stretch">
            <my:Ribbon prism:RegionManager.RegionName="RibbonRegion" 
                 DockPanel.Dock="Top" HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch" Name="ribbon1" Height="150"/>
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" 
                     Background="#FFD9E1EF" Height="100">
                <ContentControl prism:RegionManager.RegionName="StatusRegion"></ContentControl>
            </StackPanel>
            <ScrollViewer>
                <StackPanel Orientation="Vertical" DockPanel.Dock="Left" 
                          MinWidth="150" MaxWidth="200">
                    <TreeView ItemContainerStyle="{StaticResource TreeItemStyleKey}"
                         prism:RegionManager.RegionName="TreeRegion"
                         Name="treeView1" HorizontalAlignment="Stretch"
                         VerticalAlignment="Stretch" BorderThickness="0"
                    />
                </StackPanel>
            </ScrollViewer>
            <StackPanel Orientation="Vertical" DockPanel.Dock="Right" 
                      Background="#FF677BA7" Width="100">
                <ContentControl prism:RegionManager.RegionName="AlertRegion"></ContentControl>
            </StackPanel>
            <ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <StackPanel HorizontalAlignment="Stretch" 
                          VerticalAlignment="Stretch" Background="#FFC0DBF2">
                    <TabControl ItemContainerStyle="{StaticResource TabItemStyleKey}"
                        prism:RegionManager.RegionName="BlotterRegion" Name="tabControl1"
                        HorizontalAlignment="Stretch" 
                        VerticalAlignment="Stretch" Width="Auto">
                    </TabControl>
                </StackPanel>
            </ScrollViewer>
        </DockPanel>
    </Grid>
</Window>

Step 4

Now let us create a few modules by implementing the IModule interface and register with regions. For example, we created a module called cash blotter module and register it in the following way:

public class CashBlotterModule : IModule
{
    private readonly IRegionViewRegistry regionViewRegistry = null;
    public CashBlotterModule(IRegionViewRegistry regionViewRegistry)
    {
        this.regionViewRegistry = regionViewRegistry;
    }
    public void Initialize()
    {
        this.regionViewRegistry.RegisterViewWithRegion("BlotterRegion", typeof(CashBlotterView));
        this.regionViewRegistry.RegisterViewWithRegion("TreeRegion", typeof (CashItemsTreeView));
    }
}

We will just add new XAML files and the corresponding view model files for modules we are adding. Here is a snapshot of Solution Explorer with the skeleton module files that we are adding. We added a cash blotter module, a deriv blotter module, cash items tree module, deriv items tree module, common module, ticketing module, etc. Names of the modules may not seem realistic, however let us just call them so for the sake of this example.

Step 5

We will now update the BootStrapper class to load modules as shown below. (Below, modules are just added in the module catalog for brevity of this example. However you can further load modules using the container and decouple them from the bootstrapper altogether for loading modules independently and as needed.)  

public class BootStrapper : UnityBootstrapper
{
    protected override System.Windows.DependencyObject CreateShell()
    {
        return this.Container.Resolve<PrismAppShell>();
    }
    protected override void InitializeModules()
    {
        base.InitializeModules(); 
        App.Current.MainWindow = (PrismAppShell)this.Shell;
        App.Current.MainWindow.Show();
    }
    protected override void ConfigureModuleCatalog()
    {
        base.ConfigureModuleCatalog();
        ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
        // Seems like as soon as you add a new module, internally seletor module
        // adopter (since blotter region is on tab control), automatically adds a tab item)
        moduleCatalog.AddModule(typeof(PrismApp.Module.Cash.Blotter.CashBlotterModule));
        moduleCatalog.AddModule(typeof(PrismApp.Module.Deriv.Blotter.DerivBlotterModule));
        //moduleCatalog.AddModule("CashBlotterModule", 
        //   "PrismApp.Module.Cash.Blotter.CashBlotterModule");
    }
}

In above code, inside ConfigureModuleCatalog the module loading will be upgraded in the following section.

When we run now, the skeleton application comes up like this:

Step 6

  • Replaced the WPF DataGrid with DevExpress grid control, WPF Toolbox ribbon with DevExpress ribbon control. (Third-party DevExpress controls are not needed to accomplish this, you can use any UI control, including regular .NET provided WPF controls).
  • View Injection - did view injection from cash blotter module and ticket module and as well from derivatives blotter and ticket module. 
  • Injected views in desired Prism regions on the shell, i.e., on BlotterRegion, RibbonRegion, TreeRegions.  
  • Model - connected to database for fake trade data.  (Code sample for data is not shown here, you can set up your own data provider and retrieve data and hook up to blotter module to display.)
  • ViewModel - created a number of view model classes in order for the views to bind to fake data. 

After all the above steps in step 6, our skeleton app looks like the following. In the UI below, when "Cash Trading" tab is chosen, the cash blotter module will provide the necessary data and service, and same for the Derivative Trading tab will be serviced by the deriv blotter module. Same goes for tickets. Also, alert region on the UI (now shown empty) will be filled by the alert module, same way status region in the UI (now showing empty) will be filled with status messages from the status module. All these modules can be developed independent of the main application, can be independently built, tested, and configured.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here