Ninject is a very simple and in the same time powerful IoC container. I used it in few projects and had very little or no problems.
Most of the time, getting instances from Ninject kernel requires simple line with binding expression, or even this is sometimes unnecessary if type is self bindable (i.e., if it is a concrete class with parameterless constructor).
Little harder is getting Ninject to work with WCF. You cannot just bind interfaces types because proxies which implement them are created through .NET mechanism. Luckily, WCF system is very flexible and mostly can be changed/extended with custom functionality.
How we can do that? The best solution is to add new behavior for our WCF services. Behavior
is a class that implements IServiceBehavior
interface. ApplyDispatchBehavior
method accessible through that interface allows our code to change instance provider of our service. Instance provider on the other hand is object with IInstanceProvider
interface implementation and GetInstance
method. This method is defined in the following way:
object GetInstance(InstanceContext instanceContext);
object GetInstance(InstanceContext instanceContext, Message message);
Inside one of them, we can create instance of our service from Ninject container.
Let us start from the top, with behavior
class. It can be applied to service from attribute.
public class NinjectBehaviorAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase,
Collection endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
Type serviceType = serviceDescription.ServiceType;
IInstanceProvider instanceProvider =
new NinjectInstanceProvider(NinjectServiceLocator.Kernel, serviceType);
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
{
DispatchRuntime dispatchRuntime = endpointDispatcher.DispatchRuntime;
dispatchRuntime.InstanceProvider = instanceProvider;
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
All interesting things happens inside of ApplyDispatchBehavior
method. First NinjectInstanceProvider
class is created to which instance of Ninject kernel and our desired service type information is passed. Instance provider is defined as follows:
public class NinjectInstanceProvider : IInstanceProvider
{
private Type serviceType;
private IKernel kernel;
public NinjectInstanceProvider(IKernel kernel, Type serviceType)
{
this.kernel = kernel;
this.serviceType = serviceType;
}
public object GetInstance(InstanceContext instanceContext)
{
return this.GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return kernel.Get(this.serviceType);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
Inside second overload of GetInstance
method is created actual service instance, through Ninject kernel. Ninject kernel is acquired from simple implementation of service locator. It's just a static
class with public
read only property with Ninject kernel.
public static class NinjectServiceLocator
{
public static IKernel Kernel { get; private set; }
public static void SetServiceLocator(IKernel kernel)
{
Kernel = kernel;
}
}
Instance of kernel is injected into property with SetServiceLocator
method after initialization, preferably inside NinjectWebCommon
class, which is created in App_Start
directory after adding Ninject to project from NuGet.
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
NinjectServiceLocator.SetServiceLocator(kernel);
return kernel;
}
I decided to go with this solution instead of actual implementation of Microsoft ServiceLocator
class to keep it simple, which in fact is working in a similar way.
After creating instance provider object, we apply it to all endpoints inside ApplyDispatchBehavior
.
Last thing is to actually register service types inside Ninject. Typically, we are creating all data necessary to make service proxy inside web.config file. WCF channel can be created from such configuration with ChannelFactory class. Let's implement this functionality inside Ninject Provider<> class.
public class ConfigServiceProvider : Provider
{
protected override TService CreateInstance(IContext context)
{
var @interface = typeof(TService);
var interfaceTypeName = @interface.FullName;
var endpointsConfig = (ClientSection)ConfigurationManager.GetSection
("system.serviceModel/client");
string address = null;
foreach (ChannelEndpointElement endpoint in endpointsConfig.Endpoints)
{
if (endpoint.Contract == interfaceTypeName)
{
address = endpoint.Address.OriginalString;
break;
}
}
var factory = new ChannelFactory<TService>(new WSHttpBinding(), address);
return factory.CreateChannel();
}
}
First provider is accessing configuration of all services, then searching inside of configuration for matching interface type name. If it finds one, address of WCF service endpoint is passed to ChannelFactory class which will create service proxy. With such provider, we can do actual type binding inside Ninject module:
public class WcfModule : NinjectModule
{
public override void Load() { }
public IBindingWhenInNamedWithOrOnSyntax BindServiceFromConfig()
{
return Bind().ToProvider<ConfigServiceProvider>();
}
}
public class ServicesModule : WcfModule
{
public override void Load()
{
BindServiceFromConfig();
}
}
WcfModule
class can be placed inside some library so we can use it in more than one project. I am sure that not only one of them is using WCF services . ServicesModule
on the other hand should be placed inside assembly with services interfaces and loaded from NinjectWebCommon
class inside WCF project.
And that is all. WCF web application registers services interfaces inside Ninject, creates kernel and setting its instance inside our custom ServiceLocator
class. After that, when service instance is accessed from .NET framework, NinjectBehaviorAttribute
does its magic and acquires instance of NinjectInstanceProvider
class, which is asking for instance of specified service from kernel. Kernel from its binding creates ConfigServiceProvider
through one is created actual instance of proxy, thanks to our configuration.
CodeProject