PRISM in 600 seconds

Welcome to the lean, mean, no Vicodin, U2U Consult PRISM machine. (595 seconds left.) CompositeWPF, or Composite Application Guidance (CAG) including Composite Application Library (CAL) is still commonly referred to as PRISM. The software component -CAL- extends Windows Presentation Foundation (WPF) with Modularity, Dependency Injection, Loosely Coupled Events, Distributed Commanding, and Run-Time Module Discovery, while leveraging the classic WPF principles and design concepts such as Dependency Properties, Routed Events, Commanding, Data Binding and Data Templates. In a composite application, functionality is embedded in small parts called modules. Each module is an autonomous, dynamically discoverable and loadable piece, containing its own view, logic and services. A module is preferably designed and implemented according to the MVP-pattern. The main application contains one or more shells: empty windows with regions that contain the placeholders for the module's views. This article walks through a small sample CAL-based solution. Here's how the GUI looks like, in sober black and white:

 

If you would build it as a single monolithic application, it would take you 10 minutes to develop. The  CAL-based implementation is actually composed of 6 (six) independent projects with hardly any references to one another:

And now 10 minutes is maybe even too long to explain how everything works. Anyway, I'm going to give it a try!

Shells and Regions

A Composite WPF application starts as a regular WPF application with references to all of the CAL-assemblies (the ones starting with Microsoft in the next screen shot):

The CompositeWPF namespace is imported into the XAML of the Main Window:

xmlns:cal="http://www.codeplex.com/CompositeWPF"


Regions are added in the XAML by creating ItemControl and ContentControl instances with a RegionName. This dependency property exposes the region's name to CAL's region manager:

<ItemsControl

   x:Name="MainRegion"

   cal:RegionManager.RegionName="MainRegion" />

 

Modules and Dependency Injection

The AboutModule is kind of a "Hello World" module: it just statically displays some company's logo. A module generally doesn't need to call the whole CAL-shebang, so it can get away with less references:

The module itself is a class that implements the IModule interface. It is decorated with the Module atttribute to allow dynamic discovery:

[Module(ModuleName = "AboutModule")]

public class AboutModule : IModule

{

    // ...

}


The graphical part of the module is implemented as a WPF UserControl and saved in a Views subfolder. It needs no special references or interfaces:

The IRegionManager parameter in the module's constructor will be discovered and populated by CAL's Dependency Injection Container (Unity, that is). It gives the module and the view access to the shell's region manager:

public AboutModule(IRegionManager regionManager)

{

    this.regionManager = regionManager;

}

 

CAL will call the module's constructor, and then its Initialize method, where the view is registered in the named region:

public void Initialize()

{

    this.regionManager.RegisterViewWithRegion(

        "MainRegion",

        typeof(Views.U2UConsultLogo));

}

 

Bootstrapping the Application

The main project contains a bootstrapper. It makes sure that CAL's components are properly initialized before anything else happens:

internal class Bootstrapper : UnityBootstrapper

{

    // ...

}

 

The OnStartUp of the application is overridden:

protected override void OnStartup(StartupEventArgs e)

{

    base.OnStartup(e);

 

    Bootstrapper bootstrapper = new Bootstrapper();

    bootstrapper.Run();

}

 

Since the bootstrapper will do the initialization, the StartupUri is removed from app.xaml:

<Application x:Class="U2UConsult.CAL.Sample.App"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <!--StartupUri="Shell.xaml"-->

</Application>

 

Back in the bootstrapper, the CreateShell() method is overridden to make sure that the shell gets loaded and displayed:

protected override DependencyObject CreateShell()

{

    Shell shell = new Shell();

    shell.Show();

    return shell;

}

 

The GetModuleCatalog() method is also overridden. It returns the collection of discovered modules. There are different types of module catalog subclasses that can inspect directories, configuration files, or even XAML files. The following code is an example of using the ModuleCatalog base class itself. This way of statically loading modules requires a reference from the shell project to the module project(s), so it's only used while developing/debugging:

protected override IModuleCatalog GetModuleCatalog()

{

    ModuleCatalog catalog = new ModuleCatalog();

    catalog.AddModule(typeof(AboutModule.AboutModule));

    return catalog;

}

 

Model View Presenter

The region at the bottom of the main window's shell displays the application's status. XAML-wise it's just a textblock. When it is is implemented using a (lightweight) MVP-pattern and packaged as a CAL module, it looks rather impressive:

The view contains a textblock which is bound to a property in the PresentationModel:

<TextBlock

   TextWrapping="NoWrap"

   Text="{Binding StatusMessage}"/>

 

The StatusModule requires more of the CAL feature set than the AboutModule, so it has more injected stuff (i.e. parameters in its constructor):

public StatusModule(

    IUnityContainer container,

    IRegionManager regionManager,

    IEventAggregator eventAggregator)

{

    this.container = container;

    this.regionManager = regionManager;

    this.eventAggregator = eventAggregator;

}

 

The registration of the views and services that are provided by the module, is nicely factored out to a seperate method:

public void RegisterViewsAndServices()

{

    this.container.RegisterType<Views.IStatusView, Views.StatusView>(

        new ContainerControlledLifetimeManager());

    this.container.RegisterType<PresentationModels.IStatusPresentationModel, PresentationModels.StatusPresentationModel>(

        new ContainerControlledLifetimeManager());

}

 

And here's the module's Initialize-method:

public void Initialize()

{

    this.RegisterViewsAndServices();

 

    PresentationModels.IStatusPresentationModel model =

        this.container.Resolve<PresentationModels.StatusPresentationModel>();

    IRegion statusRegion = this.regionManager.Regions["StatusRegion"];

    statusRegion.Add(model.View);

}

 

The PresentationModel gets its view from Dependency Injection, again through a parameter in its constructor. So here's where the model and its view are connected:

public StatusPresentationModel(Views.IStatusView view)

{

    this.view = view;

    view.Model = this;

 

    this.StatusMessage = "The status module is operational.";

}

 

Distributed Events

The SelectionModule allows the end user to select an option, after which it will update the application's status. This status is displayed in the status region at the bottom of the shell:

Some corners were cut here: the project only contains a Module and a View. Anyway, the SelectionModule needs to communicate with the StatusModule. Both modules share an event -an instance of CompositePresentationEvent- through CAL's event aggregator, a mechanism for distributed events. The reference to this event aggregator comes from ... Dependency Injection indeed: the constructors of SelectionModule, SelectionView, StatusModule, and StatusPresentationModel take an IEventAggrator parameter.

A global StatusReported event is declared in the application. The SelectionModule raises this event via a call to Publish():

private void RadioButton_Checked(object sender, RoutedEventArgs e)

{

    this.selectedOption = e.Source as RadioButton;

 

    string status = string.Format(

        "You selected '{0}'.",

        this.selectedOption.Content.ToString());

 

    this.eventAggregator.GetEvent<Infrastructure.StatusReportedEvent>().Publish(status);

}

 

The StatusModule has a local event handler for the same event:

private void OnAppStatusChanged(string message)

{

    this.StatusMessage = message;

}

 

It registers that handler with CAL through a Subscribe-call:

this.eventAggregator.GetEvent<Infrastructure.StatusReportedEvent>()

     .Subscribe(this.OnAppStatusChanged, ThreadOption.UIThread, true);

 

The handler itself is straightforward - data binding will update the view:

private void OnAppStatusChanged(string message)

{

    this.StatusMessage = message;

}

 

As you observed, the reference to the event is not by name (like for a region), but it is made by type. Both modules, as well as all other modules that want to notify status changes, need to know the event type:

public class StatusReportedEvent : CompositePresentationEvent<string> { }


The type is defined is a so-called Infrastructure project to which multiple modules -and probably even the shell- will have a reference. To call a spade a bloody shovel: GlobalVariables would be a more appropriate name for this assembly. But that name seems to upset software architects.

Composite Commands

In WPF we like to communicate through Commands, not Events. So here's the next use case. The shell has a toolbar region on top. This will host the toolbar buttons of each individual module, but also some global commands. A global "Clear" button clears any registered view, and updates the status:

The global command itself is an instance of CAL's CompositeCommand class - a command that can have child commands. It is defined in the Infrastructure project, together with a proxy to it that can be overridden for unit testing:

namespace Infrastructure

{

    using Microsoft.Practices.Composite.Presentation.Commands;

 

    public static class GlobalCommands

    {

        public static CompositeCommand ClearCommand = new CompositeCommand();

    }

 

    public class ClearCommandProxy

    {

        public virtual CompositeCommand ClearCommand

        {

            get

            {

                return GlobalCommands.ClearCommand;

            }

        }

    }

}


The button with the large "X" is linked to the global command through its XAML:

<UserControl

   x:Class="GlobalCommandsModule.Views.SelectionCommandView"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:inf="clr-namespace:Infrastructure;assembly=Infrastructure" >

    <Button Command="{x:Static inf:GlobalCommands.ClearCommand}" ... />

</UserControl>


When the SelectionView is loaded, it hooks a DelegateCommand to the global ClearCommand:

public SelectionView(

    IEventAggregator eventAggregator,

    ClearCommandProxy commandProxy)

{

    InitializeComponent();

 

    this.eventAggregator = eventAggregator;

 

    this.clearCommand = new DelegateCommand<object>(this.Clear, this.CanClear);

    commandProxy.ClearCommand.RegisterCommand(this.clearCommand);

}


DelegateCommand is yet another CAL-class. It lets you directly specify the ICommand-code for Execute and CanExecute as delegates.

Dynamic Module Lookup

During development it makes sense to keep a reference from the host application to each module, and load it statically into the module catalog. At the end of the day, it's better to cut that reference and go for dynamic lookup. So all modules are compiled and their dll's gathered in a "Modules"-folder:

 

The "production"-bootstrapper makes use of one of the more specialized catalog modules:

protected override IModuleCatalog GetModuleCatalog()

{

    DirectoryModuleCatalog catalog = new DirectoryModuleCatalog();

    catalog.ModulePath = "../../Modules";

 

    return catalog;

}

 

Source Code

Here's the full source code of the sample application: U2UConsult.CAL.Sample.zip (1,79 mb)

Enjoy!