Создание плагина для веб-сервера
Для веб-сервера ПОЛИНОМ:MDM предусмотрена возможность создания плагинов. Плагин расширяет
WEB API собственными методами, регистрирует свои сервисы и использует ядро ПОЛИНОМ:MDM через
сессию Polynom.Api.ISession.
Общая информация
Плагин представляет собой отдельную сборку с суффиксом .Plugin.dll (тип «библиотека классов»).
При старте хост сканирует каталог модулей веб-сервера и каталог расширений
{CommonAppData}/ASCON/Polynom/WebServer/Plugins, находит сборки по маске *.Plugin.dll
и подключает их автоматически.
При подключении плагина хост:
- загружает сборку и находит класс, реализующий
IPluginServiceRegistrar; - создаёт его через
Activator.CreateInstanceи вызываетRegisterServices(IServiceCollection); - добавляет сборку в маршрутизацию MVC — методы плагина становятся доступны по адресам
/api/v1/{controller}/{method}; - подключает XML-документацию
*.Plugin.xmlк Swagger.
Плагин подключается к SDK одной ссылкой на метапакет Ascon.Polynom.Web.Api.Plugins.
Метапакет агрегирует транзитивные пакеты Plugins.Contracts, Plugins.Core,
Plugins.AspNetCore и Plugins.Kernel.
Подключение SDK
Добавьте в файл проекта одну ссылку на метапакет:
<ItemGroup>
<PackageReference Include="Ascon.Polynom.Web.Api.Plugins" Version="24.1.*" />
</ItemGroup>
Пакет публикуется во внутренний NuGet-фид. В репозитории SDK также поставляется локальный фид
SDK/Web/Packages/dotnet. Пример конфигурации nuget.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- Подключите корпоративный NuGet-фид с пакетами SDK. -->
<add key="sdk-local-feed" value="../../Packages/dotnet" />
</packageSources>
</configuration>
Прикладные зависимости (FluentValidation, FluentValidation.DependencyInjectionExtensions,
Asp.Versioning.Mvc) плагин объявляет самостоятельно.
Структура проекта
Ascon.Polynom.{Feature}.Plugin/
├── Ascon.Polynom.{Feature}.Plugin.csproj
├── ServiceRegistrar.cs // IPluginServiceRegistrar
├── Controllers/ // контроллеры : WebApiPluginController
├── Interfaces/
│ ├── Requests/ // I{Action}{Entity}Request
│ └── Responses/ // I{Action}{Entity}Response
├── Models/
│ ├── Requests/ // {Action}{Entity}Request
│ └── Responses/ // {Action}{Entity}Response
├── Services/ // бизнес-логика плагина
└── Validation/
└── Requests/ // FluentValidation-валидаторы
Обязательно. Имя сборки (
AssemblyName) должно оканчиваться на.Plugin, иначе хост не обнаружит плагин. XML-документацию включают черезDocumentationFile— она попадает в Swagger.
Файл проекта
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup>
<RootNamespace>Ascon.Polynom.Feature</RootNamespace>
<AssemblyName>Ascon.Polynom.Feature.Plugin</AssemblyName>
<DocumentationFile>$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ascon.Polynom.Web.Api.Plugins" Version="24.1.*" />
<PackageReference Include="Asp.Versioning.Mvc" Version="8.*" />
<PackageReference Include="FluentValidation" Version="12.*" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.*" />
</ItemGroup>
</Project>
Контракты запроса и ответа
Для каждого метода объявляются интерфейс и класс. В интерфейсе задаётся полиморфизм
System.Text.Json, иначе десериализация контрактов не работает.
Интерфейс запроса:
using System.Text.Json.Serialization;
using Ascon.Polynom.Web.Api.Plugins.Contracts.Interfaces.Requests.Base;
using Ascon.Polynom.Feature.Models.Requests;
namespace Ascon.Polynom.Feature.Interfaces.Requests;
[JsonPolymorphic(
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType),
JsonDerivedType(typeof(GetStatisticsRequest))]
public interface IGetStatisticsRequest : IWebApiRequest
{
string? ReferenceName { get; set; }
}
Класс запроса наследует WebApiRequest:
using System.Text.Json.Serialization;
using Ascon.Polynom.Web.Api.Plugins.Contracts.Requests.Base;
namespace Ascon.Polynom.Feature.Models.Requests;
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
public class GetStatisticsRequest : WebApiRequest, IGetStatisticsRequest
{
public string? ReferenceName { get; set; }
}
Интерфейс ответа описывает возвращаемые данные, конкретный класс их реализует. Имена типов
строятся по шаблону {Action}{Entity}Request/{Action}{Entity}Response.
Контроллер
Контроллер наследует WebApiPluginController (Ascon.Polynom.Web.Api.Plugins.AspNetCore).
Базовый класс предоставляет маршрут api/v{version:apiVersion}/[controller] и [ApiController] —
дублировать их не нужно. Атрибуты [Authorize], [ApiVersion("1")] и [RequestValidation]
контроллер задаёт явно: плагинный базовый класс их не наследует.
using Ascon.Polynom.Web.Api.AspNetCore; // [RequestValidation] (поставляет SDK)
using Ascon.Polynom.Web.Api.Plugins.AspNetCore; // WebApiPluginController
using Ascon.Polynom.Web.Api.Plugins.Core; // IPluginAuthorizationService
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Ascon.Polynom.Feature.Controllers;
[ApiVersion("1")]
[Authorize]
[RequestValidation]
public class SampleController : WebApiPluginController
{
private readonly StatisticsService _statisticsService;
public SampleController(
IPluginAuthorizationService authorizationService,
StatisticsService statisticsService,
IServiceProvider services) : base(authorizationService, services)
{
_statisticsService = statisticsService;
}
[HttpPost(nameof(GetStatistics))]
public async Task<ActionResult<IGetStatisticsResponse>> GetStatistics(
IGetStatisticsRequest request,
CancellationToken cancellationToken)
{
CheckAuthorization();
var session = await GetPolynomApiSession(cancellationToken);
if (session is null)
return Unauthorized();
var result = await _statisticsService.GetStatisticsAsync(session, request, cancellationToken);
return new JsonResult(result);
}
}
Имя действия задаётся в атрибуте [HttpPost(nameof(Метод))] — используется Action-Based JSON RPC:
действие совпадает с именем метода.
Сервис и работа с сессией
Бизнес-логика выносится в сервис. Сервис получает открытую сессию Polynom.Api.ISession
параметром и не создаёт её самостоятельно.
using Ascon.Polynom.Api;
namespace Ascon.Polynom.Feature.Services;
public class StatisticsService
{
public Task<IGetStatisticsResponse> GetStatisticsAsync(
ISession session,
IGetStatisticsRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
// session.Objects.AllReferences, session.Objects.RootConcepts и т. д.
throw new NotImplementedException();
}
}
Регистрация сервисов
DI-регистрация выполняется классом, реализующим IPluginServiceRegistrar. Хост создаёт его через
Activator.CreateInstance, поэтому у класса должен быть конструктор без параметров.
using Ascon.Polynom.Web.Api.Plugins.Core;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
namespace Ascon.Polynom.Feature;
public class ServiceRegistrar : IPluginServiceRegistrar
{
public void RegisterServices(IServiceCollection services)
{
services.AddValidatorsFromAssembly(typeof(ServiceRegistrar).Assembly);
services.AddScoped<StatisticsService>();
}
}
Валидация
Валидаторы FluentValidation наследуют AbstractValidator<I{Action}{Entity}Request> и подключаются
автоматически через AddValidatorsFromAssembly. Валидация выполняется, если на контроллере задан
атрибут [RequestValidation].
using FluentValidation;
namespace Ascon.Polynom.Feature.Validation.Requests;
public class GetStatisticsRequestValidator : AbstractValidator<IGetStatisticsRequest>
{
public GetStatisticsRequestValidator()
{
RuleFor(x => x.ReferenceName).MaximumLength(200);
}
}
Авторизация
Доступ к методам ограничивается атрибутом [Authorize]. В начале каждого метода вызывается
CheckAuthorization(). Сессия ядра для текущего пользователя возвращается методом
GetPolynomApiSession(cancellationToken); если сессия не получена, метод возвращает ответ 401
(или бросает UnauthorizedException, если подключена библиотека Polynom.Web.Exceptions).
Сборка и подключение
Соберите проект:
dotnet build -c ReleaseРазместите полученные файлы
Ascon.Polynom.{Feature}.Plugin.dllиAscon.Polynom.{Feature}.Plugin.xmlв каталоге плагинов хоста (каталог модулей веб-сервера или{CommonAppData}/ASCON/Polynom/WebServer/Plugins).Перезапустите веб-сервер — плагин подключится автоматически, его методы появятся в Swagger.
Пример из SDK
Полный разобранный пример плагина приведён в статье Разработка плагинов для веб-сервера (пример).
Исходный код примера расположен по пути SDK/Web/Samples/Server/Samples.WebApi.Plugin.
Возможные ошибки
| Симптом | Причина | Решение |
|---|---|---|
| Плагин не обнаружен | имя сборки без суффикса .Plugin |
задайте AssemblyName с суффиксом .Plugin |
ServiceRegistrar не создан |
конструктор с параметрами | сделайте конструктор без параметров |
| Метод не появился в Swagger | нет файла *.Plugin.xml рядом с DLL |
включите DocumentationFile и разместите XML рядом со сборкой |
| Ошибка десериализации запроса | у контракта нет [JsonDerivedType] |
добавьте полиморфизм System.Text.Json на интерфейс |
| Валидаторы не срабатывают | нет [RequestValidation] или AddValidatorsFromAssembly |
добавьте атрибут и регистрацию валидаторов |
401 при вызове метода |
не вызван CheckAuthorization() или пустая сессия |
приведите контроллер к шаблону выше |
| Ошибка компиляции при подключении SDK | ссылка на внутренний проект вместо пакета | подключите метапакет Ascon.Polynom.Web.Api.Plugins |