Skip to main content

add-feature

Add a vertical-slice feature (command/query + handler + validator + endpoint) to an existing FSH module. Use when adding an API endpoint or business operation to a module that already exists.

معلومات المصدر

المستودع
fullstackhero/dotnet-starter-kit
آخر نشاط في المصدر
١٣ يوليو ٢٠٢٦ في ١٣:٢٨
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦٬٨١٨
التفرعات
٢٬٠٠٣

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
add-feature
description
Add a vertical-slice feature (command/query + handler + validator + endpoint) to an existing FSH module. Use when adding an API endpoint or business operation to a module that already exists.
argument-hint
[ModuleName] [Area] [FeatureName]
# Add Feature A feature is a vertical slice **split across two projects**: the request/response types live in the module's `.Contracts` project (public API); the handler, validator, and endpoint live in the runtime project. Full conventions: `.agents/rules/api-conventions.md`. ## Layout (real) ``` src/Modules/{X}/Modules.{X}.Contracts/v1/{Area}/{Feature}Command.cs # ICommand<T>/IQuery<T> src/Modules/{X}/Modules.{X}.Contracts/Dtos/{Entity}Dto.cs # response DTOs (if any) src/Modules/{X}/Modules.{X}/Features/v1/{Area}/{Feature}/ ├── {Feature}CommandHandler.cs # public sealed, injects the DbContext directly ├── {Feature}CommandValidator.cs # required for commands + paginated queries └── {Feature}Endpoint.cs # internal static extension ``` ## Step 1 — Command/Query (Contracts project) `Mediator` interfaces (`using Mediator;`). Records. A create command can return the raw `Guid`. ```csharp namespace FSH.Modules.{X}.Contracts.v1.{Area}; public sealed record Create{Entity}Command(string Name, decimal PriceAmount, string PriceCurrency) : ICommand<Guid>; ``` Read/list DTOs go in `Modules.{X}.Contracts/Dtos/`. Paginated queries return `PagedResponse<T>` (`FSH.Framework.Shared.Persistence`) — see `query-patterns`. ## Step 2 — Handler (runtime `Features/`) — inject the DbContext, NOT a repository There is **no generic `IRepository<T>`**. Inject the module's `{X}DbContext`. `public sealed`, primary ctor, `ValueTask<T>`, `.ConfigureAwait(false)`, guard first. Tenant/audit fields are auto-stamped — only inject `ICurrentUser` if you need the acting user (`GetUserId()` / `GetTenant()`). ```csharp public sealed class Create{Entity}CommandHandler(CatalogDbContext dbContext) : ICommandHandler<Create{Entity}Command, Guid> { public async ValueTask<Guid> Handle(Create{Entity}Command command, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(command); var entity = {Entity}.Create(command.Name, new Money(command.PriceAmount, command.PriceCurrency)); dbContext.{Entities}.Add(entity); await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); return entity.Id; } } ``` Throw `NotFoundException` / `CustomException(msg, errors, HttpStatusCode)` (`FSH.Framework.Core.Exceptions`) — the global handler maps them to ProblemDetails. ## Step 3 — Validator (required; same folder) ```csharp public sealed class Create{Entity}CommandValidator : AbstractValidator<Create{Entity}Command> { public Create{Entity}CommandValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(200); RuleFor(x => x.PriceCurrency).NotEmpty().Length(3); } } ``` `Architecture.Tests` fails the build if a command/paginated-query handler has no `{Name}Validator`. ## Step 4 — Endpoint (same folder) ```csharp public static class Create{Entity}Endpoint { internal static RouteHandlerBuilder MapCreate{Entity}Endpoint(this IEndpointRouteBuilder endpoints) => endpoints.MapPost("/{entities}", async (Create{Entity}Command command, IMediator mediator, CancellationToken ct) => Results.Ok(await mediator.Send(command, ct))) .WithName("Create{Entity}") .WithSummary("Create a {entity}") .RequirePermission({X}Permissions.{Entities}.Create) .WithIdempotency(); // on replay-safe POSTs } ``` ## Step 5 — Wire it in `{X}Module.MapEndpoints` ```csharp group.MapCreate{Entity}Endpoint(); // group = endpoints.MapGroup("api/v{version:apiVersion}/{x}") … ``` ## Step 6 — Verify ```bash dotnet build src/FSH.Starter.slnx # 0 warnings (TreatWarningsAsErrors) dotnet test src/Tests/{X}.Tests # + add a handler/validator test (see testing-guide) ``` ## Checklist - [ ] Command/Query in the **Contracts** project (`using Mediator;`), DTOs in `Contracts/Dtos/` - [ ] Handler `public sealed`, injects `{X}DbContext` (no repository), `ValueTask<T>` + `.ConfigureAwait(false)` - [ ] `{Name}Validator` exists - [ ] Endpoint `internal static …Map{Feature}Endpoint`, `.RequirePermission(...)`, `.WithName/.WithSummary` - [ ] Wired in `{X}Module.MapEndpoints` - [ ] Build 0 warnings; test added
عرض على GitHub