Administrator
  • Joined on 2025-04-12

Ava3v.WebApi.Core (0.3.0)

Published 2026-09-13 23:08:43 +03:00 by Administrator

Installation

dotnet nuget add source --name Administrator --username your_username --password your_token https://git.ava3v.ru/api/packages/Administrator/nuget/index.json
dotnet add package --source Administrator --version 0.3.0 Ava3v.WebApi.Core

About this package

Общее веб-ядро ava3v: minimal-API → MediatR (CQRS) с билдерами (GET/POST/PUT/DELETE/file/form-data), базовые команды/запросы/хендлеры, ValidationBehavior (FluentValidation), единый маппинг ошибок в HTTP, локализация, контексты (correlation/current-user), JWT. Извлечено из WareHub.

Ava3vLibs

Монорепозиторий общих .NET-библиотек ava3v.

  • Общая версия пакетов — <Version> в Directory.Build.props (одна на всю монорепу).
  • Версии внешних зависимостей — в Directory.Packages.props (Central Package Management), меняются в одном месте.
  • Приватный NuGet-фид: https://git.ava3v.com/api/packages/Administrator/nuget.

Пакеты (зависимость строго вниз)

Пакет Зависит от Назначение
Ava3v.Abstractions — Базовые контракты (ноль зависимостей): интерфейсы сущностей (IHasId/IHasParentId/IHasChildrenInfo и др.), EntityBase, DTO, Enumeration/UserKindEnum, контексты, Security, PagedResponse, паджинг-запросы (IPagedQuery/IHasSearch/ITreePagedQuery), ошибки бизнес-логики.
Ava3v.Components.Contract Abstractions (+EFCore) Грид-лоадер (фильтры/сортировка/поиск/уникальные значения по IQueryable) + построитель древовидного реестра.
Ava3v.WebApi.Core Abstractions (+MediatR/AspNetCore/…) minimal-API → MediatR (CQRS): билдеры (GET/POST/PUT/DELETE/file/form-data), middleware, локализация, хендлер-базы, JWT.
Ava3v.DataAccess Abstractions (+EFCore.Relational) Базовый DbContext (аудит/soft-delete/период действия), snake_case-конфиг сущностей, query-фильтры.

Публикация

Токен Gitea (personal access token с правом write:package) держи у себя — в репо не коммитим. Перед публикацией новой версии подними <Version> в Directory.Build.props (одну и ту же версию Gitea второй раз не примет; --skip-duplicate это проглотит).

Всё сразу (обычный путь)

cd ~/Ava3vLibs
rm -rf artifacts
dotnet pack Ava3vLibs.slnx -c Release -o artifacts

dotnet nuget push "artifacts/*.nupkg" \
  --source https://git.ava3v.com/api/packages/Administrator/nuget \
  --api-key <твой-gitea-токен> \
  --skip-duplicate

По отдельности

Порядок важен, только если фид пустой: Ava3v.Abstractions публикуй первым — остальные от него зависят. dotnet pack любого проекта складывает .nupkg в общий artifacts/.

Общий шаблон:

cd ~/Ava3vLibs
PROJ=Ava3v.Abstractions   # или Ava3v.Components.Contract / Ava3v.WebApi.Core / Ava3v.DataAccess

dotnet pack src/$PROJ/$PROJ.csproj -c Release -o artifacts
dotnet nuget push "artifacts/$PROJ.*.nupkg" \
  --source https://git.ava3v.com/api/packages/Administrator/nuget \
  --api-key <твой-gitea-токен> \
  --skip-duplicate

Конкретно по каждому:

1. Ava3v.Abstractions (сначала)

dotnet pack src/Ava3v.Abstractions/Ava3v.Abstractions.csproj -c Release -o artifacts
dotnet nuget push "artifacts/Ava3v.Abstractions.*.nupkg" --source https://git.ava3v.com/api/packages/Administrator/nuget --api-key <токен> --skip-duplicate

2. Ava3v.Components.Contract

dotnet pack src/Ava3v.Components.Contract/Ava3v.Components.Contract.csproj -c Release -o artifacts
dotnet nuget push "artifacts/Ava3v.Components.Contract.*.nupkg" --source https://git.ava3v.com/api/packages/Administrator/nuget --api-key <токен> --skip-duplicate

3. Ava3v.WebApi.Core

dotnet pack src/Ava3v.WebApi.Core/Ava3v.WebApi.Core.csproj -c Release -o artifacts
dotnet nuget push "artifacts/Ava3v.WebApi.Core.*.nupkg" --source https://git.ava3v.com/api/packages/Administrator/nuget --api-key <токен> --skip-duplicate

4. Ava3v.DataAccess

dotnet pack src/Ava3v.DataAccess/Ava3v.DataAccess.csproj -c Release -o artifacts
dotnet nuget push "artifacts/Ava3v.DataAccess.*.nupkg" --source https://git.ava3v.com/api/packages/Administrator/nuget --api-key <токен> --skip-duplicate

Закоммитить исходники

git add -A
git commit -m "Ava3vLibs <version> — <что изменилось>"
git push

Как это потребляют другие проекты

В nuget.config потребителя добавить источник (один раз):

<packageSources>
  <add key="ava3v" value="https://git.ava3v.com/api/packages/Administrator/nuget/index.json" />
</packageSources>

Затем нужные пакеты (транзитивно тянут Ava3v.Abstractions):

<PackageReference Include="Ava3v.WebApi.Core" Version="0.2.0" />
<PackageReference Include="Ava3v.Components.Contract" Version="0.2.0" />
<PackageReference Include="Ava3v.DataAccess" Version="0.2.0" />

Массовые операции по выделению грида (Ava3v.Components.Contract 0.2.33+)

Грид с 0.1.158 умеет «выделить всё по фильтру»: на сервер приходит не список ключей, а GridSelectionRequest — флаг AllMatchingSelected, ExcludedKeys и Filter (тело getBackendRequest() грида) либо SelectedKeys для явного выделения. Разрешение в запрос и предохранитель — GridSelectionResolver:

// Команда: { GridSelectionRequest Selection; ... patch ... }
var rows = await db.Items.ResolveAsync(command.Selection, x => x.Id, maxAffectedRows: 10_000, ct);
// > лимита — BusinessLogicException с Code = "BulkLimitExceeded" ДО изменения данных (COUNT до лимита + 1)
await rows.ExecuteUpdateAsync(s => s.SetProperty(x => x.Status, command.Status), ct);

ApplySelection — только запрос без проверки, EnsureWithinLimitAsync — только проверка. Лимит держите согласованным с фронтовым selectAllMatchingLimit (по умолчанию 10 000).

Заметки к выпуску

0.3.0 — ЛОМАЮЩЕЕ: авторство при добавлении

  • Ava3vDbContextBase: CreatedBy при добавлении ВСЕГДА берётся из текущего пользователя, заданное значение (в том числе прилетевшее из DTO через маппер) игнорируется. Раньше заполненный CreatedBy не трогался — клиент мог записать запись от чужого имени.
  • Сохранить исходного автора можно только из кода, явным режимом с областью действия: using (context.PreserveAuthorship()) { … await context.SaveChangesAsync(); } — для сидеров, генераторов демо-данных, переноса и синхронизации данных. В режиме CreatedBy = 0 всё равно заменяется текущим пользователем; UpdatedBy/DeletedBy режим не затрагивает; автор записи журнала аудита — всегда тот, кто выполняет операцию. CreatedAt это решение не трогает.
  • Что проверить у себя: все места, где CreatedBy задаётся явно (сидеры, импорты, DevData, генераторы, вызываемые через API — там автором станет вызвавший), обернуть в режим.
  • Аудит: тесты на настоящем Postgres (tests/Ava3v.Audit.PostgresTests, Testcontainers postgres:14.1 либо TEST_POSTGRES_CONNECTION); без Docker локально — Skipped, в CI (CI=true) — падение.

Аудит (Ava3v.Audit, 0.3.0+)

Журнал изменений по полям и снимки версий в транзакции самих изменений. Контракты — в Ava3v.Abstractions.Audit, реализация — в пакете Ava3v.Audit. Подключение:

// DI
services.AddAva3vAudit<AppDbContext>(o => { o.Schema = "audit"; o.WriteSnapshots = true; });
services.AddDbContext<AppDbContext>((sp, o) => o.UseNpgsql(cs).UseAva3vAudit(sp));
// Своё: описания полей, резолверы ссылок, форматтеры, скоуп тенанта, политика доступа, имена авторов
services.AddScoped<IAuditFieldDescriptorProvider, IssueAuditFields>();
services.AddScoped<IAuditReferenceResolver, UserReferenceResolver>();   // читать через IgnoreQueryFilters!
services.AddScoped<IAuditScopeProvider, WorkspaceAuditScope>();         // TenantId/ScopeId + фильтр чтения
services.AddScoped<IAuditAccessPolicy, EntityAuditAccessPolicy>();      // без неё — любому аутентифицированному
services.AddScoped<IAuditUserNameResolver, UserNameResolver>();

// Контекст: наследник Ava3vDbContextBase (штамп метаданных и мягкое удаление — до аудита)
protected override void OnModelCreating(ModelBuilder b) { base.OnModelCreating(b); b.ApplyAva3vAudit(auditOptions); }
public bool SuppressAudit { get; set; }   // IAuditSuppressible — миграции и массовая загрузка

// Эндпоинт истории: GET api/audit/getShort?entityType=Issue&entityId=5&skip=0&take=20
app.MapAva3vAudit();
// Программно: IAuditLogService.GetShortAsync(...), IEntitySnapshotService.GetAsOfAsync<T>(id, moment)

Правила:

  • журнал пишется вторым SaveChanges того же контекста в той же транзакции; без явной транзакции интерсептор открывает свою и коммитит после журнала; откат/ошибка/отмена журнал не оставляют;
  • EnableRetryOnFailure несовместим с собственной транзакцией: оборачивайте сохранения в CreateExecutionStrategy().ExecuteAsync(...) (интерсептор бросает понятную ошибку, если этого не сделать), либо UseOwnTransaction = false;
  • ExecuteUpdate/ExecuteDelete обходят и метаданные, и мягкое удаление, и журнал — в проекте нужен архитектурный тест с разрешённым списком таких вызовов;
  • мягкое удаление пишется как Deleted; lifecycle-поля, [AuditIgnore], INotAuditable не пишутся;
  • автор записи — текущий пользователь (не CreatedBy сущности); ключ сущности — long;
  • таблицы {schema}.audit_log и {schema}.entity_snapshot — миграция на стороне потребителя.

Dependencies

ID Version Target Framework
Ava3v.Abstractions 0.3.0 net10.0
FluentValidation 12.1.1 net10.0
FluentValidation.DependencyInjectionExtensions 12.1.1 net10.0
Mapster 10.0.8 net10.0
MediatR 12.4.1 net10.0
Microsoft.AspNetCore.Authentication.JwtBearer 10.0.9 net10.0
Microsoft.AspNetCore.OpenApi 10.0.9 net10.0
Microsoft.EntityFrameworkCore 10.0.9 net10.0
Microsoft.OpenApi 2.9.0 net10.0
Details
NuGet
2026-09-13 23:08:43 +03:00
9
ava3v
58 KiB
Assets (2)
Versions (67) View all
0.11.0 2026-10-22
0.10.3 2026-10-05
0.10.2 2026-10-05
0.10.1 2026-09-26
0.10.0 2026-09-21