| 1 | # Wissance WebApiToolkit — полный стек слоёв (fundamental → operational) |
| 2 | |
| 3 | Источник: [Wissance/WebApiToolkit](https://github.com/Wissance/WebApiToolkit), README и Wiki. |
| 4 | |
| 5 | ## Назначение KB |
| 6 | Эта KB-страница описывает **все слои**, которые нужны агентам, чтобы **проектировать, собирать, разворачивать и сопровождать** REST backend на базе WebApiToolkit. |
| 7 | |
| 8 | **База идеи** (из README): библиотека позволяет поднять CRUD REST API «почти одной строкой» за счёт: |
| 9 | - автогенерации контроллеров (assembly on-the-fly) |
| 10 | - вынесения логики в **Manager** слой |
| 11 | - стандартизации ответов/ошибок и query-поведения |
| 12 | |
| 13 | README: https://raw.githubusercontent.com/Wissance/WebApiToolkit/master/README.md |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## L0 — Fundamental (концептуальные основы) |
| 18 | |
| 19 | ### L0.1 Architectural principle: Controller ≈ thin, Manager ≈ core |
| 20 | - Контроллеры (REST/gRPC/SignalR) должны быть **тонкими адаптерами транспорта**. |
| 21 | - Вся прикладная логика и работа с хранилищем концентрируется в **Manager**. |
| 22 | |
| 23 | README заявляет общий контракт через `IModelManager` и возможность переиспользовать один Manager для разных транспортов. |
| 24 | |
| 25 | ### L0.2 Standardization principle |
| 26 | Цель — **одинаковое поведение** у всех CRUD эндпоинтов: |
| 27 | - единый формат ответа/ошибок |
| 28 | - единые paging/sorting/filter правила |
| 29 | - единые bulk правила |
| 30 | |
| 31 | (в README: “Output of all REST methods is standardize”, “Unified error format out of the box”, paging/sorting/filter, bulk) |
| 32 | |
| 33 | --- |
| 34 | |
| 35 | ## L1 — Library/Package layer (что подключать) |
| 36 | |
| 37 | ### L1.1 Основные пакеты |
| 38 | По README (NuGet бейджи): |
| 39 | - `Wissance.WebApiToolkit.Core` — базовые контракты/контроллеры/менеджеры/утилиты |
| 40 | - `Wissance.WebApiToolkit.Ef` — EF-ориентированная реализация и DI entrypoints |
| 41 | - `Wissance.WebApiToolkit.AWS.S3` — S3 utilities (опционально) |
| 42 | |
| 43 | ### L1.2 Target frameworks |
| 44 | `Wissance.WebApiToolkit.Core` и `Wissance.WebApiToolkit.Ef` таргетят `net6.0;net8.0;net9.0`. |
| 45 | |
| 46 | --- |
| 47 | |
| 48 | ## L2 — Code-generation & Composition layer (как появляется контроллер) |
| 49 | |
| 50 | ### L2.1 On-the-fly generation |
| 51 | Контроллер генерируется в runtime (assembly) и подключается к MVC как application part. |
| 52 | |
| 53 | ### L2.2 DI entrypoints (точки входа) |
| 54 | Ключевые entrypoints (EF пакет): |
| 55 | - `Wissance.WebApiToolkit.Ef.Extensions.ServiceCollectionExtensions.AddSimplifiedAutoController(...)` |
| 56 | - `Wissance.WebApiToolkit.Ef.Extensions.ServiceCollectionExtensions.AddFullyConfiguredAutoController(...)` |
| 57 | |
| 58 | Обе функции делают 3 вещи: |
| 59 | - **регистрируют scoped manager** (через `EfBasedManagerFactory`) |
| 60 | - **генерируют controller assembly** (через `OnTheFlyServicesGenerator.GenerateController(...)`) |
| 61 | - **возвращают Assembly** |
| 62 | |
| 63 | Источник кода: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Ef/Extensions/ServiceCollectionExtensions.cs |
| 64 | |
| 65 | ### L2.3 Подключение в ASP.NET Core |
| 66 | Рекомендуемый шаблон (README): |
| 67 | 1) вызвать `services.AddSimplifiedAutoController<...>(...)` → получить `Assembly` |
| 68 | 2) `services.AddControllers().AddApplicationPart(assembly).AddControllersAsServices();` |
| 69 | |
| 70 | README: https://raw.githubusercontent.com/Wissance/WebApiToolkit/master/README.md |
| 71 | |
| 72 | --- |
| 73 | |
| 74 | ## L3 — Domain & Contract layer (что должен предоставить проект) |
| 75 | |
| 76 | ### L3.1 Entity identity contract |
| 77 | Для автоконтроллера есть generic constraints: |
| 78 | - `TObj : class, IModelIdentifiable` |
| 79 | - `TId : IComparable` |
| 80 | |
| 81 | Т.е. сущность должна реализовать контракт идентификации, а ключ быть сравнимым. |
| 82 | |
| 83 | Источник: `ServiceCollectionExtensions.cs` (см. L2.2). |
| 84 | |
| 85 | ### L3.2 Resource naming contract |
| 86 | Тебе нужен **стабильный `resourceName`** (строка), из которого строится имя сборки/контроллера и маршрутизация ресурса. |
| 87 | |
| 88 | --- |
| 89 | |
| 90 | ## L4 — Query semantics layer (paging/sorting/filter) |
| 91 | |
| 92 | ### L4.1 Read filter contract |
| 93 | Generic constraint: |
| 94 | - `TFilter : class, IReadFilterable` |
| 95 | |
| 96 | --- |
| 97 | |
| 98 | ## L5 — Application/Manager layer (ядро приложения) |
| 99 | |
| 100 | ### L5.1 Stable boundary |
| 101 | Менеджер — это boundary между транспортом и persistence/business. |
| 102 | |
| 103 | README: “support to work with any persistent storage (`IModelManager` interface)” |
| 104 | |
| 105 | ### L5.2 EF implementation path |
| 106 | EF путь подразумевает: |
| 107 | - `DbContext` |
| 108 | - EF-based manager factory: |
| 109 | - `EfBasedManagerFactory.CreateSimplifiedManager(...)` |
| 110 | - `EfBasedManagerFactory.CreateFullyDefinedManager(...)` |
| 111 | |
| 112 | Источник: `ServiceCollectionExtensions.cs` (см. L2.2). |
| 113 | |
| 114 | ### L5.3 Extensibility path (как агент расширяет поведение) |
| 115 | Чтобы агент мог “строить приложения”, в KB фиксируем стандартные варианты расширения: |
| 116 | - **Custom manager**: своя реализация `IModelManager` (или composition поверх EF менеджера) |
| 117 | - **ManagerConfiguration**: использовать `AddFullyConfiguredAutoController(...)` и `ManagerConfiguration` для тонкой настройки поведения менеджера |
| 118 | - **FilterFunc**: в simplified-варианте есть `filterFunc` для запрета/разрешения элементов (вход: объект; выход: bool) |
| 119 | |
| 120 | --- |
| 121 | |
| 122 | ## L6 — Persistence & Infrastructure layer |
| 123 | |
| 124 | ### L6.1 EF DbContext |
| 125 | Проект должен владеть: |
| 126 | - конфигурацией `DbContext` (connection string, migrations, provider) |
| 127 | - жизненным циклом/миграциями |
| 128 | |
| 129 | ### L6.2 Storage portability |
| 130 | Если нужна БД не EF — реализовать `IModelManager` для другого storage. |
| 131 | |
| 132 | --- |
| 133 | |
| 134 | ## L7 — API Surface layer (CRUD + Bulk) |
| 135 | |
| 136 | ### L7.1 CRUD surface |
| 137 | Контроллеры предоставляют `GET/POST/PUT/DELETE` в едином формате. |
| 138 | |
| 139 | ### L7.2 Bulk surface |
| 140 | README: bulk `Create/Update/Delete` на уровне контроллера и интерфейса. |
| 141 | |
| 142 | --- |
| 143 | |
| 144 | ## L8 — Cross-cutting layer (responses, errors, logging) |
| 145 | |
| 146 | ### L8.1 Unified response & error model |
| 147 | README заявляет унификацию ответов и ошибок. |
| 148 | |
| 149 | --- |
| 150 | |
| 151 | ## L9 — Security layer (обязательное поверх автогенерации) |
| 152 | Автогенерация CRUD **не** закрывает требования безопасности. |
| 153 | |
| 154 | --- |
| 155 | |
| 156 | ## L10 — Testing & Quality layer |
| 157 | |
| 158 | --- |
| 159 | |
| 160 | ## L11 — Delivery layer (build/release) |
| 161 | |
| 162 | --- |
| 163 | |
| 164 | ## L12 — Operational layer (deploy, run, support) |
| 165 | |
| 166 | --- |
| 167 | |
| 168 | ## L13 — Agent-facing “recipe” (что агент должен уметь сделать автоматически) |
| 169 | |
| 170 | --- |
| 171 | |
| 172 | ## API Contract (exact) |
| 173 | Это точная спецификация, выведенная из базовых контроллеров WebApiToolkit. |
| 174 | |
| 175 | ### Base routes |
| 176 | - **CRUD/read** контроллеры: `api/[controller]` |
| 177 | - задаётся атрибутом `[Route("api/[controller]")]` |
| 178 | - источник: `Wissance.WebApiToolkit.Core.Controllers.BasicReadController` и наследники |
| 179 | |
| 180 | Источник: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicReadController.cs |
| 181 | |
| 182 | - **Bulk** контроллеры: `api/bulk/[controller]` |
| 183 | - задаётся атрибутом `[Route("api/bulk/[controller]")]` |
| 184 | - источник: `Wissance.WebApiToolkit.Core.Controllers.BasicBulkCrudController` |
| 185 | |
| 186 | Источник: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicBulkCrudController.cs |
| 187 | |
| 188 | --- |
| 189 | |
| 190 | ### Endpoints: Read (list) |
| 191 | `GET /api/{ControllerName}` |
| 192 | |
| 193 | **Query parameters**: |
| 194 | - `page` (int?, default = 1, min = 1) |
| 195 | - `size` (int?, default = 25, min = 1) |
| 196 | - `sort` (string) — имя поля/свойства для сортировки |
| 197 | - `order` (string) — `asc` или `desc` (любой другой ввод → трактуется как `asc`) |
| 198 | - `additionalFilters` — параметры фильтрации, собираемые через `TFilter : IReadFilterable` → `IDictionary` |
| 199 | |
| 200 | Факты: |
| 201 | - значения по умолчанию: `page=1`, `size=25` (см. `PagingUtils`) |
| 202 | - порядок сортировки: если `order` пустой/неизвестный → `Ascending` (см. `SortOption`) |
| 203 | |
| 204 | Источники: |
| 205 | - `BasicReadController.ReadAsync(...)`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicReadController.cs |
| 206 | - `PagingUtils`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Utils/PagingUtils.cs |
| 207 | - `SortOption`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Data/SortOption.cs |
| 208 | - `IReadFilterable`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Data/IReadFilterable.cs |
| 209 | - `EmptyAdditionalFilters`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Data/EmptyAdditionalFilters.cs |
| 210 | |
| 211 | **Response body envelope**: `OperationResultDto<PagedDataDto<TRes>>` |
| 212 | - `OperationResultDto<T>` JSON содержит: |
| 213 | - `Success: bool` |
| 214 | - `Message: string` |
| 215 | - `Data: T` |
| 216 | - поле `Status: int` в JSON **не сериализуется** (помечено `[JsonIgnore]`), но HTTP status код выставляется в `HttpContext.Response.StatusCode = result.Status` |
| 217 | |
| 218 | Источники: |
| 219 | - `BasicReadController` (выставление status + возвращаемое тело): https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicReadController.cs |
| 220 | - `OperationResultDto`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Dto/OperationResultDto.cs |
| 221 | - `PagedDataDto`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Dto/PagedDataDto.cs |
| 222 | |
| 223 | **`PagedDataDto<T>` поля**: |
| 224 | - `Page: long` |
| 225 | - `Total: long` |
| 226 | - `Pages: long` |
| 227 | - `Data: IList<T>` |
| 228 | |
| 229 | --- |
| 230 | |
| 231 | ### Endpoints: Read (by id) |
| 232 | `GET /api/{ControllerName}/{id}` |
| 233 | |
| 234 | - `{id}` берётся из route (`[HttpGet("{id}")]`) |
| 235 | - Response: `OperationResultDto<TRes>` |
| 236 | - HTTP status выставляется по `result.Status` |
| 237 | |
| 238 | Источник: `BasicReadController.ReadByIdAsync(...)`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicReadController.cs |
| 239 | |
| 240 | --- |
| 241 | |
| 242 | ### Endpoints: CRUD |
| 243 | Базовые mutating-операции реализованы в `BasicCrudController` (на базе `api/[controller]`). |
| 244 | |
| 245 | - `POST /api/{ControllerName}` |
| 246 | - body: `TRes` |
| 247 | - response: `OperationResultDto<TRes>` |
| 248 | |
| 249 | - `PUT /api/{ControllerName}/{id}` |
| 250 | - body: `TRes` |
| 251 | - response: `OperationResultDto<TRes>` |
| 252 | |
| 253 | - `DELETE /api/{ControllerName}/{id}` |
| 254 | - response body: отсутствует (метод возвращает `Task`, но выставляет `HttpContext.Response.StatusCode`) |
| 255 | |
| 256 | Источник: `BasicCrudController`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicCrudController.cs |
| 257 | |
| 258 | --- |
| 259 | |
| 260 | ### Endpoints: Bulk |
| 261 | Базовые bulk-операции реализованы в `BasicBulkCrudController` (на базе `api/bulk/[controller]`). |
| 262 | |
| 263 | - `POST /api/bulk/{ControllerName}` |
| 264 | - body: `TRes[]` |
| 265 | - response: `OperationResultDto<TRes[]>` |
| 266 | |
| 267 | - `PUT /api/bulk/{ControllerName}` |
| 268 | - body: `TRes[]` |
| 269 | - response: `OperationResultDto<TRes[]>` |
| 270 | |
| 271 | - `DELETE /api/bulk/{ControllerName}?id={id1}&id={id2}...` |
| 272 | - query: `id: TId[]` |
| 273 | - response body: отсутствует (метод возвращает `Task`, но выставляет status) |
| 274 | |
| 275 | Источник: `BasicBulkCrudController`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicBulkCrudController.cs |
| 276 | |
| 277 | --- |
| 278 | |
| 279 | ### Manager contract (exact) |
| 280 | Контроллеры вызывают `IModelManager<TRes, TObj, TId>`. |
| 281 | |
| 282 | Методы: |
| 283 | - `CreateAsync(TRes)` → `OperationResultDto<TRes>` |
| 284 | - `BulkCreateAsync(TRes[])` → `OperationResultDto<TRes[]>` |
| 285 | - `UpdateAsync(TId, TRes)` → `OperationResultDto<TRes>` |
| 286 | - `BulkUpdateAsync(TRes[])` → `OperationResultDto<TRes[]>` |
| 287 | - `DeleteAsync(TId)` → `OperationResultDto<bool>` |
| 288 | - `BulkDeleteAsync(TId[])` → `OperationResultDto<bool>` |
| 289 | - `GetAsync(page,size,sorting,parameters)` → `OperationResultDto<Tuple<IList<TRes>, long>>` |
| 290 | - `GetByIdAsync(TId)` → `OperationResultDto<TRes>` |
| 291 | |
| 292 | Источник: `IModelManager`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Managers/IModelManager.cs |
| 293 | |
| 294 | --- |
| 295 | |
| 296 | ### Operational notes (важно для агентского генератора) |
| 297 | - **HTTP status** берётся из `OperationResultDto.Status`, но **в JSON его нет** → клиентам нельзя полагаться на наличие `status` в теле. |
| 298 | - Для `DELETE` (и обычного, и bulk) контроллер возвращает `Task`/пустой body → клиенты должны смотреть на HTTP status. |
| 299 | - `sort` поддерживает **одну колонку** (как минимум на уровне контракта `SortOption`). |
| 300 | - `order` принимает только `asc|desc` (case-insensitive) → иначе `asc`. |
| 301 | - `page/size` не могут выключить пагинацию: “no option to receive all data without paging” (комментарий в `BasicReadController`). |
| 302 | |
| 303 | --- |
| 304 | |
| 305 | ## Пример wiring (из TestApp) |
| 306 | В TestApp показан вариант с `AddFullyConfiguredAutoController(...)` + `ManagerConfiguration`. |
| 307 | |
| 308 | Источник: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.TestApp/Startup.cs |
| 309 | |
| 310 | --- |
| 311 | |
| 312 | ## Ссылки |
| 313 | - Репо: https://github.com/Wissance/WebApiToolkit |
| 314 | - README: https://raw.githubusercontent.com/Wissance/WebApiToolkit/master/README.md |
| 315 | - Wiki: https://github.com/Wissance/WebApiToolkit/wiki |
| 316 | - DI entrypoints (EF): https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Ef/Extensions/ServiceCollectionExtensions.cs |
| 317 | ## TFilter template (exact pattern for agents) |
| 318 | Этот раздел нужен, чтобы агент мог **автоматически генерировать фильтры** для `GET /api/{Controller}`. |
| 319 | |
| 320 | ### Факт из WebApiToolkit |
| 321 | `BasicReadController.ReadAsync(...)` получает `TFilter additionalFilters` и делает: |
| 322 | - `additionalFilters.SelectFilters()` → `IDictionary additionalQueryParams` |
| 323 | - передаёт это в `Manager.GetAsync(..., parameters: additionalQueryParams)` |
| 324 | |
| 325 | Источник: `BasicReadController.ReadAsync(...)`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Controllers/BasicReadController.cs |
| 326 | |
| 327 | Контракт фильтра: |
| 328 | - `TFilter : IReadFilterable` |
| 329 | - `IReadFilterable.SelectFilters(): IDictionary` |
| 330 | |
| 331 | Источник: `IReadFilterable`: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Data/IReadFilterable.cs |
| 332 | |
| 333 | --- |
| 334 | |
| 335 | ### Правила для агентов (обязательные) |
| 336 | - **Правило 1 — ключи словаря**: ключи в `IDictionary` должны быть **стабильными** и совпадать с тем, что ожидает реализация `IModelManager.GetAsync(..., parameters)`. |
| 337 | - По умолчанию удобно использовать те же имена, что и в query (`from`, `to`, `status`, …). |
| 338 | - **Правило 2 — не класть null/пустое**: `SelectFilters()` должен исключать `null`, пустые строки, пустые коллекции. |
| 339 | - **Правило 3 — типы значений**: клади в словарь значения в типах, которые менеджер реально умеет обработать (обычно `string`, `int`, `bool`, `DateTime`, arrays). |
| 340 | - **Правило 4 — имена query-параметров**: если нужно фиксированное имя в URL, используй `[FromQuery(Name = "...")]` на свойстве. |
| 341 | |
| 342 | --- |
| 343 | |
| 344 | ### Шаблон `TFilter` (рекомендуемый) |
| 345 | Пример фильтра, который агент может генерировать “по умолчанию”: |
| 346 | |
| 347 | ```csharp |
| 348 | using System; |
| 349 | using System.Collections; |
| 350 | using System.Collections.Generic; |
| 351 | using Microsoft.AspNetCore.Mvc; |
| 352 | using Wissance.WebApiToolkit.Core.Data; |
| 353 | |
| 354 | public sealed class MyResourceFilters : IReadFilterable |
| 355 | { |
| 356 | // Пример диапазона дат: ?from=2022-01-01&to=2024-12-31 |
| 357 | [FromQuery(Name = "from")] |
| 358 | public DateTime? From { get; init; } |
| 359 | |
| 360 | [FromQuery(Name = "to")] |
| 361 | public DateTime? To { get; init; } |
| 362 | |
| 363 | // Пример точного совпадения: ?status=Active |
| 364 | [FromQuery(Name = "status")] |
| 365 | public string? Status { get; init; } |
| 366 | |
| 367 | // Пример числового фильтра: ?minScore=10 |
| 368 | [FromQuery(Name = "minScore")] |
| 369 | public int? MinScore { get; init; } |
| 370 | |
| 371 | // Пример мульти-значения: ?id=1&id=2&id=3 |
| 372 | [FromQuery(Name = "id")] |
| 373 | public int[]? Id { get; init; } |
| 374 | |
| 375 | public IDictionary SelectFilters() |
| 376 | { |
| 377 | var dict = new Dictionary<string, object>(); |
| 378 | |
| 379 | if (From is not null) dict["from"] = From.Value; |
| 380 | if (To is not null) dict["to"] = To.Value; |
| 381 | |
| 382 | if (!string.IsNullOrWhiteSpace(Status)) dict["status"] = Status!; |
| 383 | if (MinScore is not null) dict["minScore"] = MinScore.Value; |
| 384 | |
| 385 | if (Id is { Length: > 0 }) dict["id"] = Id; |
| 386 | |
| 387 | return dict; |
| 388 | } |
| 389 | } |
| 390 | ``` |
| 391 | |
| 392 | ### Как агенту связать `TFilter` с контроллером |
| 393 | - Для автогенерации через `AddSimplifiedAutoController<..., TFilter>` / `AddFullyConfiguredAutoController<..., TFilter>` — передай этот тип `TFilter` как generic параметр. |
| 394 | - Для ручных контроллеров на базе `BasicReadController<TRes, TObj, TId, TFilter>` — укажи `TFilter` в generic параметрах. |
| 395 | |
| 396 | ### Пустой фильтр |
| 397 | Если фильтров нет — используй `EmptyAdditionalFilters`. |
| 398 | |
| 399 | Источник: https://raw.githubusercontent.com/Wissance/WebApiToolkit/a1016f6631d3a7bcc76eb986ff6abcd1b0e5e91f/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Core/Data/EmptyAdditionalFilters.cs |
| 400 | |