Миграция плагинов веб-сервера на SDK
Статья описывает переход плагина веб-сервера ПОЛИНОМ:MDM со старого плагинного API на новый
SDK плагинов, поставляемый метапакетом Ascon.Polynom.Web.Api.Plugins.
Кому адресовано
Владельцам плагинов, собранных на прежнем плагинном API:
- проект ссылался напрямую на сборки из каталога веб-сервера —
Ascon.Polynom.Api.dll,Ascon.Polynom.Web.Api.Core.dll,Ascon.Polynom.Web.Api.Plugins.Basic.dll; - контроллер наследовал
WebApiPluginControllerизAscon.Polynom.Web.Api.Plugins.Basicи использовалIPolynomAuthorizationService; - методы объявлялись как
[HttpGet(...)]с параметрами строки запроса.
Что изменилось
| Область | Было | Стало |
|---|---|---|
| Подключение SDK | <Reference> на DLL из каталога веб-сервера |
одна PackageReference на Ascon.Polynom.Web.Api.Plugins |
| Базовый контроллер | WebApiPluginController из Plugins.Basic |
WebApiPluginController из Plugins.AspNetCore |
| Сервис авторизации | IPolynomAuthorizationService |
IPluginAuthorizationService |
Маршрут и [ApiController] |
задавались в контроллере | наследуются из базового класса |
| Версионирование и валидация | не задавались | [ApiVersion("1")], [RequestValidation] |
| Способ вызова | [HttpGet(...)], параметры строки запроса |
[HttpPost(nameof(Метод))], DTO-контракт (Action-Based JSON RPC) |
| Контракты | модели без полиморфизма | I{Action}{Entity}Request/Response + [JsonPolymorphic]/[JsonDerivedType] |
| Регистрация сервисов | отсутствовала | ServiceRegistrar : IPluginServiceRegistrar |
| Валидация | отсутствовала | FluentValidation через AddValidatorsFromAssembly |
Шаг 1. Подключение SDK
Было — прямые ссылки на сборки продукта:
<ItemGroup>
<Reference Include="Ascon.Polynom.Api">
<HintPath>..\..\..\..\..\Bin\WebServer\Ascon.Polynom.Api.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Ascon.Polynom.Web.Api.Core">
<HintPath>..\..\..\..\..\Bin\WebServer\Ascon.Polynom.Web.Api.Core.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Ascon.Polynom.Web.Api.Plugins.Basic">
<HintPath>..\..\..\..\..\Bin\WebServer\Ascon.Polynom.Web.Api.Plugins.Basic.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
Стало — одна ссылка на метапакет SDK:
<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>
Убедитесь, что подключён NuGet-фид с пакетами SDK (в репозитории SDK доступен локальный фид
SDK/Web/Packages/dotnet), и включена XML-документация:
<DocumentationFile>$(AssemblyName).xml</DocumentationFile>.
AssemblyName обязан оканчиваться на .Plugin.
Шаг 2. Базовый класс контроллера
Было:
using Ascon.Polynom.Web.Api.Core.Interfaces;
using Ascon.Polynom.Web.Api.Plugins.Basic;
public class SampleController : WebApiPluginController
{
public SampleController(IPolynomAuthorizationService authorizationService,
IServiceProvider services) : base(authorizationService, services)
{
}
}
Стало:
using Ascon.Polynom.Web.Api.Plugins.AspNetCore;
using Ascon.Polynom.Web.Api.Plugins.Core;
[ApiVersion("1")]
[Authorize]
[RequestValidation]
public class SampleController : WebApiPluginController
{
public SampleController(IPluginAuthorizationService authorizationService,
IServiceProvider services) : base(authorizationService, services)
{
}
}
Атрибуты [ApiController] и [Route("[controller]")] удаляются — они наследуются из базового
класса. Вместо них добавляются [ApiVersion("1")], [Authorize] и [RequestValidation].
Шаг 3. Метод: переход на Action-Based JSON RPC
Было — параметры строки запроса через [HttpGet]:
[HttpGet(nameof(GetStatistics))]
public async Task<ActionResult<CountInfo>> GetStatistics(
string referenceName, CancellationToken cancellationToken)
{
var session = await GetPolynomApiSession(cancellationToken);
if (session is null)
return new JsonResult(new CountInfo());
// ... сбор статистики ...
}
Стало — DTO-контракт через [HttpPost]:
[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]; имя действия совпадает с именем метода.
Шаг 4. Контракты запроса и ответа
Вместо параметров метода объявляются интерфейс и класс. На интерфейсе задаётся полиморфизм:
[JsonPolymorphic(
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType),
JsonDerivedType(typeof(GetStatisticsRequest))]
public interface IGetStatisticsRequest : IWebApiRequest
{
string? ReferenceName { get; set; }
}
Класс наследует WebApiRequest, для запрета неизвестных полей применяется
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)].
Шаг 5. Сервисы и DI
Добавьте класс, реализующий IPluginServiceRegistrar (конструктор без параметров):
public class ServiceRegistrar : IPluginServiceRegistrar
{
public void RegisterServices(IServiceCollection services)
{
services.AddValidatorsFromAssembly(typeof(ServiceRegistrar).Assembly);
services.AddScoped<StatisticsService>();
}
}
Шаг 6. Валидация
Валидаторы FluentValidation подключаются автоматически через AddValidatorsFromAssembly
и выполняются при наличии [RequestValidation] на контроллере:
public class GetStatisticsRequestValidator : AbstractValidator<IGetStatisticsRequest>
{
public GetStatisticsRequestValidator()
{
RuleFor(x => x.ReferenceName).MaximumLength(200);
}
}
Критические изменения (Breaking Changes)
- Контракт вызова меняется с
GETс параметрами строки запроса наPOSTс телом JSON — обновите клиентов. - Тип и namespace базового контроллера изменились;
[Route]/[ApiController]больше не задаются вручную. - Тип сервиса авторизации конструктора меняется с
IPolynomAuthorizationServiceнаIPluginAuthorizationService. - Прямые ссылки на внутренние сборки продукта (
Ascon.Polynom.Web.Api.Coreи др.) запрещены — плагин ссылается только на пакеты SDK. - Возвращаемые модели должны быть описаны интерфейсами с
[JsonDerivedType].
Рекомендации по миграции
- Замените
<Reference>на сборки из каталога веб-сервера однойPackageReferenceна метапакет SDK. - Переведите контроллер на базовый класс из
Plugins.AspNetCore; уберите[Route]/[ApiController], добавьте[ApiVersion("1")],[Authorize],[RequestValidation]. - Замените
IPolynomAuthorizationServiceнаIPluginAuthorizationService; в начале методов вызывайтеCheckAuthorization(). - Переведите методы на
[HttpPost(nameof(Метод))]с DTO-контрактом; параметры строки запроса перенесите в свойства запроса. - Опишите интерфейсы запроса/ответа с
[JsonPolymorphic]/[JsonDerivedType]. - Добавьте
ServiceRegistrarи валидаторы FluentValidation. - Убедитесь, что имя сборки оканчивается на
.Plugin, а XML-документация включена. - Соберите плагин и проверьте появление методов в Swagger после перезапуска веб-сервера.
Возможные ошибки
| Симптом | Причина | Решение |
|---|---|---|
| Плагин не обнаружен | имя сборки без суффикса .Plugin |
задайте AssemblyName с суффиксом .Plugin |
Ошибка компиляции типа из Polynom.Web.Api.* |
остался using на внутренний namespace |
замените namespace на Ascon.Polynom.Web.Api.Plugins.* |
| Ошибка десериализации запроса | нет [JsonDerivedType] |
добавьте полиморфизм на контракт |
| Валидаторы не срабатывают | нет [RequestValidation] или AddValidatorsFromAssembly |
добавьте атрибут и регистрацию валидаторов |
401 при вызове метода |
не вызван CheckAuthorization() или пустая сессия |
приведите метод к шаблону |
| Метод не появился в Swagger | нет файла *.Plugin.xml рядом с DLL |
включите DocumentationFile и разместите XML рядом со сборкой |