Inversion of Control with WCF and Unity
Lately, I often talks with friends and colleagues about Inversion of Control (IOC) and WCF. Then, I’ve decided to publish this post and explain the necessary steps to get IoC in WCF. For this example, let’s get to use Unity ad framework for IoC.
First, the necessary components are:
- InstanceProvider: it is the component that creates the service instance. It uses the UnityContainer;
- ServiceBehavior: creates the InstanceProvider and allows to use it in the current context;
- ServiceHost: handles the environment of our service. The ServiceHost configures the service for use and then it allows to apply the created ServiceBehavior instance;
We then get: UnityInstanceProvider, UnityServiceBehavior and UnityServiceHost.
UnityInstanceProvider
The first step is to create the InstanceProvider. The code is really simple:
1: internal class UnityInstanceProvider : IInstanceProvider2: {3: private readonly IUnityContainer container;4: private readonly Type contractType;5:6: public UnityInstanceProvider(IUnityContainer container, Type contractType)
7: {8: this.container = container;
9: this.contractType = contractType;
10: }11:12: public object GetInstance(InstanceContext instanceContext)13: {14: return GetInstance(instanceContext, null);15: }16:17: public object GetInstance(InstanceContext instanceContext, Message message)18: {19: return container.Resolve(contractType);
20: }21:22: public void ReleaseInstance(InstanceContext instanceContext, object instance)23: {24: container.Teardown(instance);25: }26: }
The first constructor’s parameters is the instance of the UnityContainer and the second is the specific endpoint contract. Both are used by the GetInstance to resolve the service’s instance. Naturally, the method could be better.
UnityServiceBehavior
The ServiceBehavior, as mentioned above, creates the InstanceProvider for the specifiv service endpoint:
1: public class UnityServiceBehavior : IServiceBehavior2: {3: private readonly IUnityContainer container;4:5: public UnityServiceBehavior(IUnityContainer container)
6: {7: this.container = container;
8: }9:10: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)11: {12: }13:14: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)15: {16: }17:18: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)19: {20: foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)21: {22: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)23: {24: if (endpointDispatcher.ContractName != "IMetadataExchange")25: {26: string contractName = endpointDispatcher.ContractName;
27: ServiceEndpoint serviceEndpoint = serviceDescription.Endpoints.FirstOrDefault(e => e.Contract.Name == contractName);28: endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(this.container, serviceEndpoint.Contract.ContractType);29: }30: }31: }32: }33: }
The constructor gets from ServiceHost the container’s instance. Then the ApplyDispatchBehavior assigns the instance to the UnityInstanceProvider with the contracts used to resolve the service instance. Finally the UnityInstanceProvider is assigned to InstanceProvider property of DispatchRuntime for each EndpointDispatcher.
UnityServiceHost
The finaly step is to create the ServiceHost:
1: public class UnityServiceHost : ServiceHost2: {3: private IUnityContainer unityContainer;
4:5: public UnityServiceHost(IUnityContainer unityContainer, Type serviceType) : base(serviceType)6: {7: this.unityContainer = unityContainer;
8: }9:10: protected override void OnOpening()11: {12: base.OnOpening();
13:14: if (this.Description.Behaviors.Find<UnityServiceBehavior>() == null)15: {16: this.Description.Behaviors.Add(new UnityServiceBehavior(this.unityContainer));17: }18: }19: }
The code is really simple. The ServiceHost's constructor gets the container and the service type as parameters. In the OnOpening method override the ServiceHost checks if the UnityServiceBehavior is already added to Service’s behaviors. If not, it creates the instance and pass the container’s instance to the contructor.
Utilizzo in una applicazione console self-hosted
Now we are ready. This is the sample configuration we'll use in our service:
1: <?xml version="1.0" encoding="utf-8" ?>2: <configuration>3: <configSections>4: <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />5: </configSections>6:7: <unity>8:9: <containers>10:11: <container>12: <types>13:14: <type type="DotNetSide.WCF.IoC.Console.InnerBookService, DotNetSide.WCF.IoC.Console"15: name="inner"/>16:17: <type type="DotNetSide.WCF.IoC.Console.IBookService, DotNetSide.WCF.IoC.Console"18: mapTo="DotNetSide.WCF.IoC.Console.BookService, DotNetSide.WCF.IoC.Console">19: <typeConfig>20: <constructor>21: <param name="innerService" parameterType="DotNetSide.WCF.IoC.Console.InnerBookService, DotNetSide.WCF.IoC.Console">22: <dependency name="inner" />23: </param>24: </constructor>25: </typeConfig>26: </type>27:28: </types>29: </container>30:31: </containers>32:33: </unity>34:35: </configuration>
Simply must be created an InnerBookService instance and then get it into the BookService’s constructor, an IBookService contract implementation:
1: [ServiceContract]2: internal interface IBookService3: {4: [OperationContract]5: Book ReadBook(string bookId);
6: }7:8: class BookService : IBookService
9: {10: private readonly InnerBookService innerService;11:12: public BookService(InnerBookService innerService)
13: {14: this.innerService = innerService;
15: }16:17: public Book ReadBook(string bookId)18: {19: return this.innerService.ReadBook(bookId);20: }21: }22:23: class InnerBookService
24: {25: public Book ReadBook(string bookId)26: {27: return new Book()28: {29: Title = "WCF",
30: Description = "A book on Windows Communication Foundation"
31: };32: }33: }
Finally we build the ServiceHost with the instance of the UnityContainer:
1: var unityContainer = new UnityContainer();
2: var configurationSection = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
3: configurationSection.Containers.Default.Configure(unityContainer);4:5: Uri serviceAddress = new Uri("http://localhost:9001/BookService");6: Uri mexAddress = new Uri("http://localhost:9001/BookService/mex");7: using (UnityServiceHost host = new UnityServiceHost(unityContainer, unityContainer.Resolve<IBookService>().GetType()))8: {9: host.Description.Behaviors.Add(new ServiceMetadataBehavior());
10: host.AddServiceEndpoint(typeof(IBookService), new BasicHttpBinding(), serviceAddress);11: host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), mexAddress);
12:13: host.Open();14:15: System.Console.Read();16:17: host.Close();18: }
We also add a WS-MetadataExchange endpoint. In this way we can create our client and try to call the service.
Actually, the UnityServiceHost is not necessary but helps us to encapsulate the behavior creation. We can also write:
1: using (ServiceHost host = new ServiceHost(unityContainer.Resolve<IBookService>().GetType()))2: {3: host.Description.Behaviors.Add(new UnityServiceBehavior(unityContainer));
4: // ...
5: }6:
Feedback?
bye