Feel Good.

20 января 2011

IoC/DI в WCF на примере Unity 2.0

В этой статье я продемонстрирую процесс внедрения IoC/DI контейнера (на примере Unity 2.0) в WCF сервис. В примере я выбрал Unity 2.0, но на самом деле это не существенно, и по желанию можно легко адаптировать код под другой IoC/DI контейнер. Для тех кто не знаком еще с данным контейнером, рекомендую глянуть вводную статью. Существуют несколько путей внедрения IoC/DI контейнера в WCF сервис, рассмотрим один из них*.
Итак, предже чем приступить к реализации наметим основные шаги:
  1. Для начала создадим простой WCF сервис
  2. Реализуем свой IInstanceProvider
  3. Далее опишем собственную реализацию IServiceBehavior
  4. Определим BehaviorExtensionElement
  5. И закончим, добавив необходимые настройки в web.config
*На самом деле это не единственный способ внедрения контейнера, существуют варианты с использованием ServiceHostBase и ServiceHostFactoryBase

Шаг 1.Создадим новый WCF проект, назовем его WcfService, с контрактом в виде интерфейса:

[ServiceContract]

public interface IHelloService

{

    [OperationContract]

    string Say(string name);

}



Реализуем данный интерфейс в виде класса, с осуществлением инъекции (injection) в виде типа IEnveloper в конструктор:

public class HelloService : IHelloService

{

    IEnveloper _enveloper;

 

    // Инъекция типа IEnveloper в конструктор

    public HelloService(IEnveloper enveloper)

    {

        _enveloper = enveloper;

    }

 

    public string Say(string name)

    {

        string data = string.Format("Hello, {0}", name);

        return _enveloper.Envelop(data);

    }

}



Шаг 2.За создание экземпляра IHelloService отвечает IInstanceProvider, но стандартный IInstanceProvider не умеет делать инъекции, поэтому необходимо реализовать свой, для этого добавляем следующий класс:

public class UnityInstanceProvider : IInstanceProvider

{

    IUnityContainer _container;

    Type _serviceType;

 

    public UnityInstanceProvider(IUnityContainer container, Type serviceType)

    {

        _serviceType = serviceType;

        _container = container;

    }

 

    public object GetInstance(InstanceContext instanceContext, Message message)

    {

        // Я не использую здесь информацию message, хотя можно

        // разрешать типы на основе полей (или заголовков)

        // содержащиеся в message.           

        return GetInstance(instanceContext);

    }

 

    public object GetInstance(InstanceContext instanceContext)

    {

        // Здесь происходит разрешение типа, со всеми инъекциями.

        return _container.Resolve(_serviceType);

    }

 

    public void ReleaseInstance(InstanceContext instanceContext, object instance)

    {

        // Так как тип породил контейнер, он же его и разрушает.

        _container.Teardown(instance);

    }

}


Шаг 3.UnityInstanceProvider готов, но стандартное поведение(behavior) использует страндартный InstanceProvider, и для того чтобы сообщить среде, что надо использовать наш UnityInstanceProvider нужно реализовать свое поведение (behavior):

public class UnityServiceBehavior : IServiceBehavior

{

    IUnityContainer _container;

 

    public UnityServiceBehavior(IUnityContainer container)

    {

        _container = container;

    }

 

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)

    {

    }

 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

    {

        foreach (var cdb in serviceHostBase.ChannelDispatchers)

        {

            ChannelDispatcher cd = cdb as ChannelDispatcher;

            if (cd != null)

            {

                foreach (var ed in cd.Endpoints)

                {

                    // Тип нашего сервиса

                    Type serviceType = serviceDescription.ServiceType;

 

                    // Подготовим провайдер.

                    // Хотя провайдер сам бы мог строить контейнер в себе,

                    // но лучше иметь один контейнер на всех.

                    IInstanceProvider provider = new UnityInstanceProvider(_container, serviceType);

 

                    // Укажем среде использовать наш провайдер.

                    ed.DispatchRuntime.InstanceProvider = provider;

                }

            }

        }

 

    }

 

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

    {

    }

}


Шаг 4.Осталось наделить наш новый WCF сервис нашим новым поведением UnityServiceBehavior. Это можно сделать как и в коде, так и через web.config (что удобнее). Так как UnityServiceBehavior является по сути расширением, и чтобы опледелить его в настройках (web.config) необходимо определить для него элемент конфигурации:

public class UnityServiceBehaviorExtensionElement : BehaviorExtensionElement

{

    public override Type BehaviorType

    {

        get { return typeof(UnityServiceBehavior); }

    }

 

    protected override object CreateBehavior()

    {

        IUnityContainer container = new UnityContainer();

 

        // Настраивать конечно лучше в конфигурационном

        // файле, но так будет быстрее и надежнее.

        container.RegisterType<IEnveloper, XmlEnveloper>();

 

        return new UnityServiceBehavior(container);

    }

}


где XmlEnveloper одна из возможных реализаций IEnveloper:

public class XmlEnveloper : IEnveloper

{

    public string Envelop(string data)

    {

        return string.Format("<result>{0}</result>", data);

    }

}


Шаг 4.Наделяем наш WCF сервис новым поведением, делается это просто в web.config.
Регистрируем расширение в секции extensions:

<extensions>

  <behaviorExtensions>

    <add name="serviceInjection" type="WcfService.UnityServiceBehaviorExtensionElement, WcfService"/>

  </behaviorExtensions>

</extensions>


Добавляем поведение serviceInjection в секцию behavior:

<behaviors>

  <serviceBehaviors>

    <behavior>

      .

      .

      <serviceInjection/>

      .

      .

    </behavior>

  </serviceBehaviors>

</behaviors>




Сервис готов!

1 комментарий:

  1. Интересная статья, когда то очень нужно было в диплом прикрутить инверсию в WCF, но так и не нашел подходящего решения, теперь буду знать, СПАСИБО!

    ОтветитьУдалить