Introduction
What is the most iconic program ever written? In my mind, it’s the programmers first program “Hello Word
” - it’s where you see the very first fruits of your efforts. The first time you convert a thought to an action in the programming world.
In this article, I have made this classic programming example way too complicated. Converting what should be one file in one project in one solution to five separate examples with the very first example containing five separate projects in one solution.
These examples are not intended to be examples of writing “Hello World
” in a complex way; but rather in a way which can help explain dependency injection and inversion of control in a way which is not clouded by other technologies. What better way to do that then to make the core of the program do only one thing and that is to display “Hello World
”.
There are a number of dependency injection frameworks available. These examples use Structuremap
because it is one of the oldest frameworks and the framework of choice as these example programs were written.
Background
Originally, these were put together for examples to explain these principals to other developers with whom I worked, as part of this update I also moved from structuremap 2.x to version 4.x.
What is Dependency Injection
A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
Source - https://en.wikipedia.org/wiki/Dependency_injection
As an example:
static void Main(string[] args)
{
service sc = new service();
Client cc = new Client(sc);
cc.DoSomeThing();
}
Although a dependency can be passed to an object as a concrete class, it is more generally accepted to pass an object which implements an interface. Coding against the contract (Interface) allows you to pass any concrete class which implements the contract.
Example:
Public class SQLDBConnection : IDBConnection
Public class ODBCConnection : IDBConnection
Public class OracleConnection : IDBConnection
All classes share the same IDBConnection
contract definition.
- Programming against the interface allows you not to worry about implementation
- Factories can be used to create the concrete class
IDBConnection = DBFactory(“configfile”)
What is Inversion of Control?
In traditional programming, the flow of the business logic is determined by objects that are statically bound to one another. With inversion of control, the flow depends on the object graph that is built up during program execution. Such a dynamic flow is made possible by object interactions being defined through abstractions. This run-time binding is achieved by mechanisms such as dependency injection or a service locator. In IoC, the code could also be linked statically during compilation, but finding the code to execute by reading its description from external configuration instead of with a direct reference in the code itself.
In dependency injection, a dependent object or module is coupled to the object it needs at run time. Which particular object will satisfy the dependency during program execution typically cannot be known at compile time using static analysis.
Source - https://en.wikipedia.org/wiki/Inversion_of_control
static void Main(string[] args)
{
using (var sm = new Injector.ContainerBootstrapper())
{
IClient cc = sm.GetInstance<IClient>();
cc.DoSomeThing();
}
}
How are Concrete Classes Injected?
- Factory Pattern
- Service Locator pattern
- Dependency injection
- Constructor injection
- Parameter injection
- Setter injection
- Interface injection
- Other patterns
You will see examples of both the Service Locator pattern as well as Dependency injection as part of this article.
Structuremap
Structuremap
is a Dependency Injection / Inversion of control tool for .NET that can be used to improve the architectural qualities of an object oriented system by reducing the mechanical cost of good design techniques:
Consider using StructureMap
if you:
- require significant extensibility
- simply want a generic configuration tool
- want to support multiple deployment configurations
- are using a Test-Driven Development philosophy or want to largely automate testing
- want to isolate a troublesome subsystem or provide smooth migration paths away from legacy interfaces
- need a great deal of configurable properties or plugin hot spots
Objects created by structuremap
can be configured for the following life times:
PerRequest
- The default operation. A new instance will be created for each request. Singleton
- A single instance will be shared across all requests ThreadLocal
- A single instance will be created for each requesting thread. Caches the instances with ThreadLocalStorage
. HttpContext
- A single instance will be created for each HttpContext
. Caches the instances in the HttpContext.Items
collection. HttpSession
- A single instance will be created for each HttpSession
. Caches the instances in the HttpContext.Session
collection. Use with caution. Hybrid
- Uses HttpContext
storage if it exists, otherwise uses ThreadLocal
storage.
Other IOC Options – Partial Listing
- Microsoft Unity
- Ninject
- Castle Windsor
- Spring.Net
- Autofac
Using the Code
A_IOC – Our starting point. Still more complex than simply writing:
static void Main(string[] args)
{
Console.Write("Hello World");
Console.Read();
}
This project set the stage, all of the other solutions will build from this one. As this article progresses, you will be directed to points of interest in each solution to compare and contrast.
All the projects contain the following projects:
- IOC -
Main
static void Main(string[] args)
{
LoggerClass.Logger l =
new LoggerClass.Logger(LoggerClass.Logger.LogLevel.Info,@"\logA","logfile");
FacadeClass.FacadeClass f = new FacadeClass.FacadeClass();
l.Log("Start", "Main", LoggerClass.Logger.LogLevel.Info);
Console.Write(f.StringHelloWorld());
l.Log("End", "Main", LoggerClass.Logger.LogLevel.Info);
Console.Read();
}
FacadeClass
- Uses ObjectHello
and ObjectWorld
to create the displayed string
public class FacadeClass
{
ObjectHello.HelloClass a = new ObjectHello.HelloClass();
ObjectWorld.WorldClass b = new ObjectWorld.WorldClass();
public FacadeClass()
{
a.CreateNewHello("Hello");
b.CreateNewWorld("World");
}
public string StringHelloWorld()
{
return a.Hello + ' ' + b.World;
}
}
ObjectHello
and ObjectWorld
- Contrived classes returning what is passed in on the constructor
public class HelloClass
{
public void CreateNewHello(string hello)
{
Hello = hello;
}
public string Hello { get; set; }
}
LoggerClass
- Simple logger - Later on, we will add two additional projects called
InterfaceClass
and Injector
If we were to look at how intertwined each piece was together, it look like this:
B_IOC – The Journey Starts
Firstly, notice this solution contains a new project called InterfaceClass
. This is a project which will eventually become the focal point of this and all solutions to follow.
It is simply where we keep all our interfaces for the solution. Remember an interface is a contract which objects that implement the interface have to provide a concrete implementation. The interface itself contains no implementation code, only signatures to program against.
public interface IFacadeClass
{
string DisplayStuff();
}
public class FacadeClass : Interfaces.IFacadeClass
{
Interfaces.IHelloClass a = new ObjectHello.HelloClass();
Interfaces.IWorldClass b = new ObjectWorld.WorldClass();
public FacadeClass()
{
a.CreateNewHello("Hello");
b.CreateNewWorld("World");
}
public string DisplayStuff()
{
return a.Hello + ' ' + b.World;
}
}
Secondly, notice all the project files now derive from the Interfaces.IFacadeClass
and must implement their respective contracts.
Thirdly, notice local variables have been replaced with their Interface counterpart.
Instead of:
LoggerClass.Logger l = new LoggerClass.Logger
(LoggerClass.Logger.LogLevel.Info,@"\logA","logfile");
FacadeClass.FacadeClass f = new FacadeClass.FacadeClass();
ObjectHello.HelloClass a = new ObjectHello.HelloClass();
ObjectWorld.WorldClass b = new ObjectWorld.WorldClass();
We have now:
ILogger l = new LoggerClass.Logger(Interfaces.LogLevel.Info, @"\logB", "logfile");
Interfaces.IFacadeClass f = new FacadeClass.FacadeClass();
Interfaces.IHelloClass a = new ObjectHello.HelloClass();
Interfaces.IWorldClass b = new ObjectWorld.WorldClass();
The codemap now looks like this:
C_IOC – Ready to Inject the Dependencies
In this project, the only thing that really changes is the facade. We change this to not rely on the objects being instantiated in the facade itself. As written, this project will compile but will fail when executing. The method StringHelloWorld
will fail with a null
reference error.
Additionally, we are passing in a reference to the log object to log inside the facade
class.
Comments are supplied to correct and run successfully.
public class FacadeClass : Interfaces.IFacadeClass
{
Interfaces.IHelloClass a;
Interfaces.IWorldClass b;
public FacadeClass() { }
public FacadeClass(Interfaces.IHelloClass ac,
Interfaces.IWorldClass bc,Interfaces.ILogger lg)
{
a = ac;
b = bc;
if (lg != null)
lg.Log("Start", "Facade",Interfaces.LogLevel.Error);
a.CreateNewHello("Hello");
b.CreateNewWorld("World");
}
public string StringHelloWorld()
{
return a.Hello + ' ' + b.World;
}
}
Codemap non-working:
Notice ObjectA
and B
(Hello
and World
) are created but nothing depends on them.
Now the objects are included.
static void Main(string[] args)
{
Interfaces.IFacadeClass f = new FacadeClass.FacadeClass();
}
D_IOC – Include structuremap
In this project, we include a new project called injector
, it has a class called ContainerBootStrapper
which will be responsible for wiring up the concrete classes to inject into the classes constructors.
You should Enable NuGet package restore to ensure that you automatically get structuremap
assemblies.
The projects which change include:
IOC - using container to return concrete object for logger and facade.
static void Main(string[] args)
{
var sm = new Injector.ContainerBootstrapper();
Interfaces.ILogger log = sm.GetInstance<Interfaces.ILogger>();
Interfaces.IFacadeClass f = sm.GetInstance<Interfaces.IFacadeClass>();
if (log != null && f != null)
{
log.Log("Start", "Main", Interfaces.LogLevel.Warning);
Console.Write(f.StringHelloWorld());
log.Log("End", "Main", Interfaces.LogLevel.Warning);
}
Console.Read();
}
ObjectHello
- Now logs as well and has the logger injected as part of the constructor.
public class HelloClass : Interfaces.IHelloClass
{
Interfaces.ILogger _lg;
public HelloClass(Interfaces.ILogger lg)
{
_lg = lg;
}
string _hello = string.Empty;
public void CreateNewHello(string hello)
{
if(_lg != null)
_lg.Log("Create Hello","HelloClass",Interfaces.LogLevel.Info);
_hello = hello;
}
public string Hello { get { return _hello; } }
}
Injector
- This is a class which should be instantiated as close to the beginning of the program start as possible. You can see it is called in the very first line of Program.cs.
public class ContainerBootstrapper
{
Container _c;
public ContainerBootstrapper()
{
_c = new Container(x =>
{
x.For<Interfaces.IHelloClass>().Use<ObjectHello.HelloClass>();
x.For<Interfaces.IWorldClass>().Use<ObjectWorld.WorldClass>();
x.For<Interfaces.IFacadeClass>().Use<FacadeClass.FacadeClass>();
x.For<Interfaces.ILogger>().Singleton().Use<LoggerClass.Logger>()
.Ctor<Interfaces.LogLevel>("levelset").Is(Interfaces.LogLevel.Info)
.Ctor<String>("pathname").Is(@"\logD")
.Ctor<String>("filename").Is("logfile");
});
}
public T GetInstance<T>()
{
return _c.TryGetInstance<T>();
}
}
The first three lines which are of format x.For<interface>().Use<concrete>()
map the concrete class to us for the interface.
The fourth line is similar but changes its lifetime to singleton and then sets the parameters to pass into the concrete class Logger
.
In earlier versions, we would create a logger such as this:
Interfaces.ILogger log =
new LoggerClass.Logger(Interfaces.LogLevel.Info, @"\logC", "logfile");
Now it is created by the container:
Interfaces.ILogger log = sm.GetInstance<Interfaces.ILogger>();
The codemap now looks like this:
Notice now that since injector still needs a reference to each object to resolve, all the project DLLs will still be imported automatically into the application bin directory.
E_IOC - Almost Done!
In this version, we break the dependency of including each object in the container to resolve. Additionally, I have an example of AOP (Aspect Oriented Programming) where the real concrete class is wrapped with a similar class as a proxy and added functionality for centralized error handling.
IOC - added the IDisposable
contract (interface) to the container so that we can wrap instantiation in a using
.
using (var sm = new Injector.ContainerBootstrapper())
{ ... }
Injector
- changed from fluent style of associating interface to concrete to scanning assemblies.
public class ContainerBootstrapper : IDisposable
{
Container _c;
public ContainerBootstrapper()
{
_c = new Container(x =>
{
x.Scan(s =>
{
s.AssembliesFromApplicationBaseDirectory
(assm => assm.FullName.StartsWith("Object"));
s.AssembliesFromApplicationBaseDirectory
(assm => assm.FullName.StartsWith("Facade"));
s.AssembliesFromApplicationBaseDirectory
(assm => assm.FullName.StartsWith("Logger"));
s.WithDefaultConventions();
});
});
_c.Configure(a => a.ForSingletonOf<Interfaces.ILogger>());
_c.Configure(a => a.For<Interfaces.IFacadeClass>().DecorateAllWith<facadeAOP>());
}
public T GetInstance<T>()
{
return _c.TryGetInstance<T>();
}
public void Dispose()
{
_c.Dispose();
}
}
}
Notice a new class is added to the project called facadeAOP
, this is a class which implements the same Interface the object which is resolved to the interface. DecorateAllWith
will use this new class as a proxy. The proxy will instantiate the concrete class calling it when needed as well as the logger we need to log errors.
public class facadeAOP : Interfaces.IFacadeClass
{
Interfaces.IFacadeClass ic;
Interfaces.ILogger _log
public facadeAOP(Interfaces.IFacadeClass fc,Interfaces.ILogger log)
{
ic = fc;
_log = log;
}
public string StringHelloWorld()
{
try
{
return ic.StringHelloWorld();
}
catch (Exception ex)
{
_log.Log(ex.Message,"AOP",Interfaces.LogLevel.Error);
}
}
}
LoggerClass
- changed how it reads its configuration. In previous examples, we were able to pass parameters to the constructor. I struggled with this version passing the parameters as I did in the D_IOC
example but after thinking about it, realized that the best class for determining what parameters it needed from the config file was LoggerClass
itself.
public class Logger : ILogger
{
Interfaces.LogLevel _levelset;
string _Pathname;
string _Filename;
public Logger()
{
var logSetting = ConfigurationManager.AppSettings;
_levelset = (Interfaces.LogLevel)Enum.Parse(typeof(Interfaces.LogLevel),
logSetting["levelset"],true);
_Pathname = logSetting["pathname"];
_Filename = logSetting["filename"];
}
public void Log(string Message, string module, Interfaces.LogLevel level)
{ ... }
}
Codemap for this project shows how the objects are no longer a dependency for this project to build and the object can easily be swapped in and out as long as they implement the interface.
Caveats
Since the projects are no longer referenced by the core program (only interface and injector are) they do not automatically move to the application bin directory. This needs to be done for each project built.
I would change to post build event to always on each project.
You need to set the build order for IOC so that all projects are built first.
A New Addition
E_IOC - Ninject for All You Code Ninjas!
I have added a new project to this article which shows the use of Ninject rather than Structuremap
IOC. The changes are made to only the injector
class. No other changes have been made.
In structure map, the configuration was done this way:
_c = new Container(x =>
{
x.Scan(s =>
{
s.AssembliesFromApplicationBaseDirectory
(assm => assm.FullName.StartsWith("Object"));
s.AssembliesFromApplicationBaseDirectory
(assm => assm.FullName.StartsWith("Facade"));
s.AssembliesFromApplicationBaseDirectory
(assm => assm.FullName.StartsWith("Logger"));
s.WithDefaultConventions();
});
});
_c.Configure(a => a.ForSingletonOf<Interfaces.ILogger>());
_c.Configure(a => a.For<Interfaces.IFacadeClass>().DecorateAllWith<facadeAOP>());
In Ninject, the configuration is done this way:
_c =new StandardKernel();
_c.Bind(b => b.FromAssembliesMatching("Facade*.*")
.SelectAllClasses()
.BindDefaultInterfaces()
.Configure(c => { c.InSingletonScope(); c.Intercept().With<facadeAOP>(); }));
_c.Bind(b => b.FromAssembliesMatching("Object*.*").SelectAllClasses().BindDefaultInterfaces());
_c.Bind(b => b.FromAssembliesMatching("Logger*.*").SelectAllClasses().BindDefaultInterfaces());
Both of these have used convention over configuration to map the class to the interface. They both have the facade class defined as a singleton and both decorate / Intercept the facade
class with another class to wrap it in a try catch.
In Structuremap
, the facadeAOP
class is defined as a class of the same type we are decorating:
public class facadeAOP : Interfaces.IFacadeClass
{
Interfaces.IFacadeClass ic;
Interfaces.ILogger _log;
public facadeAOP(Interfaces.IFacadeClass fc,Interfaces.ILogger log)
{
ic = fc;
_log = log;
}
public string StringHelloWorld()
{
try
{
return ic.StringHelloWorld();
}
catch (Exception ex)
{
_log.Log(ex.Message, "AOP", Interfaces.LogLevel.Error);
return "Error occured - " + ex.Message;
}
}
}
For Ninject, we accomplish in a similar fashion, however we change the class slightly to implements Ninjects interface of IInterceptor
. Since we are using Ninjects Interface, we need to change appropriately.
public class facadeAOP : IInterceptor
{
Interfaces.ILogger _log;
public facadeAOP(Interfaces.ILogger log)
{
_log = log;
}
public void Intercept(IInvocation invocation)
{
try
{
invocation.Proceed();
}
catch (Exception ex)
{
_log.Log(ex.Message, "AOP", Interfaces.LogLevel.Error);
Console.WriteLine("Error occured - " + ex.Message);
}
}
}
The Ninject implementation can be used on all types of objects rather than structuremap
which needed to create one for each interface being decorated.
I also needed to change to copy dll files to the bin directory:
copy $(SolutionDir)Injector\$(OutDir)ninject*.* $(SolutionDir)IOC\bin\Debug
copy $(SolutionDir)Injector\$(OutDir)linfu*.* $(SolutionDir)IOC\bin\Debug
E_IOC - Autofac
In Autofac, the configuration is done this way:
public ContainerBootstrapper()
{
var builder = new ContainerBuilder();
var facade = Assembly.LoadFrom("FacadeClass.dll");
var logger = Assembly.LoadFrom("LoggerClass.dll");
var hello = Assembly.LoadFrom("ObjectHello.dll");
var world = Assembly.LoadFrom("ObjectWorld.dll");
builder.RegisterAssemblyTypes(facade).
As<Interfaces.IFacadeClass>().
EnableInterfaceInterceptors().
InterceptedBy(typeof(facadeAOP));
builder.RegisterAssemblyTypes(logger).As<Interfaces.ILogger>().SingleInstance();
builder.RegisterAssemblyTypes(hello).As<Interfaces.IHelloClass>();
builder.RegisterAssemblyTypes(world).As<Interfaces.IWorldClass>();
builder.Register(r => new facadeAOP(r.Resolve<Interfaces.ILogger>()));
_c = builder.Build();
}
Autofac also uses an interceptor in a similar fashion as Ninject.
Note that you use a builder to register the components then Build to return a container.
Unity - No Sample
So one container I didn't touch was Unity from Microsoft.
The main reason was because of the switch by Structuremap
to load using convention over configuration.
The Unity container uses XML configuration to map instances to concrete types.
The XML configuration is very similar to the 2.x version of structuremap
and is similar to below.
Unity uses:
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="IHelloClass" mapTo="ObjectHello.HelloClass"/>
<register type="IWorldClass" mapTo="ObjectWorld.WorldClass" />
<register type="IFacadeClass mapTo="FacadeClass" />
<register type="ILogger" mapTo="LoggerClass">
<lifetime type="singleton" />
</register>
</container>
</unity>
Points of Interest
In the 2.X version of Structuremap
, there was the ability to use XML configuration to read from the config file to configure itself. This has been removed as of version 3.0 and is not supported going forward.
I found building these projects in this way a nice way to learn, layering each one in such a way that your attention is focused on just the changes. Please drop a note if you find these helpful or if you have any questions.
The change to Ninject was simply understanding the differences between Ninject and Structuremap
but no other changes in any other part of the program was made.
History
- 1st February, 2016: Initial release
- 12th February, 2016: - Update with ninject
- 16th February, 2016: - Changed Ninject to correctly create the logger as a singleton. Added AutoFac solution
- 26th February, 2016: - Touch on Unity for IOC