Перейти к основному содержимому

Использование библиотеки ApiClient для написания кода вызовов сервисов

Для упрощения написания кода вызовов сервисов Платформы и сервисов бизнес-логики используется библиотека ApiClient. Эта библиотека инкапсулирует действия, связанные с поиском сервиса, получением и передачей токена доступа, обработкой результатов вызова и ошибок. Библиотека ApiClient включена в пакет NuGet ASE.MD.Platform.Utils.ApiClient.

Эта статья содержит рекомендации об изменении кода сервисов для вызываемой и вызывающей сторон.

Вызываемая сторона

Чтобы взаимодействовать с сервисом Xyz,

  1. Для сервиса Xyz проверьте значение атрибута DiscoveryAttribute, который обычно находится в файле Startup.cs или Program.cs.

    Следующий блок кода демонстрирует применение атрибута Discovery к сборке. Атрибут указывает имя сервиса ("xyzservice"), версию API ("3.2.1") и версию реализации ("1.0"):

    [assembly: Discovery("xyzservice", "3.2.1", "1.0")]
  2. Измените контроллер, содержащий методы, которые вы планируете вызывать.

    Следующий блок кода демонстрирует контроллер XyzController, который наследуется от базового класса ControllerBase. Метод GetAllUsersSystem имеет атрибут [HttpPost], что означает, что этот метод обрабатывает HTTP POST-запросы. Атрибут [ScopeRequirement("xyz:users:read")] требует наличия разрешения для выполнения метода.

    public class XyzController : ControllerBase
    {
    [HttpPost]
    [ScopeRequirement("xyz:users:read")]
    public UserListApiResult GetAllUsersSystem()
    {
    // ...
    }
    }
  3. Создайте интерфейс с методами, доступными для вызова.

    Следующий блок кода демонстрирует создание интерфейса IXyzIdentityServiceAsync, который декорирован атрибутом ServiceName. Атрибут ServiceName указывает имя сервиса ("xyzservice") и версию API ("3.2.1"). Интерфейс содержит асинхронный метод GetAllUsersSystem, который возвращает результат типа Task<UserListApiResult>.

    using ASE.MD.Platform.Infrastructure.Xyz.ApiParams.Identity;
    using ASE.MD.Platform.Infrastructure.Xyz.ApiResults.Authentication;
    using ASE.MD.Platform.Infrastructure.Xyz.ApiResults.Identity;
    using ASE.MD.Platform.Utils.ApiClient.Models.Attributes;
    using ASE.MD.Platform.Utils.ModelsBase.ApiResults;
    using System.Threading.Tasks;

    [ServiceName("xyzservice", "3.2.1")]
    public interface IXyzIdentityServiceAsync
    {
    Task<UserListApiResult> GetAllUsersSystem();
    }
  4. Наследуйте разрабатываемый контроллер от созданного интерфейса.

    Следующий блок кода демонстрирует наследование контроллера XyzController от интерфейса IXyzIdentityServiceAsync и реализацию его методов. Теперь метод api/system будет выдавать команду для вызова метода IXyzIdentityServiceAsync.GetAllUsersSystem.

    public class XyzController : IXyzIdentityServiceAsync, ControllerBase
    {
    // Реализуйте методы интерфейса
    }

Вызывающая сторона

Чтобы вызвать метод сервиса Платформы, установите библиотеку ASE.MD.Platform.Utils.ApiClient.

Следующий блок кода демонстрирует подключение необходимых пространств имен для работы с библиотекой ASE.MD.Platform.Utils.ApiClient. Пространство имен Interfaces предоставляет доступ к основным интерфейсам библиотеки, а пространство имен Extensions добавляет необходимые расширения для работы с ними.

using ASE.MD.Platform.Utils.ApiClient.Interfaces;
using ASE.MD.Platform.Utils.ApiClient.Extensions;

При вызове метода сервиса библиотека ASE.MD.Platform.Utils.ApiClient автоматически определяет необходимые области разрешений доступа и получает токен доступа одним из следующих способов:

  • Поиск действующего токена в кэше токенов. Если этот вызов не является первым, токен уже может находиться в кэше.
  • Использование токена пользователя (механизм аутентификации Delegation). Токен пользователя используется для получения токена сервиса.
  • Использование токена без данных о пользователе (механизм аутентификации Client Credentials). Этот способ применим, если метод вызываемого сервиса допускает использование такого токена.

Токен пользователя для предоставления разрешения может быть найден следующими способами:

  • Проверка наличия токена в текущем контексте HTTP-запроса.
  • Использование механизма аутентификации Password. Если токен не найден, производится запрос к сервису аутентификации Identity с использованием имени учетной записи и пароля пользователя.

Если во время попытки получения токена возникает ошибка, библиотека ApiClient записывает ошибку в журнал событий и пробует получить токен другим способом. Если все способы исчерпаны, но токен не получен, библиотека ApiClient выбрасывает исключение.

Использование интерфейса IWebApiDirectClient

Для упрощения настройки вызова сервисов Платформы можно использовать интерфейс IWebApiDirectClient.

Следующий блок кода демонстрирует регистрацию сервисов IXyzIdentityService и ISomeOtherInterface, использующих IWebApiDirectClient, в контейнере зависимостей в методе ConfigureServices файла Startup.cs.

services.AddWebApiDirectClient<IXyzIdentityService>(Configuration, [ProxyGeneratorOptions - не обязательно]);
services.AddWebApiDirectClient<ISomeOtherInterface>(Configuration, [ProxyGeneratorOptions - не обязательно]);

Аргументы универсального типа используются для различения настроек разных клиентов. Например, если используется механизм аутентификации Password, библиотека ApiClient ищет соответствующие учетные данные по аргументам универсального типа.

Чтобы использовать возможности интерфейса IWebApiDirectClient в коде контроллера, необходимо внедрить соответствующие сервисы через конструктор класса контроллера.

Следующий блок кода демонстрирует внедрение зарегистрированных сервисов в контроллер и их использование для выполнения асинхронных запросов к сервисам. Метод PostAsync позволяет отправлять POST-запросы к указанному сервису с передачей параметров и заголовков запроса.

public class MyController : Controller
{
private readonly IWebApiDirectClient<IXyzIdentityServiceAsync> _directClient1;
private readonly IWebApiDirectClient<ISomeOtherInterface> _directClient2;

public MyController(
IWebApiDirectClient<IXyzIdentityServiceAsync> directClient1,
IWebApiDirectClient<ISomeOtherInterface> directClient2)
{
_directClient1 = directClient1;
_directClient2 = directClient2;
}

// Пример вызова метода с указанием имени целевого сервиса
public async Task<IActionResult> ExampleCall()
{
var responseWithServiceName = await _directClient1.PostAsync(
"MyTargetService",
"MethodName",
someMethodParameter,
someActionWithRequestHeaders = null);

return Ok(responseWithServiceName);
}

// Пример вызова метода с использованием URI
public async Task<IActionResult> AnotherExampleCall()
{
var uri = new Uri("https://example.com/api/Home/MyMethod");
var responseWithUrl = await _directClient1.PostAsync(
uri,
"api/Home/MyMethod",
someMethodParameter,
someActionWithRequestHeaders = null);

return Ok(responseWithUrl);
}
}

При вызове метода proxy.GetAllUsersSystem() происходит обращение к соответствующему методу на стороне сервиса IXyzIdentityServiceAsync.GetAllUsersSystem(). Метод сервиса должен соответствовать вашему запросу. Для проверки методов сервиса используйте API-методы, такие как /api/System.

Следующий блок кода демонстрирует добавление поставщика токенов в Startup.cs:

services.AddIdentityTokenProvider(Configuration, [IdentityTokenFetchingOptions]);

Следующий блок кода демонстрирует использование токена в контроллере. Параметр в FetchTokenAsync соответствует названию сервиса в конфигурации (ApiConfiguration:PasswordGrantCredentials:0:TarsetServiceName), откуда библиотека ApiClient получает учетные данные для запроса токена.

public class MyController : Controller
{
private readonly IUsersTokenProvider _usersTokenProvider;

public MyController(IUsersTokenProvider usersTokenProvider)
{
_usersTokenProvider = usersTokenProvider;
}

public async Task<IActionResult> FetchToken()
{
string identityToken = await _usersTokenProvider.FetchTokenAsync("IXyzIdentityService");
return Ok(identityToken);
}
}

Следующий блок кода демонстрирует пример конфигурации для получения токена.

{
"ApiConfiguration": {
"IdentityProvider": "local", // или другой провайдер, например "xyzprovider" или "gitprovider"
"PasswordGrantCredentials": [
{
"TargetServiceName": "IXyzIdentityService", // Данные для сервиса IXyzIdentityService
"UserLogin": "Admin",
"UserPassword": "Admin123$56"
},
{
"TargetServiceName": null, // Данные для остальных сервисов
"UserLogin": "Admin",
"UserPassword": "Admin123$56"
}
]
}
}

Настройка конфигурации вызывающего сервиса

Согласно концепции межсервисного взаимодействия в Платформе при вызове методов сервиса вызывающий сервис использует клиента. Поскольку вызывающий сервис может вызывать несколько сервисов, должны быть определены имена клиентов, которые будут использованы при вызове каждого сервиса. Для этой цели необходимо изменить конфигурацию вызывающего сервиса в файле appsettings.json.

Следующий блок кода демонстрирует пример конфигурационных параметров для прокси-клиента.

"ClientProxyConfig": {
"RequestTimeoutSeconds": 120
}

Следующий блок кода демонстрирует пример файла конфигурации.

"ApiConfiguration": {
Провайдер, для которого указаны учетные данные
"IdentityProvider": "local", "xyzprovider" или "gitprovider",
"PasswordGrantCredentials": [
{
Данные для получения токена с использованием типа предоставления разрешения Password при обращении к сервису IXyzIdentityService
"TargetServiceName": "IXyzIdentityService",
"UserLogin": "Admin",
"UserPassword": "Admin123$56"
},
{
Данные для получения токена с использованием типа предоставления разрешения Password при обращении к остальным сервисам
"TargetServiceName": null,
"UserLogin": "Admin",
"UserPassword": "Admin123$56"
},
],

"ApiName": "Название клиента и название API-ресурса, если клиент защищает свои методы. Если значение не задано, используется значение DiscoveryAttribute. Если задана секция ClientSelector, используются данные из этой секции",

"ClientSecret": "Секрет",
// Выбор клиента в зависимости от вызываемого сервиса. Если секции нет, используется значение ApiName
"ClientSelector": [
{
"TargetServiceName": "", // если имя сервиса не указано в запросе или нужного имени нет в списке, используется значение из TargetServiceName
"ClientId": "DefaultIdentityClient"
},
{
"TargetServiceName": "IXyzIdentityService",
"ClientId": "client1"
},
{
"TargetServiceName": "SomeService",
"ClientId": "client2"
}
],
// связь клиента и секрета клиента. Если секций ClientSelector и ClientSecrets нет, используется ClientSecret
"ClientSecrets": [
{
"ClientId": "client1",
"Secret": "secret1"
},
{
"ClientId": "client2",
"Secret": "secret2"
}
],
},

Разделы ClientSecret и ApiName считаются устаревшими и оставлены для обеспечения обратной совместимости. Они используются только в случае отсутствия новых разделов ClientSecrets и ClientSelector.

Раздел PasswordGrantCredentials содержит учетные данные пользователя для работы с сервисами с использованием механизма аутентификации Password. Рекомендуется отказаться от использования этого способа и использовать механизм аутентификации Client Credentials, который поддерживается новыми разделами ClientSecrets и ClientSelector.

Часть разделов в конфигурационном файле являются необязательными. Порядок поиска и выбора параметров конфигурации следующий:

  1. Поиск имени клиента.

    Если секция ClientSelector присутствует, ищется запись с именем целевого сервиса TargetServiceName. Если в секции ClientSelector отсутствует нужная запись, используется значение поля ApiName.

  2. Поиск секрета клиента.

    Если секция ClientSelector присутствует, проверяются записи в секции ClientSecrets. Если секция ClientSelector отсутствует, используется значение из поля ClientSecret.

  3. Поиск пароля пользователя.

    В секции PasswordGrantCredentials ищется запись с соответствующим именем целевого сервиса TargetServiceName. Если соответствующая запись не найдена, используется запись с пустым значением TargetServiceName.