In Part 1 of this series, I gave a quick summary of what Prism is, and when and why you would want to use it. The remaining parts of this series will address the "how" of using it. Since this series is largely an overview, I won't cover everything you can possibly do under Prism. And since Prism is designed so that the programmer can pick and choose what functionality they need, not everything I cover here will be relevant to all applications. However, the goal is that this series will be a good jumping-off point for your own Prism application development. Part 2 will cover two components you'll find in every Prism application: the Shell and the Bootstrapper.
The Shell
Composite applications are architected such that individual parts of the application can be developed, tested, and modified independent from other parts. If you think of the application as a painting, the shell is the canvas, a container that integrates everything together in a coherent whole. It is essentially the “application” part of the composite application. The XAML file in the shell consists of one or more regions, which are named logical placeholders into which views are injected. Each region is attached to a common container control such as a ContentControl, ItemsControl, ListBox, or TabControl. The shell we’ll be using for this sample application will consist of four regions each attached to a ContentControl.
Start by opening Visual Studio and selecting the New Project dialog from the file menu. Create a WPF Application.
Click OK to create the project and solution. Though not strictly necessary, I like to rename the automatically generated MainWindow class to Shell due to convention.
Next, we need to create an Infrastructure project as a class library. If the Shell is our canvas, the Infrastructure project is the palette. It holds all classes, resources, and constants that will be shared between the Shell and the various modules, as well as all the interfaces for the shared services.
Delete any classes created and add a reference to this project to your Shell project. Next, add a folder called "Constants" to the Infrastructure project. To this new folder add a new public static class called "RegionNames" and add the constants shown below. If this were a real application rather than an instructive one, the naming conventions for the regions would probably reflect the functional purpose of the injected modules for each region.
namespace Infrastructure.Constants
{
public static class RegionNames
{
public const string MENU_REGION = "MenuRegion";
public const string SHELL_BOTTOM_REGION = "ShellBottomRegion";
public const string SHELL_MIDDLE_REGION = "ShellMiddleRegion";
public const string SHELL_TOP_REGION = "ShellTopRegion";
}
}
Now we add the Prism libraries to our Shell. Right click on the PrismSample project and select "Manage NuGet Packages..." Click on the "Browse" tab and type "prism.unity" in the search box. Select the "Prism.Unity" package and install it.
Finally, we implement the Shell XAML. Our layout will be pretty straightforward with a logo in the upper left corner, the menu region below that, and the other three regions taking up the right side.
<Window x:Class="PrismSample.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://www.codeplex.com/prism"
xmlns:constants="clr-namespace:Infrastructure.Constants;assembly=Infrastructure"
mc:Ignorable="d"
Title="Prism Sample Application" Height="900" Width="1600" Icon="Resources/DMC Wheel Transparent Background.ico">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Margin="50, 25" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Image Grid.Row="0" Margin="10,20" Source="Resources/Small_jpg.jpg" />
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="{x:Static constants:RegionNames.MENU_REGION}" />
</Grid>
<Grid Grid.Column="1" Background="#FFE7E7E7">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl Margin="30, 30, 30, 0" Grid.Row="0" prism:RegionManager.RegionName="{x:Static constants:RegionNames.SHELL_TOP_REGION}" />
<ContentControl Margin="30, 15" Grid.Row="1" prism:RegionManager.RegionName="{x:Static constants:RegionNames.SHELL_MIDDLE_REGION}" />
<ContentControl Margin="30, 0, 30, 30" Grid.Row="2" prism:RegionManager.RegionName="{x:Static constants:RegionNames.SHELL_BOTTOM_REGION}" />
</Grid>
</Grid>
</Window>
If we were to try to execute our program now, we'd throw an exception indicating a missing service location provider. Prism is designed that when it needs to access a component of the application (in this case the RegionManager) it doesn't initialize the component directly but instead delegates this task to a ServiceLocator. So how do we let Prism know what ServiceLocator to use? That's where the next component of our application comes in.
The Bootstrapper
If the Shell is the canvas, the Infrastructure project is the palette, the modules are the images to paint, and Prism is the paintbrush, that would make the Bootstrapper the medium of this tortured artistic metaphor. The Bootstrapper connects the Shell to the Prism services and the Dependency Injection container. Unique for each application, it typically inherits from the abstract classes UnityBootstrapper or MefBootstrapper, depending upon DI container used. (If using a different DI container from either Unity or MEF, there is a base abstract bootstrapper class from which all bootstrap classes inherit. The developer would create their own class which inherits that base class and customize it for that particular DI container.) Since we are using Unity for our DI, we inherit from UnityBootstrapper for our class, thereby letting Prism do most of the difficult work for us.
The first thing we need to do once we've created our Bootstrapper is override the CreateShell() method. CreateShell() lets you specify the top-level window for a Prism application. You can either create the shell object yourself, or let the ServiceLocator do it. We’ll opt for the latter.
protected override DependencyObject CreateShell()
{
return ServiceLocator.Current.GetInstance<Shell>();
}
Next, we override InitializeShell(). InitializeShell() is where you run any initialization steps you need to ensure the Shell is ready to be displayed. After that, you set the Shell as the application’s main window and show it.
protected override void InitializeShell()
{
Application.Current.MainWindow = (Shell)this.Shell;
Application.Current.MainWindow.Show();
}
This is all we have to do with the Bootstrapper for now. We will be returning to it frequently over the course of this series. Here's the complete class.
using System.Windows;
using Microsoft.Practices.ServiceLocation;
using Prism.Unity;
namespace PrismSample
{
/// <summary>
/// Initializes Prism/Unity components and services
/// </summary>
public class Bootstrapper : UnityBootstrapper
{
/// <summary>
/// Make main window class as Prism shell
/// </summary>
protected override DependencyObject CreateShell()
{
return ServiceLocator.Current.GetInstance<Shell>();
}
/// <summary>
/// Set the application's window to the current shell and display it
/// </summary>
protected override void InitializeShell()
{
Application.Current.MainWindow = (Shell)this.Shell;
Application.Current.MainWindow.Show();
}
}
}
On a side note, if we were using MEF for our dependency injection container, we'd add one more override to the Bootstrapper: ConfigureAggregateCatalog(). We'd then add a new AssemblyCatalog consisting of our Bootstrapper's assembly to MEF's AggregateCatalog inside that method.
All that’s left is to let WPF know that we want the Bootstrapper to be our startup class. First we remove the StartupUri attribute from App.xaml.
<Application x:Class="PrismSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
</Application.Resources>
</Application>
Then in App.xaml.cs we override OnStartup() as follows:
using System.Windows;
namespace PrismSample
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new Bootstrapper().Run();
}
}
}
And now our application is ready to run. Granted, it doesn't do much at this point, mainly because we haven't loaded any modules. We'll start adding those in Part 3: Modules.
The solution files and source code for this part can be downloaded by clicking here.
A Properly Pleasing Prism Primer - The Parts:
Part 1: An Introduction
Part 2: The Shell and Bootstrapper
Part 3: Modules
Learn more about DMC's .NET application development services.