Внедрение WCF-клиентов c помощью Castle.DynamicProxy

от автора

Многим разработчикам время от времени приходится взаимодействовать с различными SOAP веб-сервисами. Рассмотрим создание класса, способного обращаться к таким веб-сервисам, и постараемся при этом избежать лишних зависимостей от компонентов WCF и сделать взаимодействие максимально удобным. Имея WSDL веб-сервиса, с которым мы хотим работать, первым делом сгенерируем интерфейс для обращения к нему с помощью утилиты svcutil. Результат будет примерно следующим:

[ServiceContract] public interface ISomeService  {        [OperationContract]     Response GetData(Request request); } 

Наш компонент, периодически вызывающий этот веб-сервис, будет выглядеть так:

public class MyService {     private readonly ISomeService _someServise 	     public MyService (ISomeService someServise)     {         _someServise = someServise;     } 	     public void DoSomething()     {         var response = _someServise.GetData();         ...     } } 

Готово. MyService может максимально прозрачно работать с веб-сервисом через его интерфейс ISomeService и использует внедрение зависимости. Это делает его прекрасно приспособленным для модульного тестирования и использования других реализаций интерфейса ISomeService, получающих данные из других источников. Осталось только создать реализацию ISomeService и не забыть про особенности обработки исключений при работе с WCF:

public class SomeServiceWcfProxy : ISomeService {     private readonly ChannelFactory<ISomeService> _channelFactory; 	     public SomeServiceWcfProxy (string endpointConfigurationName)     {         _channelFactory = new ChannelFactory<ISomeService>(endpointConfigurationName)     } 	     public Response GetData(Request request)     {         IClientChannel channel = null;         try         {             channel = _channelFactory.CreateChannel();             channel.Open();             var result = channel.GetData(request);             channel.Close();             return result;		         }         catch (Exception)         {             if (channel != null)                 channel.Abort();             throw;         }     } } 

Эта реализация обеспечивает необходимое поведение. Однако, при наличии множества веб-сервисов и методов, создание подобных реализаций является морально и физически тяжелой задачей. И тут нам на помощь приходит Castle.DynamicProxy. Для перехвата методов произвольного интерфейса необходимо реализовать интерфейс IInterceptor и переопределить метод Intercept:

public class WcfProxyInterceptor<TWcfServiceInterface> : IInterceptor where TWcfServiceInterface : class {     private readonly ChannelFactory<TWcfServiceInterface> _channelFactory;      public WcfProxyInterceptor(IChannelFactoryProvider<TWcfServiceInterface> channelFactoryProvider)     {         _channelFactory = channelFactoryProvider.GetChannelFactory();                }      public void Intercept(IInvocation invocation)     {         IClientChannel channel = null;         try         {             channel = (IClientChannel)_channelFactory.CreateChannel();             channel.Open();             invocation.ReturnValue = invocation.Method.Invoke(channel, invocation.Arguments);             channel.Close();         }         catch (Exception e)         {             if (channel != null)                 channel.Abort(); 					             var ex = e as TargetInvocationException;             if (ex != null)                 throw ex.InnerException; 					             throw;         }     } }  public interface IChannelFactoryProvider<TWcfServiceInterface>  where TWcfServiceInterface : class {     ChannelFactory<TWcfServiceInterface> GetChannelFactory(); } 

Появление дополнительного интерфейса IChannelFactoryProvider обеспечивает возможность создания фабрики каналов ChannelFactory с помощью различных параметров, отличных от endpointConfigurationName из прошлого примера. Нетрудно увидеть в этом применение известного принципа открытости/закрытости из набора SOLID. Реализация IChannelFactoryProvider для Basic-аутентификации может иметь вид:

public class BasicAuthChannelFactoryProvider<TWcfServiceInterface> : IChannelFactoryProvider<TWcfServiceInterface> where TWcfServiceInterface : class {     private readonly string _endpointConfigurationName;     private readonly string _userName;     private readonly string _password;      public BasicAuthChannelFactoryProvider(string endpointConfigurationName, string userName, string password)     {         _endpointConfigurationName = endpointConfigurationName;         _userName = userName;         _password = password;     }      public ChannelFactory<TWcfServiceInterface> GetChannelFactory()     {         var channelFactory = new ChannelFactory<TWcfServiceInterface>(_endpointConfigurationName);         var clientCredentials = new ClientCredentials();         clientCredentials.UserName.UserName = _userName;         clientCredentials.UserName.Password = _password;         channelFactory.Endpoint.Behaviors.RemoveAll<ClientCredentials>();         channelFactory.Endpoint.Behaviors.Add(clientCredentials);         return channelFactory;     } } 

Осталось собрать все вместе. Для получения реализации нашего интерфейса ISomeService с помощью реализации IInterceptor в библиотеке Castle.DynamicProxy имеется класс ProxyGenerator. С его помощью можно создать экземпляр разрабатываемого класса MyService следующим образом:

var pg = new ProxyGenerator(); var someInterfaceWcfProxy = pg.CreateInterfaceProxyWithoutTarget<ISomeService>(     new WcfProxyInterceptor<ISomeService>(new BasicAuthChannelFactoryProvider<ISomeService>("myEndpoint","user","pass"))); var myService = new MyService(someInterfaceWcfProxy); 

Напоследок, рассмотрим создание MyService с помощью контейнера внедрения зависимостей Castle Windsor.

IWindsorContainer container = new WindsorContainer(new XmlInterpreter()); container.Register(Component.For<BasicAuthChannelFactoryProvider<ISomeService>>().Named("basicChannelFactoryProvider")); container.Register(Component.For<WcfProxyInterceptor<ISomeService>>().Named("myWcfProxy")     .DependsOn((Dependency.OnComponent(typeof(IChannelFactoryProvider<ISomeService>), "basicChannelFactoryProvider")))); container.Register(Component.For<ISomeService>().Interceptors(InterceptorReference.ForKey("myWcfProxy")).First); container.Register(Component.For<MyService>());  var myProxy = container.Resolve<MyService>(); 

Настройки BasicAuthChannelFactoryProvider при этом можно вынести в конфигурационный файл:

<configSections>     <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" /> </configSections> <castle>   <components>      <component id="basicChannelFactoryProvider">         <parameters>            <endpointConfigurationName>myEndpoint</endpointConfigurationName>            <userName>user</userName>            <password>pass</password>         </parameters>      </component> 	  ... 	  

В других DI контейнерах можно добиться аналогичного результата с использованием метода CreateInterfaceProxyWithoutTarget класса ProxyGenerator библиотеки Castle.DynamicProxy.

ссылка на оригинал статьи http://habrahabr.ru/post/271973/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *