I am a fan of Dependency Injection and try to apply it on all of my works from ASP.NET MVC, WPF and now WCF.
I researched on the internet and find some articles which told me to create an InstanceProvider for that purpose, the best article I can find is Using Instance Provider and ServiceHostFactory to Construct the Service on MSDN.
However, the author of the article stated that the solution doesn't work with the services which have InstanceContextMode set to Single, verified, that's true. Moreover, I don't like creating a custom ServiceHostFactory, I consider it as the last solution when WCF's extension points not enough for my need or there are many many configuration that need to be set on all the services of service host application. I feel that creating a Custom ServiceHostFactory is not flexible and it looks like "Using a cannon to shoot a bird". Still prefer WCF's Extension points and add them in config file.
Finally, I came up with a solution after reading Series of posts of Carlos Figueira. Instead of using InstanceProvider, I tried another Extension point, InstanceContextInitializer.
When WCF call stack reaches the InstanceContextInitializer, I registered an event handler to InstanceContext.Opened event, inside that handler I let the IoC container resolve the service instance's dependencies.
I have created a Nuget package and published it to nuget.org with little more implementation, check it out.
The following describes how I implemented the Dependency Injection for WCF.
InstanceContextInitializer
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using Autofac.Core;
using Autofac;
namespace WcfDependencyInjection
{
///
/// DI InstanceContext initializer for resolving dependencies of Service instance
///
public class DiInstanceContextInitializer : IInstanceContextInitializer
{
public static Container Container { get; set; }
///
/// Initialize InstanceContext
///
///
///
public void Initialize(InstanceContext instanceContext, Message message)
{
if (Container != null)
{
// Ensure handlers' removal
instanceContext.Opened -= InstanceContextOpendedHandler;
// Add event handlers for Opened events
instanceContext.Opened += InstanceContextOpendedHandler;
}
}
///
/// Handler for injecting dependencies when Service instance is ready to be used
///
///
///
private void InstanceContextOpendedHandler(object sender, EventArgs e)
{
InstanceContext context;
if ((context = sender as InstanceContext) == null) return;
var serviceInstance = context.GetServiceInstance();
Container.InjectUnsetProperties(serviceInstance);
}
}
}
The above implementation is using Autofac to resolve the service instance's dependency. The reason that I have to use Opened event is that the service instance is not ready at the moment when WCF stack reaching the initializer, if you try to call instanceContext.GetServiceInstance() you will get an error saying that the service instance cannot be used until it is opened.
Applying the InstanceContextInitializer to the service
In order to use the initializer you need to use a ServiceBehavior to add an initializer instance into DispatchRuntime.InstanceContextInitializers collection. Below is my implementation of the ServiceBehavior
using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using WcfDependencyInjection;
namespace WcfDependencyInjection
{
public class DiServiceBehavior : Attribute, IServiceBehavior
{
public virtual void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public virtual void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{
}
public virtual void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
// Apply DI InstanceContext initializer on endpoints
foreach (var dispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>())
{
foreach (var epDispatcher in dispatcher.Endpoints)
{
// Apply the initializer
var diInstanceContextInitializer = new DiInstanceContextInitializer();
epDispatcher.DispatchRuntime
.InstanceContextInitializers
.Add(diInstanceContextInitializer);
}
}
}
}
}
Using the ServiceBehavior as Attribute
Now, it's time to try it.
Firstly, I created a WCF Service Application (Web app hosted by IIS)
Next, create a String Provider
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WcfService1.Infrastructure
{
public interface IStringProvider
{
string GetDataString(int input);
}
public class StringProvider : IStringProvider
{
public string GetDataString(int input)
{
return string.Format("Your input : {0}", input);
}
}
}
Create Service interface (Service contract)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace WcfService1
{
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
}
Implement Service
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using WcfDependencyInjection;
using WcfService1.Infrastructure;
namespace WcfService1
{
[DiServiceBehavior]
public class Service1 : IService1
{
public IStringProvider Provider { get; set; }
public string GetData(int value)
{
return Provider.GetDataString(value);
}
}
}
Then, add global.asax and add code the Application_Start for setting the Autofac container for InstanceContextInitializer
...
protected void Application_Start(object sender, EventArgs e)
{
var builder = new ContainerBuilder();
builder.RegisterType<StringProvider>().As<IStringProvider>().SingleInstance();
DiInstanceContextInitializer.Container = builder.Build();
}
...
Finally, test your service.
Apply the DiServiceBehavior in .config file
As stated at the beginning, I prefer applying dependency inject in config files. In order to do that just need to add a BehaviorExtensionElement
using System;
using System.Configuration;
using System.ServiceModel.Configuration;
using WcfDependencyInjection;
namespace WcfDependencyInjection
{
///
/// Extension Element for configuring WCF DI
///
public class DiServiceBehaviorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof (DiServiceBehavior); }
}
protected override object CreateBehavior()
{
return new DiServiceBehavior();
}
}
}
And then, add the following config into config file