Update (Feb 14, 2012): I fixed an issue in the code.
You might think Unity is great and all, but how can you use it with your WCF services? You might have a service that depends on a data access object, a security object and a logger. How do you get references to these objects into your services when WCF is instanciating your service for you?
You could create static methods that locate and configure these services, like so:
MyLogger logger = MyLogger.getLogger();
MyDAO dao = MyDAO.getDao();
MySecurity security = MySecurity.getSecurityObject();
We all know why that's bad (tightly coupled). We can't change the implementation of any of these objects, or do unit testing with mocks, without changing the code.
With Unity, or any other DI container, we can configure the container and let it resolve the dependencies. Any object that is needs a reference to another object can have it injected through the constructor. All automatically. So how do we force WCF to use the Unity container to create a fully functional service instance for us with all the dependencies resolved? The key is through the magic of an InstanceProvider.
Before I reveal how this is done, I want to show three use cases that we want to be able to solve, and allude to a fourth. These three use cases will be for self-hosted WCF services.
- We have a configured Unity container, and want to create a ServiceHost that will use it.
- We have a configured Unity container, and an existing ServiceHost instance needs to use it.
- We have an existing ServiceHost instance and want to configure it with a new Unity configuration.
Here is what these use cases will look like in code:
Case 1
// Create new ServiceHost
UnityServiceHost host = new UnityServiceHost(typeof(MyService));
host.AddServiceEndpoint(typeof(IService), binding, address);
// Set existing UnityContainer
host.Container = unity;
host.Open();
Case 2
// Create new ServiceBehavior
UnityServiceBehavior behavior = new UnityServiceBehavior();
// Set existing UnityContainer
behavior.InstanceProvider.Container = unity;
// Add behavior to ServiceHost
behavior.AddToHost(host);
host.Open();
Case 3
// let behavior create a new container
UnityServiceBehavior = new UnityServiceBehavior();
// configure new container
behavior.InstanceProvider.Container.RegisterInstance(logger);
behavior.InstanceProvider.Container.RegisterInstance(dao);
behavior.InstanceProvider.Container.RegisterType(typeof(MySecurity));
// Add behavior to ServiceHost
behavior.AddToHost(host);
host.Open();
The fourth use case is running as an IIS-hosted service, which I will leave for a future post.
Creating an InstanceProvider
When WCF needs to create an instance of a service it invokes an instance provider. The default will create the appropriate type using the default constructor, but we can add a ServiceBehavior that will substitute our custom InstanceProvider. Our provider will be configured with a Unity container and call Resolve() when asked for a new Instance.
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.Unity;
namespace UnityWCF
{
public class UnityInstanceProvider : IInstanceProvider
{
public UnityContainer Container { set; get; }
public Type ServiceType { set; get; }
public UnityInstanceProvider() : this(null) {
}
public UnityInstanceProvider(Type type)
{
ServiceType = type;
Container = new UnityContainer();
}
#region IInstanceProvider Members
public object GetInstance(
InstanceContext instanceContext, Message message)
{
return Container.Resolve(ServiceType);
}
public object GetInstance(
InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public void ReleaseInstance(
InstanceContext instanceContext, object instance)
{
}
#endregion
}
}
Create the Service Behavior
The Service Behavior's ApplyDispatchBehavior method will be called before the service is instanciated. This is the point where we create and configure our InstanceProvider. The AddToHost method is used to add the behavior to the ServiceHost, ensuring that it is only added once.
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.Unity;
using System.Collections.ObjectModel;
namespace UnityWCF
{
public class UnityServiceBehavior : IServiceBehavior
{
public UnityInstanceProvider InstanceProvider
{ get; set; }
private ServiceHost serviceHost = null;
public UnityServiceBehavior()
{
InstanceProvider = new UnityInstanceProvider();
}
public UnityServiceBehavior(UnityContainer unity)
{
InstanceProvider = new UnityInstanceProvider();
InstanceProvider.Container = unity;
}
public void ApplyDispatchBehavior(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb
in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd
= cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed
in cd.Endpoints)
{
InstanceProvider.ServiceType
= serviceDescription.ServiceType;
ed.DispatchRuntime.InstanceProvider
= InstanceProvider;
}
}
}
}
public void AddBindingParameters(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters) { }
public void Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase) { }
public void AddToHost(ServiceHost host)
{
// only add to host once
if (serviceHost != null) return;
host.Description.Behaviors.Add(this);
serviceHost = host;
}
}
}
Creating the ServiceHost
The UnityServiceHost will encapsulate a UnityContainer and configure itself with the appropriate InstanceProvider and ServiceBehavior, so that the container will be used to Resolve instances when needed.
using System;
using System.ServiceModel;
using Microsoft.Practices.Unity;
namespace UnityWCF
{
public class UnityServiceHost : ServiceHost
{
public UnityContainer Container { set; get; }
public UnityServiceHost()
: base()
{
Container = new UnityContainer();
}
public UnityServiceHost(
Type serviceType,
params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
Container = new UnityContainer();
}
protected override void OnOpening()
{
new UnityServiceBehavior(Container)
.AddToHost(this);
base.OnOpening();
}
}
}
I have to credit Oran Dennison who showed how to integrate WCF and Spring.NET.