Tags: .NET, C#, Dependency Injection, Dynamic, Modules, Ninject
Table of Contents
Dynamic module loading is the act of locating and loading modules at run-time. Instead of defining what modules and implementations to load via code, define the interfaces of the modules and then bind the desired implementations dynamically at run-time.
Here is a quote from Ninject's documentation:
This module location strategy is often used for plugin or Composite based architecture styles such as the Onion Architecture and allows one to decouple your container application from its slot-in subsystems.
Ninject is a dependency injection tool that includes a feature which enables dynamic module loading - the focus of this article.
This is a simple example of how to achieve dynamic module loading with Ninject. There are many ways to do dynamic module loading - this is one of them. In this example, the main console application is a simulated tool box. A tool is dynamically loaded at run time and the sound that it makes is written to the console output. Also included in this example is a basic setup for slotting in a different tools for dynamic module loading.
There are four projects:
- Drill - the drill module, to be loaded dynamically. The Drill makes a "Whir" sound.
- Hammer - the hammer module, to be loaded dynamically. The Hammer makes a "Wham" sound.
- Interfaces - interface definitions common to all projects.
- ToolBoxApp - console application that will perform the dynamic module loading and output the sound of the dynamically loaded tool. Only one tool can be loaded at a time.
This project contains the ITool
interface as defined below. This common interface will be referenced by the other projects.
ITool.cs
:
namespace Interfaces
{
public interface ITool
{
string Use();
}
}
This project has a dependency on the Ninject 3.3.4 Nuget package. The main method:
- Creates a Ninject kernel.
- Performs dynamic module loading by locating and loading all Ninject modules in the Modules directory.
- Gets the dynamically loaded implementation of
ITool
. - Writes the sound that the dynamically loaded tool makes to console output.
Program.cs
:
using Interfaces;
using Ninject;
using System;
namespace ToolBoxApp
{
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
kernel.Load("Modules\\*.dll");
var tool = kernel.Get<ITool>();
Console.WriteLine(tool.Use());
Console.ReadLine();
}
}
}
This project implements the Drill
tool and DrillModule
Ninject module. Ninject will load the DrillModule
if it is found.
Drill.cs
:
using Interfaces;
namespace Drill
{
public class Drill : ITool
{
public string Use()
{
return "Whir";
}
}
}
DrillModule.cs
:
using Interfaces;
using Ninject.Modules;
namespace Drill
{
public class DrillModule : NinjectModule
{
public override void Load()
{
Bind<ITool>().To<Drill>();
}
}
}
This project implements the Hammer
tool and HammerModule
Ninject module. Ninject will load the HammerModule
if it is found.
Hammer.cs
:
using Interfaces;
namespace Hammer
{
public class Hammer : ITool
{
public string Use()
{
return "Wham";
}
}
}
HammerModule.cs
:
using Interfaces;
using Ninject.Modules;
namespace Hammer
{
public class HammerModule : NinjectModule
{
public override void Load()
{
Bind<ITool>().To<Hammer>();
}
}
}
Solution configurations will be used to enforce which module will be dynamically loaded by ToolBoxApp
. If the solution is built with the *_Drill
configuration, then the DrillModule
will be loaded. If the solution is built with the *_Hammer
configuration, then the HammerModule
will be loaded.
Debug_Drill
configuration (note that the Hammer
project is not built):
Debug_Hammer
configuration (note that the Drill
project is not built):
ToolBoxApp
pre-build event (this will delete any existing Modules folder and create an empty Modules folder):
rd "$(TargetDir)Modules" /s /q
mkdir "$(TargetDir)Modules"
Drill
post-build event (this will copy Drill.dll to ToolBoxApp
's Modules folder):
xcopy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)ToolBoxApp\$(OutDir)Modules" /i /y
Hammer
post-build event (this will copy Hammer.dll to ToolBoxApp
's Modules folder):
xcopy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)ToolBoxApp\$(OutDir)Modules" /i /y
- Select the Solution configuration
Debug_Drill
orRelease_Drill
. - Rebuild the solution.
- Verify that Drill.dll exists in
ToolBoxApp
's Modules folder. - Verify that Hammer.dll does not exist in
ToolBoxApp
's Modules folder. - Run
ToolBoxApp
.- You should see Whir in the console output window.
- Select the Solution configuration
Debug_Hammer
orRelease_Hammer
. - Rebuild the solution.
- Verify that Hammer.dll exists in
ToolBoxApp
's Modules folder. - Verify that Drill.dll does not exist in
ToolBoxApp
's Modules folder. - Run
ToolBoxApp
.- You should see Wham in the console output window.
In addition to Ninject being a great dependency injection tool, Ninject also makes dynamic module loading possible. In the example, ToolBoxApp
dynamically loads all Ninject modules that are present in the Modules folder. The combination of Solution configurations and build events ensure that only one of DrillModule
(Drill.dll) or HammerModule
(Hammer.dll) will exist in the Modules folder at a given time.
The full example source code can be found here.
Reply to this tweet.