Decouple WCF Services from their DI Container with Common Instance Factory

instance-factory-logo2In my last blog post I introduced the Common Instance Factory, which I built as an alternative to Common Service Locator to reduce coupling between an application and a Dependency Injection (DI) container.  Unlike the Common Service Locator (CSL), the Common Instance Factory (CIF) discourages the service location anti-pattern by using the abstract factory design pattern.

I would venture to say that in many cases, introducing this additional layer of abstraction is not always necessary.  If you are careful to register your types early in the startup of your application and avoid referencing the DI container from within your types (which is where the service location ant-pattern rears its ugly head), then selecting a DI container and sticking with it might be perfectly appropriate.  And I would certainly not use CIF for unit tests (or even some integration tests), where you need to leverage features of the DI container that are not exposed via the factory interface.

There are other situations, however, when wrapping the DI container with the CIF will give you the kind of decoupling and flexibility you want in your application architecture. This layer of abstraction can be especially advantageous, for example, when building ASP.NET apps or WCF services where performance is a critical factor. In this case, you might want to use a super-fast DI container, such as SimpleInjector, for the application while leveraging a full-featured DI container, just as Ninject, for unit testing.

The NuGet package, CommonInstanceFactory.Extensions.Wcf, provides the building blocks for hosting WCF services which are decoupled from a particular DI container. The first component is the InjectedInstanceProvider, which implements the WCF interface, IInstanceProvider, using ICommonInstanceFactory to retrieve and release instances from the DI container (see my prior blog post for more information on ICommonInstanceFactory).

public class InjectedInstanceProvider<TServiceType> : IInstanceProvider
    where TServiceType : class
{
    private readonly ICommonInstanceFactory<TServiceType> _container;

    public InjectedInstanceProvider(ICommonInstanceFactory<TServiceType> container)
    {
        _container = container;
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return GetInstance(instanceContext);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        TServiceType service = _container.GetInstance();
        return service;
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        var service = instance as TServiceType;
        if (service != null)
        {
            try
            {
                _container.ReleaseInstance(service);
            }
            finally
            {
                var disposable = instance as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
            }
        }
    }
}

If the underlying DI container does not implement ReleaseInstance, no harm no foul. Notice how ReleaseInstance checks to see if the service type implements IDisposable and, if so, calls Dispose on it. (This behavior happens to be missing from Ninject’s WCF extension, but CIF provides the correct implementation.)

Next, the CIF extension for WCF supplies an InjectedServiceBehavior, whose job it is to plug the InjectedInstanceProvider into the WCF pipeline.

public class InjectedServiceBehavior<TServiceType> : IServiceBehavior
    where TServiceType : class
{
    private readonly ICommonInstanceFactory<TServiceType> _container;

    public InjectedServiceBehavior(ICommonInstanceFactory<TServiceType> container)
    {
        _container = container;
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
        {
            var cd = cdb as ChannelDispatcher;
            if (cd != null)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.InstanceProvider
                        = new InjectedInstanceProvider<TServiceType>(_container);
                }
            }
        }
    }
}

Lastly, there is the InjectedServiceHostFactory abstract class, which container-specific adapters will need to implement to initialize a container and return a container-specific ServiceHost.

public abstract class InjectedServiceHostFactory<TContainer> : ServiceHostFactory
    where TContainer : class
{
    protected abstract TContainer CreateContainer();

    protected abstract ServiceHost CreateInjectedServiceHost
        (TContainer container, Type serviceType, Uri[] baseAddresses);

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        TContainer container = CreateContainer();
        ServiceHost serviceHost = CreateInjectedServiceHost
            (container, serviceType, baseAddresses);
        return serviceHost;
    }
}

Here is an example of a class that extends InjectedServiceHostFactory by implementing its abstract methods.  This class would most likely reside in a WCF host (such as a web project) or an assembly referenced by a host, because it needs to initialize the container by registering dependencies that are specific to the application – in this case via the GreetingModule.

public class NinjectServiceHostFactory : InjectedServiceHostFactory<IKernel>
{
    protected override IKernel CreateContainer()
    {
        IKernel container = new StandardKernel();
        container.Load<GreetingModule>();
        return container;
    }

    protected override ServiceHost CreateInjectedServiceHost
        (IKernel container, Type serviceType, Uri[] baseAddresses)
    {
        ServiceHost serviceHost = new NinjectServiceHost
            (container, serviceType, baseAddresses);
        return serviceHost;
    }
}

Here is how CommonInstanceFactory.Extensions.Wcf.Ninject extends ServiceHost with by providing NinjectServiceHost, which accepts a container to create a NinjectInstanceFactory and adds the InjectedServiceBehavior to the ServiceHost’s Description.

public class NinjectServiceHost<TServiceType> : ServiceHost
    where TServiceType : class
{
    public NinjectServiceHost(IKernel container, Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        ICommonInstanceFactory<TServiceType> instanceFactory
            = new NinjectInstanceFactory<TServiceType>(container);
        Description.Behaviors.Add(new InjectedServiceBehavior<TServiceType>(instanceFactory));
    }
}

Lastly, here is an example of a Service.svc file which references the container-specific NinjectServiceHostFactory.  (The non-generic version of NinjectServiceHost uses a little reflection magic to instantiate the generic NinjectInstanceFactory<TServiceType>.)

<%@ ServiceHost Factory="CommonInstanceFactory.Sample.Hosting.Web.ServiceHostFactories.NinjectServiceHostFactory" 
                Service="CommonInstanceFactory.Sample.Services.GreetingService" %>

When you want to change from one DI container to another, all you need to do is replace the Factory attribute with the ServiceHostFactory of the new container.  For example, here is a Service.svc which references the SimpleInjectorServiceHostFactory.

<%@ ServiceHost Factory="CommonInstanceFactory.Sample.Hosting.Web.ServiceHostFactories.SimpleInjectorServiceHostFactory" 
                Service="CommonInstanceFactory.Sample.Services.GreetingService" %>

If you have a non-web host, such as a Windows Service, you won’t need to worry about wiring up a container-specific ServiceHostFactory at all.  Instead, you can simply initialize the container yourself and pass it directly to the constructor of the container-specific ServiceHost.

ServiceHost serviceHost;
var serviceBaseAddress = new Uri("http://localhost:8000/GreetingService");
switch (containerType)
{
    case ContainerType.Ninject:
        serviceHost = new NinjectServiceHost<GreetingService>
            (CreateNinjectContainer(), typeof(GreetingService), serviceBaseAddress);
        break;
    case ContainerType.SimpleInjector:
        serviceHost = new SimpleInjectorServiceHost<GreetingService>
            (CreateSimpleInjectorContainer(), typeof(GreetingService), serviceBaseAddress);
        break;
}

Using the Common Instance Factory, switching DI containers is relatively painless, and you’ll get a layer of abstraction from the DI container that will help decouple your WCF services from any particular container.  To get CIF, download the NuGet CIF packages. To see examples of using CIF with WCF extensions, download samples and source code from the CIF CodePlex site. Enjoy.

About Tony Sneed

Married with three children.
This entry was posted in Technical and tagged , . Bookmark the permalink.

4 Responses to Decouple WCF Services from their DI Container with Common Instance Factory

  1. John says:

    Have you had any luck with using both the ninject.extensions.xml extension and the ninject.extensions.wcf extension?

    • John says:

      I guess I should say using them together

      • Tony Sneed says:

        Not sure what the benefit would be of using those together. I have to say I’ve been disappointed with the Ninject WCF extension, mainly because it does not dispose of instances when they are released, and also because self-hosting uses a singleton instance mode, which does not scale. The WCF extension for the Common Instance Factory addresses both these issues, so there is no need to use Ninject’s WCF extension.

  2. Pingback: Decouple WCF Services from their DI Container with Common Instance Factory - .NET Code Geeks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s