Многим разработчикам время от времени приходится взаимодействовать с различными 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/
Добавить комментарий