Table of Contents

Миграция плагинов веб-сервера на 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)

  1. Контракт вызова меняется с GET с параметрами строки запроса на POST с телом JSON — обновите клиентов.
  2. Тип и namespace базового контроллера изменились; [Route]/[ApiController] больше не задаются вручную.
  3. Тип сервиса авторизации конструктора меняется с IPolynomAuthorizationService на IPluginAuthorizationService.
  4. Прямые ссылки на внутренние сборки продукта (Ascon.Polynom.Web.Api.Core и др.) запрещены — плагин ссылается только на пакеты SDK.
  5. Возвращаемые модели должны быть описаны интерфейсами с [JsonDerivedType].

Рекомендации по миграции

  1. Замените <Reference> на сборки из каталога веб-сервера одной PackageReference на метапакет SDK.
  2. Переведите контроллер на базовый класс из Plugins.AspNetCore; уберите [Route]/[ApiController], добавьте [ApiVersion("1")], [Authorize], [RequestValidation].
  3. Замените IPolynomAuthorizationService на IPluginAuthorizationService; в начале методов вызывайте CheckAuthorization().
  4. Переведите методы на [HttpPost(nameof(Метод))] с DTO-контрактом; параметры строки запроса перенесите в свойства запроса.
  5. Опишите интерфейсы запроса/ответа с [JsonPolymorphic]/[JsonDerivedType].
  6. Добавьте ServiceRegistrar и валидаторы FluentValidation.
  7. Убедитесь, что имя сборки оканчивается на .Plugin, а XML-документация включена.
  8. Соберите плагин и проверьте появление методов в 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 рядом со сборкой

См. также