Wednesday, June 4, 2008

Integrating Unity with WCF

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.

  1. We have a configured Unity container, and want to create a ServiceHost that will use it.
  2. We have a configured Unity container, and an existing ServiceHost instance needs to use it.
  3. 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.

12 comments:

dreadjr said...

So how then can in the wcf service, for instance in a method can i use the UnityContainer.Resolve() method?

Ray Henry said...

You should try to avoid using the container directly in the WCF service.
Can you create the dependencies you need in the service and let the container resolve them automatically?
If not, then register the container with itself and create a dependency in your service on the Unity Container.

dreadjr said...

The problem i was running into i have requests coming in and it using the name to resolve the object by name. Also i was using the enterprise library log writer, and it was properly injecting the LogWriters in to sub classes, but the main service, the one i pass to the service host it was not.

Xav said...

Hi,
I know it's quite an old post but anyway i've tried to use it with Castle Windsor (There are few differences). I have a problem and i don't understand how it could even work with Unity: when you invoke Container.Resolve(ServiceType), you use the service's type and not an interface ... Windsor does not support this invocation.
So i'm going to modify the WindsorServiceBehavior class in order to register service's contracts and not the service's type itself.

Thxs for the post.

Xav said...

Me again ...
I think, there is another problem when using multiple service's contracts: you have to create an instance provider per contract ...

Anonymous said...

Hi Ray, I'm trying your code for Unity/WCF under IIS hosting. I've added a UnityServiceHostFactory, and referenced in .svc file:

public class UnityServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
UnityServiceHost serviceHost = new UnityServiceHost(serviceType, baseAddresses);
UnityContainer container = new UnityContainer();

serviceHost.Container = container;


//configure container
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

section.Containers["containerOne"].Configure(serviceHost.Container);

return serviceHost;
}

However, I'm getting a problem with injecting dependency into a property in my service. It doesn't execute the setter, and then if I add Dependency attribute, it generates the following error in the UnityInstanceProvider.GetInstance...:

Resolution of the dependency failed, type = "Bll.Service1", name = "". Exception message is: The current build operation (build key Build Key[Bll.Service1, null]) failed: The value for the property "DalService" could not be resolved. (Strategy type BuildPlanStrategy, index 3)

Anonymous said...

You mention that you shouldn't use the container within the WCF service. I have the situation where I want to intercept methods on objects created in service method. Thus I need to construct these resolve these object to the container some how. How should I hook these classes up if I can't use the container created with the service.

Peter Leslie Morris said...

If I have an IUserAccountService for example there will be multiple methods on this service, each having dependencies on different domain services.

Is there a way to specify different dependencies for each method (I can't think of one) or am I going to have to settle for Unity resolving lots of dependencies I am not going to use?

Anonymous said...

The method signature for AddBindingParameters in your source is incorrect - the endpoints paramater should be of type Collection

Anonymous said...

Ugg...

The method signature for AddBindingParameters in your source is incorrect - the endpoints paramater should be of type Collection<ServiceEndpoint>

Ray Henry said...

@peter: <ServiceEndpoint> is there; it's not HTML escaped however so the browser is eating it. My apologies.

R Pooran Prasad said...

can you share the working code?