Перейти к содержанию

Создание веб-сервиса WCF, который вызывает веб-сервис платформы

Создание веб-сервиса WCF, который вызывает веб-сервис платформы

Для интеграции с другими системами по протоколу SOAP или для различных других видов сервисов в SOA (Service Oriented Architecture) рекомендуется развернуть веб-сервер на платформе WCF (Windows Communication Foundation). Поскольку веб-сервис TESSA работает на платформе .NET, которая не имеет поддержки WCF, то вы можете создать отдельный веб-сервис WCF на .NET Framework, который взаимодействует с REST-сервисом TESSA (т.н. “микросервис”).

Сервис WCF возможно развернуть только на платформе .NET Framework под ОС Windows. При этом веб-сервис платформы TESSA может находиться на любом другом сервере, в т.ч. на Linux. Бизнес-логику такого сервиса мы рекомендуем перенести в контроллер и расширения в сервисе TESSA, а сервис WCF использовать в качестве “прослойки” между клиентом (системой, с которой выполняется интеграция) и веб-сервисом TESSA.

Note

Веб-сервис имеет параметры подключения только к веб-сервису TESSA по протоколу https. Он не должен иметь доступа к базе данных или файловым хранилищам системы.

Note

Для работы примера убедитесь, что у вас развёрнут веб-сервис для платформы актуальной версии (3.4 или старше), и в проектных расширениях Tessa.Extensions.Server есть ServiceController в том виде, в каком он поставляется вместе со сборкой платформы (т.е. он не изменён в ваших расширениях).

Перед созданием проекта в Visual Studio убедитесь, что установлен компонент Windows Communication Foundation:

Создайте новый проект WCF Service Application:

Укажите версию .NET Framework 4.7.2, которая будет требоваться для работы разрабатываемого веб-сервиса. При необходимости вы можете указать более старшую версию .NET Framework 4.8.

Нажмите пункт контекстного меню Manage NuGet Packages в окне Solution Explorer и установите NuGet-пакеты актуальных версий (не требуется синхронизировать версии, используемые в платформе, с версиями в вашем веб-сервисе):

  1. Newtonsoft.Json - используется для сериализации/десериализации объектов в формате JSON, который обычно применяется для REST-методов.

  2. System.Net.Http - требуется для вызова REST-методов через API HttpClient.

В файле Web.config укажите настройки подключения к веб-сервису платформы внутри тега <appSettings>:

<appSettings> <add key="BaseAddress" value="https://server/tessa"/> <add key="SendTimeout" value="00:40:00"/> <add key="Login" value="admin"/> <add key="Password" value="admin"/>

<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> </appSettings>

  1. BaseAddress - базовый адрес сервиса, замените “server” на имя сервера или IP-адрес, а “tessa” - название папки в IIS (при развёртывании на Linux удалите).

  2. SendTimeout - таймаут выполнения каждого запроса (в т.ч. таймаут открытия соединения).

  3. Login - логин, который используется через свойство ServiceHelper.Login в примерах контроллера для создания сессии в одном из методов. Для вашего сервиса указывать необязательно.

  4. Password - пароль, который используется через свойство ServiceHelper.Password в примерах контроллера для создания сессии в одном из методов. Для вашего сервиса указывать необязательно.

Для выполнения метода открытия сессии "service/Login" в тестовом контроллере требуется сериализовать JSON-объект, аналогичный классу Tessa.Extensions.Shared.Services.IntegrationLoginParameters. Объект содержит логин и пароль, для которых выполняется аутентификация.

Добавьте класс IntegrationLoginParameters.cs:

namespace TessaWcfService { /// <summary> /// Параметры входа в учётную запись для интеграционных сервисов. /// </summary> public class IntegrationLoginParameters { /// <summary> /// Логин к учётной записи. /// </summary> public string Login { get; set; }

/// <summary> /// Пароль к учётной записи. /// </summary> public string Password { get; set; } } }

Добавьте класс ServiceHelper.cs со вспомогательными методами и свойствами. Вы можете поменять тестовый сервис IService1 под свои нужды, но методы и свойства класса ServiceHelper вам, скорее всего, пригодятся:

using System; using System.Configuration; using System.Globalization; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json;

namespace TessaWcfService { public static class ServiceHelper { private static volatile bool initialized;

private static readonly object syncObject = new object();

static ServiceHelper() { BaseAddress = ConfigurationManager.AppSettings["BaseAddress"]; if (string.IsNullOrEmpty(BaseAddress)) { throw new InvalidOperationException("Specify base address in web.config"); }

string sendTimeout = ConfigurationManager.AppSettings["SendTimeout"]; SendTimeout = TimeSpan.Parse(sendTimeout, CultureInfo.InvariantCulture);

Login = ConfigurationManager.AppSettings["Login"]; Password = ConfigurationManager.AppSettings["Password"]; }

public static string BaseAddress { get; }

public static TimeSpan SendTimeout { get; }

public static string Login { get; }

public static string Password { get; }

public static void InitializeServicePointManager() { if (!initialized) { lock (syncObject) { if (!initialized) { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.DefaultConnectionLimit = 100; ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

initialized = true; } } } }

public static HttpClient CreateHttpClient(bool windowsAuth = false) { InitializeServicePointManager();

return new HttpClient( new HttpClientHandler { UseDefaultCredentials = windowsAuth, PreAuthenticate = windowsAuth, CheckCertificateRevocationList = false, UseProxy = false }) { Timeout = SendTimeout }; }

public static Uri GetRequestUri(string methodRoute) => new Uri(BaseAddress + "/web/" + methodRoute);

public static string TryGetContentType(HttpContent content) { HttpContentHeaders headers = content.Headers; return headers.Contains("Content-Type") ? headers.ContentType.MediaType : null; }

public static StringContent FromText(string text) => new StringContent(text ?? string.Empty, Encoding.UTF8, MediaTypeNames.Text.Plain);

public static StringContent FromJson(string text) => new StringContent(text, Encoding.UTF8, MediaTypeHeaderValue.Parse("application/json").MediaType);

public static StringContent FromJsonObject(object value) { var serializer = new JsonSerializer(); var sb = new StringBuilder();

using (var writer = new StringWriter(sb)) { serializer.Serialize(writer, value ?? string.Empty); }

return FromJson(sb.ToString()); }

public static async Task<string> SendAsync( HttpMethod httpMethod, string methodRoute, HttpContent content = null, string tokenHeader = null, bool returnsVoid = false, CancellationToken cancellationToken = default) { if (!string.IsNullOrEmpty(tokenHeader)) { if (content is null) { content = FromText(string.Empty); }

content.Headers.Add("Tessa-Session", tokenHeader); }

var request = new HttpRequestMessage( httpMethod, GetRequestUri(methodRoute)) { Content = content, Version = HttpVersion.Version11 };

using (HttpClient httpClient = CreateHttpClient()) using (HttpResponseMessage response = await httpClient.SendAsync( request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { response.EnsureSuccessStatusCode();

string responseContentType = TryGetContentType(response.Content); if (!string.IsNullOrEmpty(responseContentType)) { responseContentType = MediaTypeHeaderValue.TryParse(responseContentType, out MediaTypeHeaderValue header) ? header.MediaType : null; }

switch (responseContentType) { case null when returnsVoid || response.StatusCode == HttpStatusCode.NoContent: return null;

case MediaTypeNames.Text.Plain: case "application/json": return await response.Content.ReadAsStringAsync();

default: throw new NotSupportedException( $"Unsupported response content type: \"{responseContentType}\""); } } } } }

Содержимое интерфейса IService1.cs замените таким образом:

using System; using System.ServiceModel; using System.Threading.Tasks;

namespace TessaWcfService { [ServiceContract] public interface IService1 { [OperationContract] Task<string> CheckAsync();

[OperationContract] Task<string> GetDataWithoutCheckingTokenAsync(string parameter);

[OperationContract] Task<string> LoginAsync(string login, string password);

[OperationContract] Task LogoutAsync(string token);

[OperationContract] Task<string> GetCardAsync(Guid cardID, string cardTypeName = null); } }

В настоящем примере приводятся следующие методы WCF-сервиса:

  1. CheckAsync - посредством GET-запроса без аутентификации получает некий диагностический текст от сервиса платформы TESSA.

  2. GetDataWithoutCheckingTokenAsync - выполняет POST-запрос без аутентификации на метод тестового контроллера ServiceController из расширений, который возвращает переданную ему строку, заодно выполнив запрос к базе данных TESSA.

  3. LoginAsync - выполняет POST-запрос для создания сессии, возвращает токен созданной сессии.

  4. LogoutAsync - выполняет POST-запрос и закрывает сессию по указанному в параметре токену, который можно получить, открыв сессию методом LoginAsync.

  5. GetCardAsync - возвращает структуру объекта CardGetResponse для карточки с указанным идентификатором и опционально с заданным алиасом типа карточки; если алиас задан, то он помимо идентификатора дополнительно передаётся в запрос CardGetRequest.

    1. При этом сначала выполняется POST-запрос на создание сессии (аналогично LoginAsync), для этого используются параметры аутентификации в Web.config.

    2. Далее выполняется REST-запрос (POST) к тестовому контроллеру на получение структуры карточки по указанным идентификатору типа и алиасу типа карточки (параметры передаются через адресную строку). Также в этом запросе в HTTP-заголовках указывается header с именем "Tessa-Session" с токеном сессии в качестве значения (этот заголовок автоматически анализируется платформой в методах контроллеров).

    3. Проверяется актуальный тип карточки, полученный по указанному идентификатору, и если это тип "Currency" (валюта), то для него возвращается не полная структура карточки, а только название валюты, отображаемое пользователю.

    4. Перед выходом из метода в случае успешного выполнения или при наличии ошибок будет выполнен POST-запрос на закрытие сессии (аналогично методы LogoutAsync).

Описанные методы реализованы в классе WCF-сервиса Service1.svc. Замените содержимое файла Service1.svc.cs:

using System; using System.Net.Http; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json.Linq;

namespace TessaWcfService { public class Service1 : IService1 { public async Task<string> CheckAsync() => await ServiceHelper.SendAsync( HttpMethod.Get, "service");

public async Task<string> GetDataWithoutCheckingTokenAsync(string parameter) => await ServiceHelper.SendAsync( HttpMethod.Post, "service/GetDataWithoutCheckingToken", ServiceHelper.FromText(parameter));

public async Task<string> LoginAsync(string login, string password) => await ServiceHelper.SendAsync( HttpMethod.Post, "service/Login", ServiceHelper.FromJsonObject( new IntegrationLoginParameters { Login = login, Password = password }));

public async Task LogoutAsync(string token) => await ServiceHelper.SendAsync( HttpMethod.Post, "service/Logout", ServiceHelper.FromText(token), returnsVoid: true);

public async Task<string> GetCardAsync(Guid cardID, string cardTypeName = null) { string token = await this.LoginAsync(ServiceHelper.Login, ServiceHelper.Password);

try { string jsonResponse = await ServiceHelper.SendAsync( HttpMethod.Post, $"service/card/{cardID}" + (string.IsNullOrEmpty(cardTypeName) ? null : $"?type={HttpUtility.UrlEncode(cardTypeName)}"), tokenHeader: token);

dynamic response = JObject.Parse(jsonResponse); if ((string) response.Card.TypeName == "Currency") { string currencyCaption = response.Card.Sections.Currencies.Fields.Caption; return currencyCaption; }

return response; } finally { await this.LogoutAsync(token); } } } }

Убедитесь, что сервис TESSA доступен по параметрам, указанным в файле Web.config. Теперь выберите в дереве Solution Explorer (в Visual Studio ) файл Service1.svc или Service1.svc.cs, и нажмите F5. Это запустит веб-сервис в IIS Express, и также откроет WCF Test Client - приложение для тестирования WCF-сервисов. В нём мы можем проверить работу сервисов.

Дважды кликните на методе (который не отмечен красным крестом - не обращайте внимание на такие методы). Например, на скриншоте указан метод GetCard (это метод IService1.GetCardAsync), и для него в параметре cardID задан идентификатор карточки валюты RUS, которая присутствует в типовой поставке платформы. Метод вернул строку с отображаемым названием валюты, которую он получил от веб-сервиса TESSA.

Tip

При возникновении ошибок можно поставить точку останова в коде сервиса Service1.svc.cs, чтобы выяснить причину ошибки.

Т.о. можно создавать как WCF-приложения, так и любые другие серверные приложения (веб-сервисы, фоновые сервисы, консольные утилиты), которые обращаются к веб-сервису TESSA, открывают сессию и выполняют по ней работу. Такие приложения могут быть написаны на платформах .NET Framework или .NET Core, в этом случае можно использовать API HttpClient аналогичным образом. Возможно аналогичное использование REST API на других языках и платформах.

Back to top