Monthly Archives: April 2009

Composite Application Guidance for WPF

Because my esteemed colleague Carl Kenne outed his excellent blog on TDD and WPF I felt I should probably put together a little something re: the CAG for WPF and Silverlight. The idea behind the Composite Application Guidance is to enable decoupled applications in WPF and Silverlight, utilizing a shell window, a dependency injection container, separate modules providing views that are displayed in regions either directly in the shell window or nested in regions provided inside the modules. For the conceptually isolated modules to communicate between each other a dedicated messaging system is created. Composite Events, to which separate modules subscribe to and others publish.

So what’s the point?

The point behind the guidance package is that it helps you structure your WPF application in such a way that you design a very loosely coupled system from the beginning. If you don’t have any use for module separation from the beginning, fine, make all your functionality reside in one module, that’s fine. You may want a few services, you may just want one, that’s fine too. The point is: Even with your one module consisting of a single Service managing one Model class and with your single Controller using one PresentationModel class to feed a single View xaml-file, you still have separated all your concerns, making testing a doddle with you being able to pass any mock object in just implementing the appropriate interfaces. You easily just make two different configurations for the DI-container of your choice for test and production environments.

So how do I get started?

Download the Composite Application Guidance package using the link above and pay attention to the requirements they specify right there on the download page. As you can see from the first page, one of the key new features introduced with this version of the CAG for WPF and Silverlight is that you can make Silverlight applications and WPF applications on a shared codebase using a nifty Project Linker. Less code is good. OK, so download the source code, open the shipped solutions and build them.

After that you are ready to create a solution and to get a feel for what this is and how it works.

Open up Visual Studio and get on with it

Create a WPF project called CompanyDashboard.Desktop (if you want to follow this story in detail, otherwise call it whatever you like). Use the Open in Windows Explorer menu option in Visual Studio and create a folder called Library.Desktop. In a new Windows Explorer window, open up your CAG source folder and browse down the Composite.UnityExtensions\Bin\Debug folder and copy everything pdbs, xmls and all into your new Library.Desktop folder. From Visual Studio, Add Reference and Browse to the folder and add everything. Rename (using the Refactor menu option, obviously) the Window1.xaml to Shell.xaml.

Add a class library project called CompanyDashboard.Common to your solution. Rename the Class1.cs to Constants.cs and add a public const string MainRegion containing the string value “MainRegion”.

Make it so that your shell.xaml looks like below:

<Window x:Class="CompanyDashboard.Desktop.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cal="http://www.codeplex.com/CompositeWPF"
    xmlns:common="clr-namespace:CompanyDashboard.Common;assembly=CompanyDashboard.Common"
    Title="Company Dashboard" Height="600" Width="800">
    <StackPanel>
        <ItemsControl Name="HeaderRegion"
                  cal:RegionManager.RegionName="{x:Static common:Constants.HeaderRegion}"
                  Height="200" Width="800" />
        <TabControl Name="MainRegion"
                  cal:RegionManager.RegionName="{x:Static common:Constants.MainRegion}"
                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        </TabControl>
    </StackPanel>
</Window>

The XAML above defines two regions, a header region that is an items control and a main region which is a tab control. Now the point of these regions will be explained further below, but for now just accept that this is what we are going to do.

Create your Unity bootstrapper

Add a class to your CompanyDashboard.Desktop solution. Call it CompanyDesktopBootstrapper and let it inherit UnityBootstrapper like so:

using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.UnityExtensions;
using System.Windows;
namespace CompanyDashboard.Desktop
{
    class CompanyDashboardBootstrapper :
         UnityBootstrapper
    {
        protected override DependencyObject
              CreateShell()
        {
            Shell shell = new Shell();
            shell.Show();
            return shell;
        }
        protected override IModuleCatalog
              GetModuleCatalog()
        {
            ModuleCatalog catalog =
                 new ModuleCatalog();
            return catalog;
        }
    }
}

The UnityBootstrapper class is abstract and needs help creating a Module Catalog and creating the Shell window, this is what we do above. Since the bootstrapper wants to set up all the instances we need to rock’n’roll we shall disable the built-in WPF startup-object management and start the UnityBootstrapper instead. To accomplish this we shall attack the App.xaml and App.xaml.cs files. In the app.xaml-file, remove the StartupUri attribute from the Application element. In the app.xaml.cs, add the following function:

        protected override
            void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            CompanyDashboardBootstrapper bootstrapper
              = new CompanyDashboardBootstrapper();
            bootstrapper.Run();
        }

This way, the bootstrapper is called and on its own creates the Shell window and any dependencies that are necessary or that become necessary with future versions of the software.

At this point you can press play on tape and view your empty CAG application.

Wait a minute… What are we doing here?

The sample will create a composite application bringing together information from a two sources, a “CRM system”, which will be the main source of customer information, and an “Order system” plus an extra module looking up address info on google maps and stuff like that. Whenever a customer is selected, the word gets out (Composite Events) and the other modules scramble to present their own information in context.

Adding some modules

Add a Class Library project to the solution and call it CompanyDashboard.CRMModule. Feel free to use Solution Folders to create some order in your solution explorer. Add another module called CompanyDashboard.SalesModule. What’s going to happen is that the bootstrapper will load the modules from a folder located under the application base folder. To enable this, a few things need to happen, such as post-build events that copy the DLL:s from the module output folders into the Modules folder under CompanyDashboard.Desktop/bin/Debug. Before we get to that though, we need to set up some references.

You will need the following references in a typical Module:

  • PresentationCode.dll
  • PresentationFramework.dll
  • WindowsBase.dll
  • Microsoft.Practices.Composite.dll
  • Microsoft.Practices.Composite.Presentation.dll

In CompanyDashboard.CRMModule, rename Class1.cs to CRMModule.cs and add a using statement for Microsoft.Practices.Composite.Modularity and let your CRMModule class implement the IModule interface. In order for your module to be dynamically loaded, you need to also add the attribute [Module(ModuleName=”CRMModule”)]. For the Sales Module, which will depend on the CRMModule, an additional attribute will need to be specified. The SalesModule.cs will look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Composite.Modularity;
namespace CompanyDashboard.SalesModule
{
    [Module(ModuleName = "SalesModule")]
    [ModuleDependency("CRMModule")]
    public class SalesModule : IModule
    {
        public void Initialize()
        {
        }
    }
}

This will now mean that the CRMModule will be initialized first, and then the SalesModule. In order to add a module to this application, all you need to do is to create a DLL, give it the right attributes and deploy it in the Modules folder and you have your new features accessible. We will demonstrate this with Module no. 3 eventually.

Come back in the next post to see how views are created and filled with data from separate data stores.