Introduction
A modular design adopts the Lego approach to building an application. There are lots of small blocks loosely coupled together with each block knowing little about its neighbours other than how it connects with them. This greatly facilitates project maintenance and testing as each block can be easily swapped out and replaced without disrupting the other modules. This article discusses how to use the Ninject and Prism frameworks to produce a loosely-coupled modular WPF Application.
Loose Coupling.
One of the ways that loose coupling can be achieved is by using the Inversion of Conrol (IOC) design pattern. With this pattern the bit that’s inverted is the way a class plugs into, or connects with, a dependent object. Instead of the class plugging into the object, by using the object’s New
method, the object is plugged into the utilizing class, usually through the class’s constructor. A dependency injection framework is responsible both for the assembly of the object and making it available to the class that uses it. The class’s constructor often takes an Interface as a parameter so that the concrete Type passed to it can be determined by the framework and all object creation can be done at a central location.
The Ninject Dependency Injection Framework.
Ninject’s Kernel
class creates the objects. This class has a container that stores references to objects using a Dictionary
type List. The references are stored in the container using this sort of syntax.
Kernel.Bind<Idirector>().To<director>();
Objects are then assembled using
Kernel.Get<IDirector>();
The object returned will be the Director
class. The idea behind this is that you should program to Interfaces rather than concrete classes and let the Kernel
determine the concrete class that's returned. A different concrete class can be returned for testing or updating purposes by simply changing the binding in the Kernel
. In the sample application, the HeadViewViewModel
takes an IDirector
type class as a parameter in its constructor. The HeadViewViewModel
is added to the Kernel
like this
Kernel.Bind<HeadViewViewModel>().ToSelf();
With all the above bindings in place, a call to Kernel.Get<HeadViewViewModel>();
will run the class’s constructor. Ninject will see that the constructor needs a parameter of type IDirector
, it will then search its container to find the appropriate binding and inject the Director
class into the constructor.
Why Use Ninject?
The decision regarding which dependency injection framework to use is mainly a matter of taste, Ninject is only one of many and they all have advantages and disadvantages. As well as being well supported and intuitive to use, Ninject has a couple of particularly attractive features. First off, there is no need to pollute dependent methods with adornments and secondly the error messages are comprehensive, detailing what has gone wrong , the possible causes and suggested remedies. The following paragraphs describe a simple way of using Ninject with Windows Presentation Foundation (WPF).
Configuring The Main Window
The ‘out of the box’ WPF application has a MainWindow
as a view onto which numerous controls can be placed. The enabling code for the controls resides in the window’s code behind or its DataContext
. A cleaner approach is to have a Shell
window that just defines regions within it. Modules (dlls) can then mount views in a particular region without knowing anything about the Shell
other than a region’s name. A RegionManager
is needed to pull off this trick. This is where Ninject comes in useful as it can make the RegionManager
available to any module that needs it. Here’s an example Shell
window that defines two regions, a HeadRegion and a BodyRegion.
<Window x:Class="WpfNinjectMef.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"
Title="Shell" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
<Grid>
<StackPanel >
<ItemsControl cal:RegionManager.RegionName="HeadRegion" />
<ItemsControl Margin="0,20,0,0" cal:RegionManager.RegionName="BodyRegion" />
</StackPanel>
</Grid>
</Window>
With the Shell
window in place, it’s time to define some modules that can interact with it and with each other.
The Module
A module is nothing more than Class Library (Dll) defined in its own project within the Solution for the application. With Ninject, a module has a Module class derived from NinjectModule
. This class has a Load
method that enables the module to be initialised when it is first loaded.
Activating a View Region.
A RegionManager
class is used to inject a view from a module into a region in the Shell
window. This is usually done in the module’s Load
method. The method is called when the module is loaded. In this example, a HeadView
is injected into the HeadRegion of the Shell
window. A view is simply a WPF UserControl
var regionManager = this.Kernel.Get<IRegionManager>();
var headView = this.Kernel.Get<HeadView>();
IRegion region = regionManager.Regions["HeadRegion"];
region.Add(headView, "HeadView");
region.Activate(headView);
Communicating between modules.
It’s often necessary to pass objects between modules for further processing or to send notification that an event has occurred. The pattern used in the example employs the Microsoft.Practices.Prism.PubSubEvents EventAggregator
. The procedure for its use is quite straightforward. First, define an event by sub-classing the PubSubEvent<T>
class
public class HeadMessageSentEvent : PubSubEvent<string>
{
}
The publisher then publishes the event as follows
aggregator.GetEvent<HeadMessageSentEvent>().Publish(this.message);
The observer subscribes to the event using something like this
aggregator.GetEvent<HeadMessageSentEvent>()
.Subscribe(this.MessageReceived);
......
private void MessageReceived(string msg)
{
this.ReceivedMessage = msg;
}
That’s it, there is no need to worry about having to decorate methods with attributes or bother with unsubscribing.
Setting the DataContext.
Ninject provides a neat way of setting a View’s DataContext. It is possible to bind a class type to a method so that, when Kernel.Get<classType>(
) is called, the method runs. Here’s how.
Kernel.Bind<HeadView>()
.ToMethod(context => new HeadView { DataContext = this.Kernel.Get<HeadViewViewModel>() });
With this binding in place, calling Kernel.Get<HeadView>();
will create a new HeadView
and set its DataContext
to a HeadViewViewModel
class by using an object initializer.
Booting Up the Application
Before the application can run, the Ninject Kernel container has to be configured, the modules initialized and the Shell
window created. This is the job of a Bootstrapper class that has the NinjectBootstrapper
class as its base. The NinjectBootstrapper
class is part of a Ninject extension for Prism available here.
public class WpfNinjectBootstrapper : NinjectBootstrapper
{
#region Methods
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.Kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
}
protected override DependencyObject CreateShell()
{
return new Shell();
}
protected override void InitializeModules()
{
base.InitializeModules();
Kernel.Load("*.dll");
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Shell)this.Shell;
Application.Current.MainWindow.Show();
}
#endregion
}
Configuring App.Xaml to run the Bootstrapper.
The default App.xaml has the following in the opening Application tag.
StartupUri="MainWindow.xaml"
This needs to be deleted and the method below inserted into App.xaml.cs.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bootstrapper = new WpfNinjectBootstrapper();
bootstrapper.Run(true);
}
Everything is now ready to go. It’s a good idea to keep a weather eye on the output window when running in debug mode. Some errors can lead to a severe loss of functionality without actually breaking the application. Tracking them down in the output window can save a lot of head scratching.
The Sample Applications.
There are two sample demos, one using WPF and the other Silverlight. Each application is very basic, they only have two NinjectModules
. Each module mounts a view in a separate region of the Shell
window. A nonsensical message is exchanged between the modules and displayed in the window when either one of the two buttons is pressed. The application uses the Prism.NinjectExtension
. This extension seems to depend on historical versions of Prism and Ninject so the extension source code has been imported into the solution in order that the latest version of its dependencies can be used. I am grateful to rhyswalden for making the code available.
Conclusion.
There is a lot more to Ninject and the modular design pattern than is presented here but it is hoped that this brief and somewhat over-simplistic introduction can form a basis for further reading.