Действительно прозрачное использование WCF
Мотивация Для desktop-мира wcf остаётся самым распространенным способом организации клиент-серверного взаимодействия в .net как для локальных, так и для глобальных сетей. Он гибок в настройке, прост в использовании и прозрачен.По крайней мере, так должно быть. На практике добавление нового сервиса — это рутина. Нужно не забыть прописать конфигурацию на сервере, сделать то же самое на клиенте, нужно написать или сгенерировать proxy-класс. Поддерживать конфиги неудобно. Если сервис изменился, то нужно вносить изменения в proxy-класс. А ещё не забыть про регистрации в IoC-контейнере. И добавление новых хостов для новых сервисов. И еще хочется простой асинхронности. По отдельности всё просто, но даже для статьи я дописывал этот список уже трижды, и не уверен, что не упустил чего-нибудь.
Время автоматизировать. Простейший сценарий от создания решения до вызова wcf-сервиса выглядит так:
Install-Package Rikrop.Core.Wcf.Unity
Пишем ServiceContract и их реализации
На сервере и клиенте добавляем одну строку регистрации в IoC (конфиги править не надо)
Поднимаем хосты с двух строк
var assembly = Assembly.GetExecutingAssembly ();
_serviceHostManager.StartServices (assembly);
На клиенте резолвим IServiceExecutor
Server.Contracts содержит интерфейсы wcf-сервисов, Server — их реализацию, а так же реализацию хостера — класса, который будет поднимать wcf-сервисы. BL — логика сервера. ConsoleServiceHost хостит сервисы в домене консольного приложения. Client.Presentaion содержит соответствующий слой клиента. В нашем примере там только команда вызова сервиса и обработка результата. Client — консольное приложение, использующее предыдущую сборку для обработки ввода пользователя.
Собственно, nuget-пакеты нужно устанавливать следующим образом:
Rikrop.Core.Wcf.Unity содержит хелперы для регистрации в IoC-контейнере инфраструктуры, необходимой для работы wcf. Это набор готовых решений и расширений для быстрой настройки всех аспектов взаимодействия. Пакет следует добавить в проекты, где будут серверные и клиентские регистрации в IoC-контейнере. У нас это RikropWcfExample.Server и RikropWcfExample.Client. Rikrop.Core.Wcf содержит основные классы по работе с wcf, управлению каналом, сессиями, авторизацией, хостинга wcf-сервисов. Его добавим в RikropWcfExample.Server, там будет лежать хостер, и RikropWcfExample.Client.Presentation*, откуда будет происходить вызов wcf-сервиса. В RikropWcfExample.Server.Contracts добавим описание wcf-сервиса: using System.ServiceModel; using System.Threading.Tasks;
namespace RikropWcfExample.Server.Contracts
{
[ServiceContract]
public interface ICalculatorService
{
[OperationContract]
Task
namespace RikropWcfExample.Server { public class CalculatorService: ICalculatorService { private readonly FibonacciCalculator _fibonacciCalculator;
public CalculatorService (FibonacciCalculator.ICtor fibonacciCalculatorCtor) { _fibonacciCalculator = fibonacciCalculatorCtor.Create (); }
public async Task
private static IUnityContainer RegisterWcfHosting (this IUnityContainer container, string serviceIp, int servicePort) { container .RegisterServerWcf ( o => o.RegisterServiceConnection (reg => reg.NetTcp (serviceIp, servicePort)) .RegisterServiceHostFactory (reg => reg.WithBehaviors ().AddDependencyInjectionBehavior ()) ); return container; } Для клиента указывается тип обёртки-исполнителя для сервисов (ServiceExecutor), тип обёртки над привязкой (Standart предполагает NetTcp) и, собственно, адрес сервера: private static IUnityContainer RegisterWcf (this IUnityContainer container, string serviceIp, int servicePort) { container .RegisterClientWcf (o => o.RegisterServiceExecutor (reg => reg.Standard () .WithExceptionConverters () .AddFaultToBusinessConverter ()) .RegisterChannelWrapperFactory (reg => reg.Standard ()) .RegisterServiceConnection (reg => reg.NetTcp (serviceIp, servicePort)));
return container; } Всё. Не нужно регистрировать каждый сервис по интерфейсу, не нужно создавать Proxy, не нужно прописывать wcf в конфигурации — эти регистрации позволят сразу начать работать с сервисами так, будто это локальные вызовы.Но сначала нужно захостить их на сервере. Библиотека Rikrop.Core.Wcf уже включает класс ServiceHostManager, который сделает всю работу самостоятельно. Прописывать каждый сервис не нужно: using Rikrop.Core.Wcf; using System.Reflection;
namespace RikropWcfExample.Server { public class WcfHoster { private readonly ServiceHostManager _serviceHostManager;
public WcfHoster (ServiceHostManager serviceHostManager) { _serviceHostManager = serviceHostManager; }
public void Start () { var assembly = Assembly.GetExecutingAssembly (); _serviceHostManager.StartServices (assembly); }
public void Stop () { _serviceHostManager.StopServices (); } } } Запустим сервер: public static void Main () { using (var serverContainer = new UnityContainer ()) { serverContainer.RegisterServerDependencies ();
var service = serverContainer.Resolve
Console.WriteLine («Сервер запущен. Для остановки нажмите Enter.»); Console.ReadLine ();
service.Stop (); } } Запустим клиент: static void Main () { using (var container = new UnityContainer ()) { container.RegisterClientDependencies ();
var calculateFibonacciCommandCtor = container.Resolve
int number; while (int.TryParse (GetUserInput (), out number)) { var command = calculateFibonacciCommandCtor.Create (); var result = command.Execute (number); Console.WriteLine («Fibonacci[{0}] = {1}», number, result); } } } Работает:
Сравнение с классическим подходом и расширяемость
Может показаться, что предложенное решение требует довольно много инфраструктурного кода и не несёт преимуществ перед обычным использованием wcf. Проще всего будет показать разницу на примере типовых ситуаций, возникающих при работе над проектами.Добавление нового метода в существующий wcf-сервис или изменение сигнатуры существующего метода
Rikrop.Core.Wcf (.Unity)
Без использования библиотек
В ServiceContract добавить определение метода.
В классе wcf-сервиса добавить реализацию.
Теперь можно вызвать новый метод на клиенте.
В ServiceContract добавить определение метода.
В классе wcf-сервиса добавить реализацию.
Сгенерировать proxy-класс на клиенте или добавить ручную реализацию вызова нового сервиса на клиенте (иногда оба варианта, если не хочется напрямую использовать proxy-класс.
Теперь можно вызвать новый сервис на клиенте.
Добавление нового wcf-сервиса в существующий хост
Rikrop.Core.Wcf (.Unity)
Без использования библиотек
Создать ServiceContract нового сервиса.
Реализовать контракт сервиса.
Теперь можно вызвать новый сервис на клиенте.
Создать ServiceContract нового сервиса.
Реализовать контракт сервиса.
Сгенерировать proxy-класс на клиенте или добавить ручную реализацию вызова нового сервиса на клиенте (иногда оба варианта, если не хочется напрямую использовать proxy-класс.
Добавить в хост wcf код, инициализирующий ServiceHost для нового сервиса.
Зарегистрировать вклиентском IoC-контейнере Proxy-класс нового сервиса.
Добавить конфигурацию сервиса на сервере и не клиенте.
Теперь можно вызвать новый сервис на клиенте.
Изменение настроек всех wcf-сервисов (на примере типа привязки)
Rikrop.Core.Wcf (.Unity)
Без использования библиотек
В серверной регистрации изменить строку с типом привязки*.
В клиентской регистрации изменить строку с типом тип привязки*.
* см. public Result Custom
container
.RegisterType
return container; } Результат:
Алгоритм работы
Клиент авторизуется через выбранный метод в wcf-контракте. При успешной аутентификации сервер создаёт сессию, сохраняет её в репозитории и отдаёт данные о ней клиенту:
var newSession = Session.Create (userId);
_sessionRepository.Add (newSession);
return new SessionDto { SessionId = newSession.SessionId, Username = «ExampleUserName» };
Клиент получает данные о сессии и сохраняет их:
var clientSession = container.Resolve
return await _fibonacciCalculator.Calculate (n); } Клиент имеет возможность получить данные, принятые с сервера при авторизации: _logger.LogInfo (string.Format («SessionId {0} with name {1} begin calculate Fibomacci», _clientSession.SessionId, _clientSession.Session.Username)); Что внутри Большую часть инфраструктуры предоставляет библиотека System.ServiceModel.dll. Однако, есть несколько решений, которые нужно рассмотреть подробнее.Основой взаимодействия между клиентом и сервером служат реализации интерфейса IServiceExecutor, находящиеся в библиотеке Rikrop.Core.Wcf.
public interface IServiceExecutor
В статье так же косвенно упомянаются другие библиотеки:
Реализация автофабрик: private static IUnityContainer RegisterFactories (this IUnityContainer container) { new[] { Assembly.GetExecutingAssembly (), typeof (FibonacciCalculator).Assembly } .SelectMany (assembly => assembly.DefinedTypes.Where (type => type.Name == «ICtor»)) .Where (type =>! container.IsRegistered (type)) .ForEach (container.RegisterFactory);
return container;
}
Обёртки над логгерами:
private static IUnityContainer RegisterLogger (this IUnityContainer container)
{
container.RegisterType
return container; } Итоги Единожды настроив инфраструктуру на проекте, можно надолго забыть о сетевой природе взаимодействия через IServiceExexutor. Лучше всего применять системный подход и использовать так же бибилиотки для построения настольных приложений с применением mvvm-паттерна, взаимодействия с БД, логирования и других типовых задач. Но даже при нежелании использовать незнакомый и не всегда привычный фреймворк, можно найти применение идеям, лежащим в его основе. Расширяемость компонент, строгая типизация при конфигурировании, прозрачность взаимодействия на всех слоях, минимизация инфраструктурного кода и затрат времени на поддержание инфрастурктуры — это то, о чём важно не забывать при написании калькулятора и многопользовательской Enterprise-системы. Можно скачать код библиотек и подключить их к решению проектом вместо использования библиотеки. Это позволит изучить работу под отладчиком и при необходимости внести свои изменения.Бонус Нет ничего лучше практики. Я узнал, что у нас был опыт перевода довольно крупного проекта (~300.000 строк кода) в стадии где-то между разработкой и поддержкой на использование Rikrop.Core.Wcf. Это довольно интересный опыт мучений с async/await в .net 4.0, кастомизации работы с сессиями, извлечения настроек из конфига и перевод их в c#-форму. Если это кому-нибудь будет интересно, можно описать конкретный пример перехода на эту библиотеку без пеетягивания всего фреймворка.Еще есть решение для wpf с информированием пользователя через блокировку ui или всплывающие окна, реализованные через ServiceExecutorFactory. Это частный пример и он относится куда больше к wpf, чем к wcf. Но это может дать больше информации о преимуществах библиотеки и мотивации к использованию.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.