Saturday, July 12, 2008

Ninject revisited

In a previous post, I looked at Ninject, and based on a cursory look at the documentation, dismissed it as requiring me to decorate my component classes with attributes in order to use multi-parameter constructor injection. Thanks to a comment by ninject's author, Nate Kohari, I found that with a slight configuration change, we can duplicate the behavior of Unity.

The proof's in the pudding. Let's see if we can wire up the same components with both Unity and Ninject without having to make any changes to our component classes.

Here are some interfaces:

public interface IDataService
{
}

public interface ISecurityService
{
}

public interface ILogger
{
}


And here are some components that use them. The Business Logic requires all three, the Security service needs a DataService and Logger, and the DataService needs a Logger.



public class BusinessLogic
{
protected IDataService DataService { set; get; }
protected ILogger Logger { set; get; }
protected ISecurityService SecurityService { set; get; }

public BusinessLogic()
{
}

public BusinessLogic(ILogger logger, IDataService dataService, ISecurityService securityService)
:this()
{
this.Logger = logger;
this.DataService = dataService;
this.SecurityService = securityService;
}


public override string ToString()
{
return base.ToString() + '\n' +
Logger.ToString() + '\n' +
DataService.ToString() + '\n' +
SecurityService.ToString() + '\n'
;
}
}


public class DataService : IDataService
{
protected ILogger Logger { set; get; }

public DataService()
{
}

public DataService(ILogger logger)
:this()
{
this.Logger = logger;
}
}

public class SecurityService : ISecurityService
{

protected IDataService DataService { set; get; }
protected ILogger Logger { set; get; }

public SecurityService()
{
}

public SecurityService(ILogger logger, IDataService dataService)
: this()
{
this.Logger = logger;
this.DataService = dataService;
}
}

public class Logger : ILogger
{
}


If we don't want to use an auto-wring container, we need to know all the dependecies and create the Business object like so:



static BusinessLogic NormalWay()
{
var logger = new Logger();
var dataService = new DataService(logger);
var securityService = new SecurityService(logger, dataService);
var bizLogic = new BusinessLogic(logger, dataService, securityService);
return bizLogic;
}



How do we configure and create a BusinessLogic object with Unity? Like so:



static BusinessLogic UnityWay(UnityContainer container)
{
UnityContainer container = new UnityContainer();
container
.RegisterType<ILogger, Logger>()
.RegisterType<IDataService, DataService>()
.RegisterType<ISecurityService, SecurityService>()
;
var bizLogic = container.Resolve<BusinessLogic>();
return bizLogic;
}
}


Likewise, we can create a method to create a BusinessLogic object using Ninject, all auto-wired are ready to go. This however, requires a configuration class (MyModule):



static BusinessLogic NinjectWay()
{
var kernel = new StandardKernel(new MyModule(), new AutoWiringModule());
var bizLogic = kernel.Get<BusinessLogic>();
return bizLogic;

}
private class MyModule : StandardModule
{
public override void Load()
{
Bind<ILogger>().To<Logger>();
Bind<IDataService>().To<DataService>();
Bind<ISecurityService>().To<SecurityService>();
}
}



We can run the following to show that the objects get create and populate the services:



static void Main(string[] args)
{
BusinessLogic bz;

bz = NormalWay();
System.Console.WriteLine(bz);

bz = UnityWay();
System.Console.WriteLine(bz);

bz = NinjectWay();
System.Console.WriteLine(bz);

}

So the end result is that we can design our components and use constructor-injection. So long as we keep container-dependencies out of our business components, an app can be assembled using the container of your choice.

No comments: