Introduction

This article was written as a companion piece to Creating View-Switching Applications with Prism 4, also published on CodeProject. However, it can be used as a guide to setting up almost any Prism 4 application. The article provides a step-by-step explanation of how to set up a Prism application.

The checklist in this article assumes that the developer will use Unity 2.0 as the Dependency Injection (DI) container for the application, and that logging will be performed using the log4net framework. But the checklist does not depend heavily on these components, and it should be easily adaptable for use with other DI containers and logging frameworks.

The checklist includes the steps needed to set up a Prism application for View-switching, and the demo app included with the article is a View-switching app. For more information on View-switching, see the companion article. Note that the demo app does not implement logging.

Step 1: Create a Prism Solution

The first step in setting up a Prism application is to create and configure the solution and Shell project.

Step 1a – Create a solution: Create a new WPF project, making sure that the Solution drop-down in the New Project dialog is set to ‘Create new solution’. The Name field should contain the name for your solution. If you are going to use the WPF Ribbon in your app, be sure to select WPF Ribbon Application as the project type, since its window differs somewhat from the standard WPF window.

Step 1b – Add DLLs to the solution library: Create a folder at the root level of your solution called [Solution Name].Library. In that folder, create a subfolder named Prism. Add the following DLLs to that folder:

We copy the Prism DLLs to a solution library folder so that we can use a private, or side-by-side installation of Prism for our solution. This approach eliminates some of the versioning problems that can occur when Prism is updated. For example, I have several Prism 2.x solutions that I maintain. Since each solution uses its own Prism DLLs, the older version does not interfere with the newer version, and vice versa.

Step 1c – Set references to library DLLs: In the References section of Solution Explorer, add references to the DLLs added to the solution library in the preceding step.

Step 1d – Rename the Shell window: First, rename MainWindow.xaml to ShellWindow.xaml in Solution Explorer. Then, open ShellWindow.xaml.cs in VS. The class will still be named MainWindow. Rename the class using the Visual Studio refactoring tools; this will change all references to the class, as well.

Step 1f – Lay out Shell window: ShellWindow.xaml will contain controls that are declared as regions, typically ContentControl or ItemsControl objects. Each of these controls represents a region in the Shell window. These regions will hold the content that is loaded into the Shell window.

ContentControl regions are suitable for regions that will display one View at a time.

ItemsControl regions are suitable for regions that will display several Views at once, such as the task buttons in the screenshot above.

ShellWindow.xaml is laid out in much the same way as any other window, using grids, splitters, and other layout controls. The only difference is that the window contains few if any real controls. Instead, it contains placeholder controls that are declared as regions. Prism inserts content from regions into these placeholders later.

Each region control needs both a x:Name and a RegionName. The regular x:Name is used by your code to refer to the control as needed. Prism’s RegionManager uses the RegionName to load content later. So, a region declaration based on an ItemsControl looks like this:

Step 1g – Create a Bootstrapper: The bootstrapper initializes a Prism app. Most of the heavy lifting is done by the Prism library, and the main task of the bootstrapper class created by the developer is to invoke this functionality. To set up the bootstrapper, first create a class in the Shell project called Bootstrapper.cs. Update the class signature to inherit from the UnityBootstrapper class.

A sample Bootstrapper class is included in the download file. The sample class includes the method overrides discussed below.

Step 1h – Override the CreateShell() method: Create an override for the CreateShell() method so that it looks like this:

protectedoverride System.Windows.DependencyObject CreateShell()
{
/* The UnityBootstrapper base class will attach an instance
* of the RegionManager to the new ShellWindow. */returnnew ShellWindow ();
}

This override creates a new ShellWindow and sets it as the Prism Shell.

Step 1i – Override the InitializeShell() method: Create an override for the InitializeShell() method of the UnityBootstrapper class. The override method will set the base class Shell property to the Shell window for the app. The override should look like this:

Step 1j – Override the CreateModuleCatalog () method: This step assumes you want to use Module Discovery to populate the module catalog, where application modules are simply placed in a specified folder, where Prism will discover and load them. Create an override for the CreateModuleCatalog() method of the UnityBootstrapper class. The override method should look like this:

All we need to do is specify the folder where modules will be placed when compiled. Here, we specify a Modules subfolder in the Shell project bin folder.

Step 1k – Override the ConfigureRegionAdapterMappings() method: If your application uses any Region Adapters, create an override for the ConfigureRegionAdapterMappings() method of the UnityBootstrapper class. Region Adapters are used to extend Prism region capabilities to controls (such as the WPF Ribbon) that cannot support Prism regions out-of-the-box. See Appendix E to the Developer's Guide to Microsoft Prism.

Step 1l – Modify the App class: The App class represents the application—it is called when the application starts. We will use the App class to set up our Prism application, by configuring our logging framework and calling the Bootstrapper we created in the preceding steps. So, open App.xaml.cs, and add the following override method to the class:

The logger configuration call assumes we are using the log4net framework, and that it is being configured in an App.config file. See the How-To document Configuring log4net for more information on configuring log4net in this manner.

Step 1m – Modify App.xaml: WPF applications are normally initialized by the StartupUri attribute in App.xaml. This attribute simply points to the main window as the window to load on startup. Since we are taking control of initialization in the App class, we don’t need the XAML attribute. So, open App.xaml and delete that attribute.

Step 1n – Create a custom logger class: Create a new custom logger class, implementing ILoggerFacade, to enable Prism to log using your logging framework. A sample custom logger is attached to this document as Appendix B. It enables Prism logging with the log4net framework.

Step 1o – Initialize the custom logger: In the application bootstrapper, add an override to the CreateLogger() method. The override simply needs to create and return an instance of your custom logger, like this:

Step 2: Add Modules to the Application

Modules are logical units in Prism applications. Each module is set up as a separate Class Library (DLL) project within the Prism solution. A code listing for a sample module initializer class is attached to this document as Appendix B.

Step 2a – Create a module: Add a WPF User Control Library project to your solution. Name the module according to this pattern: [SolutionName] .Module[Module Name]. For example, if the solution is named MySolution, and the module represents a folder navigator, you might name it MySolution.ModuleFolderNavigator.

Step 2b – Add Prism references: Add references to the following Prism and Unity assemblies to your new module project:

Microsoft.Practices.Prism.dll

Microsoft.Practices.ServiceLocation.dll

Microsoft.Practices.Unity.dll

Microsoft.Practices.Prism.UnityExtensions.dll

The Microsoft.Practices.ServiceLocation reference is required for the Prism Service Locator, which is used to resolve objects from the DI container (see ConfigureRegionAdapterMappings() in Appendix A to this article).

Step 2c – Rename the Module Initializer class: Rename the Class1.cs file in the new module project to [Module Name]. For example, if the module project is named MySolution.ModuleFolderNavigator, rename Class1 to ModuleFolderNavigator.

Step 2d – Derive from the IModule interface: Change the class signature of the Module Initializer class to derive from the IModule interface. VS will show error squiggles under the Interface name (because it is not yet implemented) and will offer to implement the interface. Tell VS to do so, and it will add the Initialize() method required to implement IModule. Leave the Initialize() method as-is, for now.

Step 2e – Add folders to the module project: Add the following folders to the modules project to help organize the module code:

Commands: Contains ICommand objects used by local View Models.

Services: Contains local services invoked by the commands.

ViewModels: View Models for the module’s Views.

Views: The module’s Views.

You may or may not use all of these folders, depending on how you structure your applications. For example, I use a Commands folder, because I prefer to encapsulate each ICommand object in a separate class, rather than using Prism’s DelegateCommand objects. I find that it separates code better and keeps my View Models from becoming cluttered.

Step 2f– Add a post-build event to each module: Since the whole point of Prism is to eliminate dependencies between the Shell and its modules, the VS compiler won’t be able to detect the app’s modules as dependencies of the Shell project, which means the modules won’t get copied to the Shell’s application output folder. As a result, each module will need a post-build-event command to copy the module to the Modules subfolder in the Shell project application output folder. That’s the folder Prism will search for application modules.

You can access the post-build command in the project's Properties pages, under the Build Events tab:

If the application contains any projects designed to hold its common elements (these projects are typically named Common, Infrastructure, or something similar), chances are that the Visual Studio compiler won't detect these assemblies either, unless the Shell contains a reference to them. If the Visual Studio compiler can't detect these projects, it will throw an exception when the app tries to run the Bootstrapper, saying that it can't find the projects. In that case, you will need to add a post-build command for those projects, as well. The command for the event should look like this:

xcopy /y "$(TargetPath)" "$(SolutionDir)<ShellProjectName>\$(OutDir)"

Note that the common assemblies are copied to the root level of the Shell project's output directory, not to the Modules subfolder.

Many desktop applications use SQL Compact or some other in-process database as a data store. Typiucally, the database assemblies are added to the project for a private deployment. If that's the case, chances are the Visual Studio compiler will miss the database assemblies, because the Shell project knows nothing about them. So, we need a post-build command for those assemblies as well. I generally place the command in my Common project, or in a Persistence project, if I have isolated persistence in a separate project. The specific command is going to vary, depending on the database technology used, and some databases mayl require several commands to get the assemblies and support DLLs copied to the correct locations in the Shell project's output directory. Here is the command that I use to copy all required SQL Compact assemblies from my Common project to the Shell project output folder:

I have a library folder at the root of my solution that holds any controls and other assemblies the application needs. The SQL Compact assemblies are in a SqlCompact subfolder. I copy both 32 and 64-bit versions of SQL Compact, so the command copies the main assemblies in the SqlCompact folder, as well as the 32 and 64-bit subfolders and their contents. And again, we copy to the output root folder, not to the Modules subfolder.

Finally, if the module’s Views use any third-party or custom controls, the VS compiler probably won’t detect the third-party control as a dependency of the Shell, for the same reason it can’t detect modules and common assemblies. As a result, at least one of the modules using each third-party or custom control will need a separate post-build-event command to copy the control to the Shell project application output folder. Here is an example:

Note the use of the $(TargetDir) macro, followed by the name of the DLL to be copied to the Shell app output directory. The macro points VS to the output folder for the module’s assembly, which will contain the third-party control, since VS will detect the control as a dependency of the module. And it should come as no surprise that we copy the third-party control to the root level of the Shell project output folder.

Finally, note the following with respect to the post-build command:

The command needs to be contained in a single line. For example, if there is a line break between the source and destination paths, the command will exit with an error.

There can be no spaces between macros and hard-coded portions of the command. For example, $(SolutionDir) Prism4Demo.Shell\$(OutDir)Modules\ will fail, because there is a space between (SolutionDir) and Prism4Demo.Shell\$(OutDir)Modules\.

Macros contain their own terminating backslash, so you don’t need to add a backslash between a macro and a subfolder.

When copying modules to the Modules subfolder, xcopy will create the subfolder, if it doesn’t already exist.

The /y parameter suppresses the overwrite prompt for the Xcopy command. If this param is omitted, Windows will attempt to show the prompt after the first compile, which will cause Visual Studio to abort the command and exit with Code 2.

Step 3: Add Views to Modules

The next step in setting up a Prism application is to add Views to the application’s modules. The application UI is composed of module Views that Prism loads into the Shell window regions, which means that each View represents a part of the overall UI. Accordingly, Prism Views are generally contained in WPF UserControls.

Step 3a – Create the View: Create a View, which will usually take the form of a WPF User Control. You don’t need to do anything special to the View to enable its use with Prism. Note that the View may be a composite of several controls, as in a UI form, or it may consist of a single control, such as an Outlook-style Task Button or an application Ribbon Tab. In some cases, a View composed of a single control may derive from the control, rather than from UserControl. For an example, see the Ribbon Tab Views in the demo app modules.

Step 3b – Register the View: In the module’s Initialize() method, register the View with either the Prism Region Manager, or the Unity container. The choice of registry depends on the behavior desired from the View:

If the View will be loaded when the module is loaded, and if it will last for the lifetime of the application, register the View with the Prism Region Manager. An example of this type of View would be an Outlook-style Task Button.

If the View will be loaded and unloaded as the user navigates within the application, register the View with the Unity container—we will use RequestNavigate() later to load the View. Examples of this type of View would be a UI form, or an application Ribbon Tab.

The Initialize() method in the module’s initializer class should look like this:

Here are the method parameters for IRegionManager.RegisterViewWithRegion():

Region name: The first parameter specifies the name of the region into which the View will be loaded.

Type to register: The second parameter specifies the type of the View to be loaded into that region. Prism takes care of the actual loading of the View.

And here are the parameters for IUnityContainer.RegisterType<TFrom, TTo>():

TFrom: The first type parameter should always be System.Object, because this is how Prism will initially resolve the View object.

TTo: The second type parameter should match the actual type of the View object. Unity will map the resolved object from System.Object to this type.

View name: The method parameter specifies the name of the View to be registered, as a string value.

By default, Prism creates a new instance of the View object every time a resolution is requested. You can override this behavior, and register a class as a singleton, by passing a second method parameter, in the form of a new ContainerControlledLifetimeManager().

Note that there are several other overloads for IUnityContainer.RegisterType(), including an overload that allows the developer to specify an interface or base class to resolve, and the concrete type to return when a resolution is requested.

Step 3c – Implement the IRegionMemberLifetime interface: If the View should be unloaded when its host module is deactivated, implement the IRegionMemberLifetime interface on either the View or its View Model. The interface consists of a single property, KeepAlive. Setting this property to false will cause the View to be unloaded when the user navigates to a different module. Here is sample code to implement the interface:

If KeepAlive will always have a value of false, then you can safely implement the interface on the View class and hard-code the value (as shown above). However, if the value of the property will be changed at run-time, implement the interface on the corresponding View Model instead, in order to avoid having the program’s back end interacting directly with the View.

Step 3d – Add the ViewSortHint Attribute: If the View will be loaded into an ItemsControl region, or any other region that holds multiple items, you may want to add the ViewSortHint attribute to the View. This attribute specifies the sort order of the View when it is loaded into the region. Here is an example for an Outlook-style Task Button:

Note that the control declared as a Prism region must support sorting for the ViewSortHint attribute to have any effect.

Step 4: Add a Common Project to the Application

A Prism production app will have numerous base classes, interfaces, CompositePresentationEvent classes, and resource dictionaries. We can centralize dependencies and avoid duplication by placing all of these application resources in a single project. The modules contain a reference to this project, which enables them to invoke base and other classes directly, and to access resource dictionaries using ‘pack URLs’.

The Shell project can serve as the repository of these application-wide resources. However, that approach tightens coupling between the modules and the Shell, since modules can no longer be tested without the Shell. For that reason, I prefer to locate the resources in a class library designated as the Solution’s Common project.

I mentioned above that modules can use pack URLs to access resource dictionaries across assembly boundaries. A pack URL is simply a URL that contains a reference to another assembly on the user's machine, typically another project in the same solution. They are rather odd-looking, but they get the job done. I won't spend much time on them here, since they are well-documented on MSDN. Here is an example of a pack URL that references a resource dictionary called Styles.xaml in a Common project of a Prism app. The dictionary is being referenced at the application level of another project in the solution:

Note that we don't use a resource dictionary in the demo app, so it doesn't contain any references of this sort.

I typically add the following folders to the Common project:

BaseClasses

Interfaces

Events (for CompositePresentationEvent classes)

ResourceDictionaries

You can use a Common project as the repository for your business model and data access classes as well, but I prefer to put these classes in separate projects. I find that approach better for separating code and managing dependencies between layers of my application.

Conclusion

The preceding steps will produce the basic framework for a Prism application. There are a number of other elements involved in a production app, such as a View Model for each View defined in the application’s modules, and the back-end logic that implements the application’s use cases. You can find additional documentation for the demo app, as well as a discussion of loosely-coupled communication between modules, in the companion article cited above.

As always, I welcome peer review by other CodeProject users. Please flag any errors you may find and add any suggestions for this article in the Comments section at the end of the article. Thanks.

Appendix A: Sample Bootstrapper Class

The following listing shows the code for the Bootstrapper class in the demo app:

Share

About the Author

David Veeneman is a financial planner and software developer. He is the author of "The Fortune in Your Future" (McGraw-Hill 1998). His company, Foresight Systems, develops planning and financial software.

Comments and Discussions

I would like to know if you have tried to change the culture in your demo, please? I tried but I get an error("Input string not in correct format"). It seems to point towards the fsTaskButton but I may be wrong. If I remove the < fsc:TaskButton /> from the 2 modules then I can see other languages string appear in the rest of the app.