Friday, December 18, 2009

Constructor Dependency Injection with a WCF Service using Unity

In a previous article I showed how to use dependency injection to specify in a config file which repository should be used by a non WCF service.

This article will show how to do the same but for a WCF service.

The previous article can be found here.

1. WCF uses interfaces so the first thing we need to do is add an interface to our service to WCF enable it.  Since the service is called MileageClaimService I will call the interface IMileageClaimService. Below is the interface definition decorated with WCF ServiceContract and OperationContract attributes.

namespace Service.Finance.Mileage.Interfaces
{
    [ServiceContract(
        Name = "MileageClaimService",
        Namespace = "http://mycompany.com/services/finance/v1.0.0.0")]
    public interface IMileageClaimService
    {
        [OperationContract(Name = "GetAllClaimsForEmployee")]
        List<DTO.Finance.Mileage.MileageClaim> GetAllClaimsForEmployee(string employeePayrollNumber);

        [OperationContract(Name = "GetClaimById")]
        DTO.Finance.Mileage.MileageClaim GetClaimById(int id);

        [OperationContract(Name = "GetClaimByReferenceNumber")]
        DTO.Finance.Mileage.MileageClaim GetClaimByReferenceNumber(string referenceNumber);

        [OperationContract(Name = "Save")]
        [FaultContract(typeof(DBConcurrencyFault))]
        void Save(ref DTO.Finance.Mileage.MileageClaim claim);

        [OperationContract(Name = "Delete")]
        void Delete(DTO.Finance.Mileage.MileageClaim claim);

        [OperationContract(Name = "SubmitClaim")]
        void SubmitClaim(DTO.Finance.Mileage.MileageClaim claim);
    }
}

Below I’ve added the interface and the ServiceBehavior attribute to the service.

namespace Service.Finance.Mileage
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class MileageClaimService : IMileageClaimService
    {
        IMileageClaimRepository _mileageClaimRepository;
       [InjectionConstructor] 
        public MileageClaimService(IMileageClaimRepository  mileageClaimRepository)
        { 
        }

        // Other service methods go here 
    }
}

2.  We need to create the Unity classes for WCF.
I’ve created a project called Common. In here I’m going to place the Unity WCF classes (there are 3 of them) in a directory called Services\Unity.  You can just copy the code to your own project as I did (thanks to dperez for helping me with this: http://blogs.southworks.net/dperez/2008/11/02/unity-friends-the-wcf-service-host-side/).

InjectionBehavior.cs

using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.Unity;

// WCF Unity Injection Behavior
namespace Common.Services.Unity
{
    /// <summary>
    /// Creates services instance by using Unity type resolution.
    /// </summary>
    public class InjectionBehavior : IServiceBehavior
    {
        /// <summary>
        /// Injection container.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// Initializes a new instance of the InjectionBehavior class.
        /// </summary>
        /// <param name="container">Injection container.</param>
        public InjectionBehavior(IUnityContainer container)
        {
            this.container = container;
        }

        /// <summary>
        /// Provides the ability to pass custom data to binding elements to support the contract implementation.
        /// </summary>
        /// <param name="serviceDescription">The service description of the service.</param>
        /// <param name="serviceHostBase">The host of the service.</param>
        /// <param name="endpoints">The service endpoints.</param>
        /// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
        public void AddBindingParameters(
            ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase,
            Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection bindingParameters)
        {
        }

        /// <summary>
        /// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="serviceHostBase"> The host that is currently being built.</param>
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (var channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                var channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                if (channelDispatcher != null)
                {
                    foreach (var endpointDispatcher in channelDispatcher.Endpoints)
                    {
                        endpointDispatcher.DispatchRuntime.InstanceProvider = new InjectionInstanceProvider(this.container, serviceDescription.ServiceType);
                    }
                }
            }
        }

        /// <summary>
        /// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="serviceHostBase">The service host that is currently being constructed.</param>
        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }
    }
}

InjectionElement.cs
using System;
using System.Configuration;
using System.ServiceModel.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

// WCF Unity Injection Element
namespace Common.Services.Unity
{
    /// <summary>
    /// Injection element class representing a behavior extension configuration tag.
    /// </summary>
    public class InjectionElement : BehaviorExtensionElement
    {
        /// <summary>
        /// Default Unity configuration section path.
        /// </summary>
        private const string DefaultUnityConfigurationSectionPath = "unity";

        /// <summary>
        /// Initializes a new instance of the InjectionElement class.
        /// </summary>
        public InjectionElement()
        {
            this.UnityConfigurationSectionPath = DefaultUnityConfigurationSectionPath;
        }

        /// <summary>
        /// Gets the type of behavior.
        /// </summary>
        /// <value>Behavior type.</value>
        public override Type BehaviorType
        {
            get
            {
                return typeof(InjectionBehavior);
            }
        }

        /// <summary>
        /// Gets or sets the injection container name.
        /// </summary>
        /// <value>Container name.</value>
        [ConfigurationProperty("containerName")]
        public string ContainerName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Unity configuration section path to read configuration from.
        /// </summary>
        /// <value>Configuration path.</value>
        [ConfigurationProperty("unityConfigurationSectionPath", DefaultValue = DefaultUnityConfigurationSectionPath)]
        public string UnityConfigurationSectionPath
        {
            get;
            set;
        }

        /// <summary>
        /// Creates a behavior extension based on the current configuration settings.
        /// </summary>
        /// <returns>The behavior extension.</returns>
        protected override object CreateBehavior()
        {
            var unitySection = ConfigurationManager.GetSection(this.UnityConfigurationSectionPath) as UnityConfigurationSection;
            IUnityContainer container = new UnityContainer();
            if (this.ContainerName == null)
            {
                unitySection.Containers.Default.Configure(container);
            }
            else
            {
                unitySection.Containers[this.ContainerName].Configure(container);
            }
            return new InjectionBehavior(container);
        }
    }
}

InjectionInstanceProvider.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.Unity;

// WCF Unity Injection Provider
namespace Common.Services.Unity
{
    /// <summary>
    /// Injection instance provider for new service instances.
    /// </summary>
    internal sealed class InjectionInstanceProvider : IInstanceProvider
    {
        /// <summary>
        /// Injection container.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// Type of the service implementation.
        /// </summary>
        private Type instanceType;

        /// <summary>
        /// Initializes a new instance of the InjectionInstanceProvider class.
        /// </summary>
        /// <param name="container">Injection container.</param>
        /// <param name="instanceType">Type of the service implementation.</param>
        public InjectionInstanceProvider(IUnityContainer container, Type instanceType)
        {
            this.container = container;
            this.instanceType = instanceType;
        }

        /// <summary>
        /// Returns a service object given the specified System.ServiceModel.InstanceContext object.
        /// </summary>
        /// <param name="instanceContext">The current System.ServiceModel.InstanceContext object.</param>
        /// <param name="message">The message that triggered the creation of a service object.</param>
        /// <returns>The service object.</returns>
        public object GetInstance(InstanceContext instanceContext, Message message)
        {
            return this.container.Resolve(this.instanceType);
        }

        /// <summary>
        /// Returns a service object given the specified System.ServiceModel.InstanceContext object.
        /// </summary>
        /// <param name="instanceContext">The current System.ServiceModel.InstanceContext object.</param>
        /// <returns>The service object.</returns>
        public object GetInstance(InstanceContext instanceContext)
        {
            return this.GetInstance(instanceContext, null);
        }

        /// <summary>
        /// Called when an System.ServiceModel.InstanceContext object recycles a service object.
        /// </summary>
        /// <param name="instanceContext">The service's instance context.</param>
        /// <param name="instance">The service object to be recycled.</param>
        public void ReleaseInstance(InstanceContext instanceContext, object instance)
        {
            this.container.Teardown(instance);
        }
    }
}

3. Now we need to update the app.config file for the service project.  Below shows the configuration to enable the service for Unity dependency injection (it’s exactly the same as in the previous post).

<configSections>
  <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>

<unity>
    <containers>
      <container>
        <types>
           <type type="DataAccess.Finance.Mileage.Interfaces.IMileageClaimRepository,
                            DataAccess.Finance.Mileage.Interfaces"
                    mapTo="EF.DataAccess.Finance.Mileage.Repositories.MileageClaimRepository,
                            EF.DataAccess.Finance.Mileage"/>
        </types>
      </container>
    </containers> 
</unity>

And below is the new configuration to enable the service for WCF using the Unity behavior.

<system.serviceModel> 
    <extensions>
      <behaviorExtensions>
        <add name="unity" type="Common.Services.Unity.InjectionElement, Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
    <bindings>
      <netTcpContextBinding>
        <binding name="netTcpContextBindingConfig" transactionFlow="true" />
      </netTcpContextBinding>
    </bindings>

    <services>
      <service behaviorConfiguration="Service.ServiceBehavior" name="Service.Finance.Mileage.MileageClaimService">
        <endpoint address="" binding="netTcpContextBinding" bindingConfiguration="netTcpContextBindingConfig"
          name="TcpBinding_MileageClaimService" contract="Service.Finance.Mileage.Interfaces.IMileageClaimService" />
        <endpoint address="mex" binding="mexTcpBinding" name="MexTcpBinding_MileageClaimService"
          contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:8734/Services.Finance/MileageClaimService/v1.0.0.0" />
          </baseAddresses>
        </host>
      </service>
    </services>

     <behaviors>
      <serviceBehaviors>
        <behavior name="Service.ServiceBehavior">
          <serviceMetadata httpGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="false" />
          <unity />
        </behavior>
      </serviceBehaviors>
    </behaviors> 
</system.serviceModel>

Note the behaviorExtensions section contains a Unity entry.  This points to the Unity InjectionElement class you added in the last section. 

Also note the <unity /> line in the serviceBehaviors. 

Both of these are critical to enabling the WCF service to use Unity dependency injection.

Thats it, you can use the wcf service as you would do any wcf service by adding a service reference to your client project and calling the methods on it.  The repository will be injected automatically into the constructor on the service because of the behavior extensions defined in the wcf configuration.

No comments:

Post a Comment