Действительно прозрачное использование WCF

    Мотивация


    Для desktop-мира wcf остаётся самым распространенным способом организации клиент-серверного взаимодействия в .net как для локальных, так и для глобальных сетей. Он гибок в настройке, прост в использовании и прозрачен.

    По крайней мере, так должно быть. На практике добавление нового сервиса — это рутина. Нужно не забыть прописать конфигурацию на сервере, сделать то же самое на клиенте, нужно написать или сгенерировать proxy-класс. Поддерживать конфиги неудобно. Если сервис изменился, то нужно вносить изменения в proxy-класс. А ещё не забыть про регистрации в IoC-контейнере. И добавление новых хостов для новых сервисов. И еще хочется простой асинхронности. По отдельности всё просто, но даже для статьи я дописывал этот список уже трижды, и не уверен, что не упустил чего-нибудь.

    Время автоматизировать. Простейший сценарий от создания решения до вызова wcf-сервиса выглядит так:
    1. Install-Package Rikrop.Core.Wcf.Unity
    2. Пишем ServiceContract и их реализации
    3. На сервере и клиенте добавляем одну строку регистрации в IoC (конфиги править не надо)
    4. Поднимаем хосты с двух строк
      var assembly = Assembly.GetExecutingAssembly();
      _serviceHostManager.StartServices(assembly);
      
    5. На клиенте резолвим IServiceExecutor<TService>. Эта обёртка служит для вызова методов сервиса и скрывает работу с каналом.
    6. Можно пользоваться
      var articles = await _myServiceExecutor.Execute(service => service.GetArticles());
      


    Quick start


    Создадим клиент-серверное приложение. Клиенты передают серверу индекс числа из последовательности Фибоначчи, сервер возвращает число из последовательности с заданным индексом. Из кода в статье убрано логирование и обработка ошибок, но в коде на github я привожу более полный пример для иллюстрации целостного подхода.
    Структура проектов, приближенная к реальности:



    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<ulong> GetFibonacciNumber(int n);
        }
    }
    

    Реализация в CalculatorService.cs будет передавать запрос и возвращать результат из слоя бизнес-логики:

    using RikropWcfExample.Server.BL;
    using RikropWcfExample.Server.Contracts;
    using System.Threading.Tasks;
    
    namespace RikropWcfExample.Server
    {
        public class CalculatorService : ICalculatorService
        {
            private readonly FibonacciCalculator _fibonacciCalculator;
    
            public CalculatorService(FibonacciCalculator.ICtor fibonacciCalculatorCtor)
            {
                _fibonacciCalculator = fibonacciCalculatorCtor.Create();
            }
    
            public async Task<ulong> GetFibonacciNumber(int n)
            {
                return await _fibonacciCalculator.Calculate(n);
            }
        }
    }
    

    Пока можно заметить одну особенность — wcf-сервис использует async/await для описания асинхронности. В остальном никаких специфических конструкций нет.

    Теперь перейдем к регистрации. Простейший синтаксис для сервера указывает тип привязки (NetTcp) список поведений, которые должны быть добавлены к сервисам:

    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<WcfHoster>();
            service.Start();
    
            Console.WriteLine("Сервер запущен. Для остановки нажмите Enter.");
            Console.ReadLine();
    
            service.Stop();
        }
    }
    

    Запустим клиент:

    static void Main()
    {
        using (var container = new UnityContainer())
        {
            container.RegisterClientDependencies();
    
            var calculateFibonacciCommandCtor = container.Resolve<CalculateFibonacciCommand.ICtor>();
    
            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<TServiceConnection>(LifetimeManager lifetimeManager = null, params InjectionMember[] injectionMembers) where TServiceConnection: IServiceConnection
    • В app.config сервера изменить все записи в блоке <bindings>*.
    • В app.config клиента изменить все записи в блоке <bindings>*.

    * Количество работы пропорционально количеству wcf-сервисов. Если их 100, то остаётся только надеяться, что быстрая замена по файлу сработает.

    Изменение настроек нескольких wcf-сервисов (на примере типа привязки)

    Rikrop.Core.Wcf(.Unity) Без использования библиотек
    • На сервере добавить регистрацию для нового адреса и типа привязки.
    • В клиентской регистрации добавить новую регистрацию для другого адреса и типа привязки.

    • В app.config сервера изменить записи в блоке <bindings> для нужных wcf-сервисов.
    • В app.config клиента изменить записи в блоке <bindings> для нужных wcf-сервисов.


    Стоит сказать несколько слов о последних двух пунктах. При правильной организации app.config вносить изменения в него довольно легко. Это можно делать без пересборки приложения. В реальной разработке структурированная конфигурация wcf попадается довольно редко, чему виной итеративность разработки. Изменять конфигурацию непрогаммисту тоже приходится нечасто, если начальные настройки удовлетворяют требованиям. При этом, легко совершить опечатку, которую компилятор не найдёт.

    Расширяемость. Behavior для авторизации и работы с сессиями


    Расширение функциональности и изменение поведения происходит за счёт добавления при регистрации Behavior. Наиболее частым в применении является поведение, отвечающее за передачу в заголовке wcf-сообщения информации о сессии.
    Для демонстрации функционала был создан отдельный branch с расширенным кодом предыдущего примера. В стандартной настройке поведения разработчику предлагается выбрать метод авторизации — это OperationContract, который будет доступен пользователям без сессии в заголовке сообщения. Вызов остальных методов будет возможен только при заполненном заголовке.

    Регистрация на сервере будет выглядеть следующим образом:

    container
        .RegisterType<ISessionResolver<Session>, SessionResolver<Session>>()
        .RegisterServerWcf(
            o => o.RegisterServiceConnection(reg => reg.NetTcp(serviceIp, servicePort))
                    .RegisterServiceHostFactory(reg => reg.WithBehaviors()
                    .AddErrorHandlersBehavior(eReg => eReg.AddBusinessErrorHandler().AddLoggingErrorHandler(NLogger.CreateEventLogTarget()))
                    .AddDependencyInjectionBehavior()
                    .AddServiceAuthorizationBehavior(sReg => sReg.WithStandardAuthorizationManager()
                                                    .WithStandardSessionHeaderInfo("ExampleNamespace", "SessionId")
                                                    .WithOperationContextSessionIdInitializer()
                                                    .WithSessionAuthStrategy<Session>()
                                                    .WithLoginMethod<ILoginService>(s => s.Login())
                                                    .WithOperationContextSessionIdResolver()
                                                    .WithInMemorySessionRepository()
                                                    .WithStandardSessionCopier())
                                    )
                    );
    

    Можно изменить способ авторизации, добавив свою имплементацию System.ServiceModel.ServiceAuthorizationManager, изменить способ инициализации идентификатора сессии, метод проверки авторизации, способ извлечения сессии из контекста выполнения запроса, способ хранения и копирования сессий на сервере. В обобщенном случае регистрация AuthorizationBehavior может выглядеть следующим образом:

    .AddServiceAuthorizationBehavior(sReg => sReg.WithCustomAuthorizationManager<ServiceAuthorizationManagerImpl>()
                    .WithCustomSessionHeaderInfo<ISessionHeaderInfoImpl>()
                    .WithCustomSessionIdInitializer<ISessionIdInitializerImpl>()
                    .WithCustomAuthStrategy<IAuthStrategyImpl>()
                    .WithLoginMethod<ILoginService>(s => s.Login())
                    .WithCustomSessionIdResolver<ISessionIdResolverImpl>()
                    .WithCustomSessionRepository<ISessionRepositoryImpl<MySessionImpl>>()
                    .WithCustomSessionCopier<ISessionCopierImpl<MySessionImpl>>())
    

    Клиентская регистрация так же меняется:

    private static IUnityContainer RegisterWcf(this IUnityContainer container, string serviceIp, int servicePort)
    {
        container
            .RegisterType<ClientSession>(new ContainerControlledLifetimeManager())
            .RegisterClientWcf(o => o
                    .RegisterServiceExecutor(reg => reg.Standard()
                    .WithExceptionConverters()
                    .AddFaultToBusinessConverter())
            .RegisterChannelWrapperFactory(reg => reg.Standard())
            .RegisterServiceConnection(reg => reg
            .NetTcp(serviceIp, servicePort)
            .WithBehaviors()
            .AddSessionBehavior(sReg => sReg
                            .WithStandardSessionHeaderInfo("ExampleNamespace", "SessionId")
                            .WithCustomSessionIdResolver<ClientSession>(new ContainerControlledLifetimeManager())
                            .WithStandardMessageInspectorFactory<ILoginService>(service => service.Login()))));
    
        return container;
    }
    

    Результат:



    Алгоритм работы

    1. Клиент авторизуется через выбранный метод в wcf-контракте. При успешной аутентификации сервер создаёт сессию, сохраняет её в репозитории и отдаёт данные о ней клиенту:

      var newSession = Session.Create(userId);
      _sessionRepository.Add(newSession);
      return new SessionDto { SessionId = newSession.SessionId, Username = "ExampleUserName" };
      
    2. Клиент получает данные о сессии и сохраняет их:

      var clientSession = container.Resolve<ClientSession>();
      var sessionDto = Task.Run(async () => await loginServiceExecutor.Execute(s => s.Login())).Result;
      clientSession.Session = sessionDto;
      
    3. Сервер имеет возможность получить данные о вызывающем клиенте:

      public async Task<ulong> GetFibonacciNumber(int n)
      {
          var session = _sessionResolver.GetSession();
          _logger.LogInfo(
              string.Format("User with SessionId={0} and UserId={1} called CalculatorService.GetFibonacciNumber", session.SessionId, session.UserId));
      
          return await _fibonacciCalculator.Calculate(n);
      }
      
    4. Клиент имеет возможность получить данные, принятые с сервера при авторизации:

      _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<out TService>
    {
        Task Execute(Func<TService, Task> action);
        Task<TResult> Execute<TResult>(Func<TService, Task<TResult>> func);
    }
    

    В простейшем случае открывается канал и метод вызывается в контексте этого канала:

    public async Task<TResult> Execute<TResult>(Func<TService, Task<TResult>> func)
    {
        using (var wrapper = _channelWrapperFactory.CreateWrapper())
        {
            return await func(wrapper.Channel);
        }
    }
    

    Более сложные реализации могут конвертировать ошибки или дополнительно извещать об окончании обработки изменением свойства. Наибольшее распространение эти идеи получили в WPF-реализациях IServiceExecutor, где с помощью ServiceExecutorFactory можно создать обёртки над wcf-сервисом, позволяющие использовать DataBinding для оповещения UI о продолжительной операции, или отображающие popup с произвольной информацией во время ожидания ответа от сервера.
    Для легкой реализации главную роль играют Fluent interface при регистрации и стандартные реализации инфраструктуры библиотеки, из-за чего даже даже в самых сложных конструкциях легко разобраться с первого раза с помощью подскзок студии:



    В статье так же косвенно упомянаются другие библиотеки:
    • Реализация автофабрик:

      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<ILogger>(new ContainerControlledLifetimeManager(),
                                          new InjectionFactory(f => NLogger.CreateConsoleTarget()));
      
          return container;
      }
      

    Итоги


    Единожды настроив инфраструктуру на проекте, можно надолго забыть о сетевой природе взаимодействия через IServiceExexutor. Лучше всего применять системный подход и использовать так же бибилиотки для построения настольных приложений с применением mvvm-паттерна, взаимодействия с БД, логирования и других типовых задач. Но даже при нежелании использовать незнакомый и не всегда привычный фреймворк, можно найти применение идеям, лежащим в его основе. Расширяемость компонент, строгая типизация при конфигурировании, прозрачность взаимодействия на всех слоях, минимизация инфраструктурного кода и затрат времени на поддержание инфрастурктуры — это то, о чём важно не забывать при написании калькулятора и многопользовательской Enterprise-системы. Можно скачать код библиотек и подключить их к решению проектом вместо использования библиотеки. Это позволит изучить работу под отладчиком и при необходимости внести свои изменения.

    Бонус


    Нет ничего лучше практики. Я узнал, что у нас был опыт перевода довольно крупного проекта (~300.000 строк кода) в стадии где-то между разработкой и поддержкой на использование Rikrop.Core.Wcf. Это довольно интересный опыт мучений с async/await в .net 4.0, кастомизации работы с сессиями, извлечения настроек из конфига и перевод их в c#-форму. Если это кому-нибудь будет интересно, можно описать конкретный пример перехода на эту библиотеку без пеетягивания всего фреймворка.

    Еще есть решение для wpf с информированием пользователя через блокировку ui или всплывающие окна, реализованные через ServiceExecutorFactory. Это частный пример и он относится куда больше к wpf, чем к wcf. Но это может дать больше информации о преимуществах библиотеки и мотивации к использованию.

    Only registered users can participate in poll. Log in, please.

    Нужна ли статья с примером перевода реального приложения на работу с библиотеками, описанными в статье?

    • 91.2%Да124
    • 8.8%Нет12

    Нужна ли статья с примером работы в wpf-приложении и взаимодействием с UI?

    • 90.1%Да128
    • 9.9%Нет14

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 11

      +2
      Изменение настроек всех wcf-сервисов (на примере типа привязки)… В app.config сервера изменить все записи в блоке <bindings>*.
      В WCF можно описать один биндинг на все сервисы. Почему вы сравниваете грамотную реализацию с неграмотной — и приходите к выводу, что некоторая библиотека лучше?
        0
        Я привел последние два примера как раз для иллюстрации того, что не во всех сценариях библиотека позволяет делать меньше действий. Для разделения сервисов на несколько групп с разными настройками конфигурация подходит даже лучше. У нас просто не возникало таких потребностей, поэтому есть только базовые и не всегда удобные механизмы для этого (можно посмотреть реализацию ServiceHostManager или смириться и разделять на уровне контейнеров/проектов).
        +1
        Это все хорошо до тех пор, пока у вас между сервером и клиентом есть общая сборка контрактов (и желательно — в том же решении, чтобы не развлекаться с переносом кода/сборки); проще говоря — когда у вас у сервиса и клиента единый жизненный цикл. К сожалению, так бывает далеко не всегда (чтобы не сказать «редко»).

        (а когда есть гарантированная общая сборка, message-based экономнее)
          0
          Я могу только сказать, что у нас совершенно разный опыт разработки под .net. Это касается не только wcf, но и применения EF.
          Для типовых приложений, когда сервер и клиент являются по сути одним приложением, описанное мной решение подходит. При этом не важно intranet или internet используется для передачи. Да, в обычном сценарии сборка с OperationContract не добавляется на клиент из-за использования Proxy, туда идёт только DataContract.
          Если у сервера и клиента жизненные циклы различаются, они лежат в разных решениях, то можно использовать привлекательный путь web api. Или распространять сборку контрактов через nuget. Или применять классический подход.
          Я не пытаюсь сказать, что статья описывает золотой молоток, которого так долго ждали. Для наших задач подходит исходная или немного модифицированная версия библиотеки Rikrop.Core.Wcf. В тоже время я с интересом наблюдаю за библиотекой Boilerplate, которая тоже не является всеобъемлющим решением и, к сожалению, не столь модульна (я бы с удовольствием использовал репозитории и UoF оттуда), но является отличным инструментом для своего круга задач.
            +1
            >> Для типовых приложений, когда сервер и клиент являются по сути одним приложением, описанное мной решение подходит
            Я бы не стал называть это решение типовым. Это один из существующих вариантов (и его распространенность понемногу падает).

            >> Да, в обычном сценарии сборка с OperationContract не добавляется на клиент из-за использования Proxy, туда идёт только DataContract.
            Туда может вообще ничего не идти, клиент может все создавать на основе метаданных (и иногда это удобно).
          0
          И еще — почему у вас в nuget-пакете прописаны зависимости от конкретных версий Unity, а не от диапазонов?
            0
            Я приведу цитату из предыдущего поста:
            Фиксированная версия Unity. Если в Nuget-пакете для 4.0 не указать версию явно, то nuget попытается зарезолвить последнюю версию, несмотря на то, что она несовместима с .net 4. Если кто-нибудь знает способ избавиться от этой проблемы, просьба сообщить в личку.

            Диапазон для 4.0 выставить можно, поскольку известна последняя доступная версия для .net 4.0. Для 4.5 нужно ставить максимальной текущую доступную версию и обновлять по мере выхода новых версий. Возможно, есть другой путь или это лучшее решение?
              –1
              >> Для 4.5 нужно ставить максимальной текущую доступную версию и обновлять по мере выхода новых версий.
              >> Возможно, есть другой путь или это лучшее решение?
              Очевидно, есть: указать самую раннюю версию, с которой работает решение, как минимальную, верхнюю границу оставить открытой (или, если вы параноик, зафиксировать мажорную — и только ее — версию).
            0
            Привет, Вадим :)

            Давай определимся со значением слова «прозрачный» в заголовке статьи.

            1. Прозрачный, значит максимально простой для клиента способ получения данных с сервера. Т.е. разработчик Front-end приложения читая код с первого взгляда не может отличить локальный вызов от удаленного.

            2. Прозрачный, т.е. максимально простой с точки зрения работы с WCF, те, чтобы человек, читая код, с первого взгляда понял, что к чему.

            Они немного противоречат друг другу. Известный нам с тобой фреймворк имеет ряд недостатков, и прозрачностью не обладает, я бы даже сказал, что он слегка громозкий, из-за большого количества классов и абстракций, которые там есть. Человек, который хорошо им владеет, может очень быстро написать клиент-серверное приложение, что прекрасно подходит для небольших проектов, но чтобы эффективно использовать, надо достаточно глубоко в нём разобраться, больше, чем прочитать статью и зареференсить nuget-сборку, иначе в некоторых местах поведение может отличаться от ожидаемого.

            Известные проблемы текущей реализации:
            1. Фреймворк заточен под хостинг WCF-сервиса в Windows Service или Console Application. Для некоторых решений хостинг в IIS более оптимален, с текущей реализацией фреймворка не сделаешь красивого решения.
            2. В текущей реализации фреймворка с WCF-сервиса нельзя вызвать другой WCF-сервис. Эта возможность необходима для некоторых приложений.
            3. Текущая реализация работает по net.tcp протоколу, а если необходимо, к примеру, REST API, которое сереализует данные в виде XML/JSON/SOAP?
            4. Некоторая реализация WCF-сервисов предоставляет WSDL, что можно использовать так же для автодокументирования сервисов. Здесь этого так же нет.

            Подчеркну ещё раз мораль:
            Если нужно сделать маленький проект, фреймворк очень хороший.
            Если есть желание использовать в продакшне — нужно детально изучить код, прежде, чем класть чужую либо в ядро своего проекта. И лучше исходниками, а не ссылкой на сборку.
              0
              Привет, Николай.
              Спасибо за комментарий.

              Мне нравится прозрачность с точки зрения именно вызова сервисов с клиента — если не брать в рассчет авторизацию, достаточно знать только про то, как пользоваться ServiceExecutor, а с ним напутать сложно.
              Что касается эффективного использования и небольших проектов, конкретно эти 2 сборки удалось прикрутить к застарелому приложению на .net 4.0 на Prism с десятком модулей, совсем непростым взаимодействием с wcf и совершенно запутанной работой с сессиями. Это было сделано для быстрого управления конфигурацией и тестов производительности с разными настройками, и с задачей такое решение справилось. То есть, даже в отрыве от остальной инфрастуктуры — работает. Я бы назвал это даже «из коробки».
              Стало интересно о реализации вызова wcf-сервисов из других wcf-сервисов, что если добавить клиентскую регистрацию на сервер? Оказалось, что работает:
              public class CalculatorService : ICalculatorService
              {
                  private readonly IServiceExecutor<IAnotherService> _anotherServiceExecutor;
              
                  public CalculatorService(IServiceExecutor<IAnotherService> anotherServiceExecutor)
                  {
                      _anotherServiceExecutor = anotherServiceExecutor;
                  }
              
                  public async Task<int> CalculateSqrSqr(int n)
                  {
                      return await _anotherServiceExecutor.Execute(service => service.Get(n*n));
                  }
              }
              

              Я боюсь подумать сейчас о расширенных сценариях, но решение есть. Как и у некоторых других задач, которые могут стать перед пользователем. Я тоже порекомендовал в статье использовать исходный код вместо пакета.
              Ещё раз хочу сказать, что сложно пытаться сделать всеохватывающее решение — кому-то из моих коллег хватает простой реализации подобия ServiceExecutor:
              var sign = 
                      WCFHelper<ISftService>
                          .Execute("BasicHttpBinding_ISftService", serviceInstance => serviceInstance.Sign(dataToSign), exception => { });
              

              Мне нравятся идеи сокрытия сложности. Да, иногда для этого нужно написать больше кода, но в конечном счёте какой-нибудь UnitOfWorkAttribute для метода, открывающий и завершающий (если вызывающий метод не был тоже помечен как [UnitOfWork]) или откатывающий при ошибке транзакцию при выходе из метода — того стоит.
              0
              Динамическую проксю бы для WebAPI — цены бы такой библиотеке не было.
              Т.к. генерация прокси-классов через ручной запуск t4-скриптов — то еще «удовольствие».
              Или кто подскажет — может есть уже такая библиотека?

              Only users with full accounts can post comments. Log in, please.