Click here to Skip to main content
15,868,016 members
Articles / Web Development / HTML

Using Ninject to produce a loosely-coupled modular WPF Application

Rate me:
Please Sign up or sign in to vote.
4.89/5 (7 votes)
29 Sep 2014CPOL6 min read 40.5K   1K   19   14
This article discusses how to use the Ninject and Prism frameworks to produce a loosely-coupled modular WPF Application.

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.

C#
Kernel.Bind<Idirector>().To<director>();

Objects are then assembled using

C#
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

C#
//This binds the ViewModel to itself rather than an Interface.
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.

C#
<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

C#
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

C#
//The message sent here is of Type string but it can be any object
  public class HeadMessageSentEvent : PubSubEvent<string>
  {

  }

The publisher then publishes the event as follows

C#
aggregator.GetEvent<HeadMessageSentEvent>().Publish(this.message);

The observer subscribes to the event using something like this

C#
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.

C#
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.

C#
  public class WpfNinjectBootstrapper : NinjectBootstrapper
    {
        #region Methods

        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();
//The aggregator needs to be a singleton otherwise each module will get a different copy
            this.Kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
        }

        protected override DependencyObject CreateShell()
        {
            return new Shell();
        }

        protected override void InitializeModules()
        {
            base.InitializeModules();
//Load all NinjectModule type dlls in the current directory into the Kernel
// and call  each module's Load method
//Need to add references in this project to the modules that are situated in other 
//projects for this to work.
            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.

C#
StartupUri="MainWindow.xaml"

This needs to be deleted and the method below inserted into App.xaml.cs.

C#
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.

Image 1

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
Wales Wales
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Praisevery nice Pin
BillW3321-Dec-15 4:05
professionalBillW3321-Dec-15 4:05 
GeneralRe: very nice Pin
George Swan21-Dec-15 5:13
mveGeorge Swan21-Dec-15 5:13 
QuestionBrillant Article..... Pin
Your Display Name Here15-Mar-15 15:50
Your Display Name Here15-Mar-15 15:50 
AnswerRe: Brillant Article..... Pin
George Swan16-Mar-15 4:23
mveGeorge Swan16-Mar-15 4:23 
GeneralRe: Brillant Article..... Pin
Your Display Name Here16-Mar-15 6:42
Your Display Name Here16-Mar-15 6:42 
GeneralJust an FYI... Pin
SledgeHammer0129-Sep-14 5:15
SledgeHammer0129-Sep-14 5:15 
GeneralRe: Just an FYI... Pin
George Swan29-Sep-14 7:51
mveGeorge Swan29-Sep-14 7:51 
GeneralRe: Just an FYI... Pin
SledgeHammer0129-Sep-14 8:28
SledgeHammer0129-Sep-14 8:28 
GeneralRe: Just an FYI... Pin
SledgeHammer0129-Sep-14 8:34
SledgeHammer0129-Sep-14 8:34 
GeneralRe: Just an FYI... Pin
George Swan29-Sep-14 11:47
mveGeorge Swan29-Sep-14 11:47 
GeneralRe: Just an FYI... Pin
SledgeHammer0129-Sep-14 13:08
SledgeHammer0129-Sep-14 13:08 
GeneralRe: Just an FYI... Pin
George Swan29-Sep-14 19:59
mveGeorge Swan29-Sep-14 19:59 
GeneralRe: Just an FYI... Pin
Your Display Name Here16-Mar-15 6:46
Your Display Name Here16-Mar-15 6:46 
GeneralRe: Just an FYI... Pin
Your Display Name Here16-Mar-15 6:44
Your Display Name Here16-Mar-15 6:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.