@drunkcoding/dknet-implementation-skills 0.1.0 → 14.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +37 -26
  3. package/package.json +2 -3
  4. package/{.claude-plugin → plugin/.claude-plugin}/plugin.json +1 -1
  5. package/{agents → plugin/agents}/dknet-architect.md +4 -4
  6. package/{agents → plugin/agents}/dknet-bdd-engineer.md +6 -6
  7. package/{agents → plugin/agents}/dknet-implementer.md +11 -11
  8. package/{skills → plugin/skills}/README.md +13 -12
  9. package/{skills → plugin/skills}/dknet-auth-and-ownership/SKILL.md +100 -36
  10. package/{skills → plugin/skills}/dknet-bdd-tests/SKILL.md +25 -16
  11. package/{skills → plugin/skills}/dknet-bdd-tests/checklist.md +1 -1
  12. package/{skills → plugin/skills}/dknet-crud/SKILL.md +34 -22
  13. package/{skills → plugin/skills}/dknet-ddd-principles/SKILL.md +62 -13
  14. package/{skills → plugin/skills}/dknet-docs/SKILL.md +73 -31
  15. package/{skills → plugin/skills}/dknet-docs/templates/README-template.md +8 -8
  16. package/{skills → plugin/skills}/dknet-docs/templates/api-reference-template.md +7 -0
  17. package/{skills → plugin/skills}/dknet-docs/templates/architecture-template.md +14 -8
  18. package/{skills → plugin/skills}/dknet-docs/templates/data-model-template.md +2 -2
  19. package/{skills → plugin/skills}/dknet-docs/templates/events-template.md +2 -2
  20. package/{skills → plugin/skills}/dknet-dto-mapping/SKILL.md +37 -29
  21. package/{skills → plugin/skills}/dknet-efcore-config/SKILL.md +38 -38
  22. package/{skills → plugin/skills}/dknet-endpoint/SKILL.md +70 -43
  23. package/{skills → plugin/skills}/dknet-entity/SKILL.md +43 -33
  24. package/{skills → plugin/skills}/dknet-feature/SKILL.md +34 -13
  25. package/{skills → plugin/skills}/dknet-feature-lifecycle/SKILL.md +48 -42
  26. package/{skills → plugin/skills}/dknet-feature-remove/SKILL.md +15 -15
  27. package/{skills → plugin/skills}/dknet-messaging-events/SKILL.md +36 -29
  28. package/{skills → plugin/skills}/dknet-package-adoption/SKILL.md +3 -3
  29. package/{skills → plugin/skills}/dknet-platform-config/SKILL.md +14 -14
  30. package/{skills → plugin/skills}/dknet-project-structure/SKILL.md +32 -32
  31. package/{skills → plugin/skills}/dknet-queries-specs/SKILL.md +21 -13
  32. package/{skills → plugin/skills}/dknet-scaffold/SKILL.md +3 -2
  33. package/{skills → plugin/skills}/dknet-unit-tests/SKILL.md +27 -22
  34. package/plugin.json +3 -3
  35. /package/{skills → plugin/skills}/dknet-docs/checklist.md +0 -0
@@ -24,32 +24,38 @@ behavior — which is exactly the confusion this section exists to prevent.
24
24
 
25
25
  ### At a glance: which one should I copy?
26
26
 
27
- **Copy `manual` (mirror `PurchaseOrder`)** when the feature needs any of:
27
+ **Default to `auto` (mirror `Product`).** Declare the operation on the entity —
28
+ `[CrudCreate]`/`[CrudUpdate]`/`[CrudAction]`/`[RaisesEvent]`/`[GenerateDto]` — and let
29
+ `DKNet.SlimBus.Generators` emit the request, handler and route. Start here for every new aggregate
30
+ and only step down when one of the reasons below actually applies to the feature in front of you.
28
31
 
29
- - Idempotent writes — safe client retries on `POST`.
32
+ **Step down to `manual` (mirror `PurchaseOrder`)** when the feature needs any of:
33
+
34
+ - Idempotent writes — safe client retries on `POST`. The generated create route has no
35
+ `.RequiredIdempotentKey()` and none can be added to it.
30
36
  - An attribute-declared (`DataAnnotations`) rule that must actually return `400`, not just be
31
- present on the generated request.
37
+ present on the generated request (see the gap below) — and that cannot be re-expressed as a
38
+ FluentValidation rule.
32
39
  - An operation that writes more than one aggregate in one transaction.
33
40
  - A filtered or customized list/get query beyond the generic list route's `filter`/`search`/`orderBy`
34
41
  contract.
35
42
  - The acting user must come from a claim on the request itself (`[FromClaim]`).
36
43
 
37
- **Copy `auto` (mirror `Product`)** only when the aggregate is genuinely plain CRUD:
38
-
39
- - Create/update/delete carry no rule a `[Required]`/`[StringLength]`/`[Range]` can't express — and
40
- you accept those attributes are forwarded but never enforced on a generated route (see the gap
41
- below).
42
- - No idempotency requirement on create.
43
- - The DTO can be every audited property, narrowed at most with `Exclude`/`Include`.
44
- - Any extra operations fit `[CrudAction]`'s shape (mutate, return `200` + DTO) or can be excluded and
45
- hand-written when they can't.
46
-
47
- A rule that must **refuse** an operation is *not* by itself a reason to choose `manual`. A
48
- FluentValidation validator written against a generated request still runs — `UseEndpointConfigs`
49
- applies `AddFluentValidationAutoValidation()` to every endpoint group, generated routes included —
50
- so it can read stored data through `IRepositorySpec` and refuse before the generated handler runs.
51
- `Product` ships two such rules (`CreateProductRequestValidator`, `DeleteProductRequestValidator`),
52
- both answering `409` via a `PreconditionCodes`-prefixed error code.
44
+ None of these apply? Then `auto` is the answer, even for an aggregate with real business rules.
45
+ Specifically, **none of the following is a reason to hand-write**:
46
+
47
+ - **A rule that must refuse an operation.** A FluentValidation validator written against a generated
48
+ request still runs — `UseEndpointConfigs` applies `AddFluentValidationAutoValidation()` to every
49
+ endpoint group, generated routes included — so it can read stored data through `IRepositorySpec`
50
+ and refuse before the generated handler runs. `Product` ships two such rules
51
+ (`CreateProductRequestValidator`, `DeleteProductRequestValidator`), both answering `409` via a
52
+ `PreconditionCodes`-prefixed error code.
53
+ - **A DTO that must hide fields.** `[GenerateDto(..., Exclude = [...])]` narrows the shape, and
54
+ `[SensitiveData]` travels from the entity onto the generated DTO.
55
+ - **A derived response value.** A Mapster `IRegister` reaches the generated route's response with no
56
+ endpoint change (`ProductDto.GrossMargin`).
57
+ - **A domain event.** `[RaisesEvent]` raises it from the save hook; the consumer is hand-written
58
+ either way.
53
59
 
54
60
  | Trade-off | `manual` | `auto` |
55
61
  |---|---|---|
@@ -75,20 +81,20 @@ against the real tree under both sample features.
75
81
 
76
82
  | # | Path | `manual` | `auto` |
77
83
  |---|---|---|---|
78
- | 1 | `Minimal.Domains/Features/<Feature>/Entities/` | entity + hand-written event record(s) | entity only — `[RaisesEvent]`/`[CrudCreate]`/`[CrudUpdate]`/`[CrudAction]` carry the rest |
79
- | 2 | `Minimal.Infra/Features/<Feature>/Mappers/` | `IEntityTypeConfiguration<T>` | same — **no generator produces this** |
80
- | 3 | `Minimal.Infra/Features/<Feature>/StaticData/` | optional `DataSeedingConfiguration<T>` | optional (neither sample ships one for `Product`) |
81
- | 4 | `Minimal.Infra/Features/<Feature>/ExternalEvents/` | not used by `PurchaseOrder` | optional broker consumer (`ProductCreatedNotificationHandler`) |
82
- | 5 | `Minimal.AppServices/<Feature>/V1/Actions/` | command requests + handlers | only operations excluded from the generated map (e.g. `Discontinue.cs`) |
83
- | 6 | `Minimal.AppServices/<Feature>/V1/Queries/` | hand-written read requests + handlers | custom read shapes the generic list can't express (e.g. a price-summary query) |
84
- | 7 | `Minimal.AppServices/<Feature>/V1/Specs/` | `Specification<T>` filters | specs backing a validator or an excluded query |
85
- | 8 | `Minimal.AppServices/<Feature>/V1/Events/` | domain event consumers | domain event consumers only — the generator raises, it does not consume |
86
- | 9 | `Minimal.AppServices/<Feature>/V1/Validators/` | not needed — validation is enforced on literal routes | validators against a **generated** request that must refuse (`CreateXRequestValidator`, `DeleteXRequestValidator`) |
87
- | 10 | `Minimal.AppServices/<Feature>/V1/<Feature>Dto.cs` (+ optional `<Feature>MappingRegister.cs`) | hand-written DTO record | one `[GenerateDto(typeof(Entity))] public sealed partial record` line; a Mapster `IRegister` only if a response value needs deriving from more than one column |
88
- | 11 | `Minimal.Api/ApiEndpoints/<Feature>/<Entity>V1Endpoint.cs` | every route a literal `Map*` call | one `group.Map<Entity>Crud(o => …)` call, plus any excluded route mapped literally below it |
89
- | 12 | `Minimal.App.Tests/Unit/<Feature>/` | entity/validator/spec tests | entity/handler tests |
90
- | 13 | `Minimal.App.Tests/Integration/<Feature>/V1/` | result-level handler + security tests | same |
91
- | 14 | `Minimal.App.BDDTests/Features/<Plural>/` | `*.feature` + `Steps/*.cs` | same |
84
+ | 1 | `<YourApp>.Domains/Features/<Feature>/Entities/` | entity + hand-written event record(s) | entity only — `[RaisesEvent]`/`[CrudCreate]`/`[CrudUpdate]`/`[CrudAction]` carry the rest |
85
+ | 2 | `<YourApp>.Infra/Features/<Feature>/Mappers/` | `IEntityTypeConfiguration<T>` | same — **no generator produces this** |
86
+ | 3 | `<YourApp>.Infra/Features/<Feature>/StaticData/` | optional `DataSeedingConfiguration<T>` | optional (neither sample ships one for `Product`) |
87
+ | 4 | `<YourApp>.Infra/Features/<Feature>/ExternalEvents/` | not used by `PurchaseOrder` | optional broker consumer (`ProductCreatedNotificationHandler`) |
88
+ | 5 | `<YourApp>.AppServices/<Feature>/V1/Actions/` | command requests + handlers | only operations excluded from the generated map (e.g. `Discontinue.cs`) |
89
+ | 6 | `<YourApp>.AppServices/<Feature>/V1/Queries/` | hand-written read requests + handlers | custom read shapes the generic list can't express (e.g. a price-summary query) |
90
+ | 7 | `<YourApp>.AppServices/<Feature>/V1/Specs/` | `Specification<T>` filters | specs backing a validator or an excluded query |
91
+ | 8 | `<YourApp>.AppServices/<Feature>/V1/Events/` | domain event consumers | domain event consumers only — the generator raises, it does not consume |
92
+ | 9 | `<YourApp>.AppServices/<Feature>/V1/Validators/` | not needed — validation is enforced on literal routes | validators against a **generated** request that must refuse (`CreateXRequestValidator`, `DeleteXRequestValidator`) |
93
+ | 10 | `<YourApp>.AppServices/<Feature>/V1/<Feature>Dto.cs` (+ optional `<Feature>MappingRegister.cs`) | hand-written DTO record | one `[GenerateDto(typeof(Entity))] public sealed partial record` line; a Mapster `IRegister` only if a response value needs deriving from more than one column |
94
+ | 11 | `<YourApp>.Api/ApiEndpoints/<Feature>/<Entity>V1Endpoint.cs` | every route a literal `Map*` call | one `group.Map<Entity>Crud(o => …)` call, plus any excluded route mapped literally below it |
95
+ | 12 | `<YourApp>.App.Tests/Unit/<Feature>/` | entity/validator/spec tests | entity/handler tests |
96
+ | 13 | `<YourApp>.App.Tests/Integration/<Feature>/V1/` | result-level handler + security tests | same |
97
+ | 14 | `<YourApp>.App.BDDTests/Features/<Plural>/` | `*.feature` + `Steps/*.cs` | same |
92
98
 
93
99
  Generated code for `auto` lands in `obj/Generated/DKNet.SlimBus.Generators/` (requests, handlers,
94
100
  route registration) and `obj/Generated/DKNet.EfCore.DtoGenerator/` (the DTO's generated members) —
@@ -101,18 +107,18 @@ the reason a feature delete is a command and not an `rm -rf`.
101
107
 
102
108
  | Touchpoint | File | When it applies |
103
109
  |---|---|---|
104
- | Schema constant | `Minimal.Domains/Share/DomainSchemas.cs` | if the feature added its own `const string` (the samples instead use literal schema strings — `"manual_sample"`/`"sample"` — directly in their mapper's `ToTable` call) |
105
- | Broker topology | `Minimal.Infra/Extensions/ServiceBusSetup.cs` | the `azb.Produce<T>`/`azb.Consume<T>` pair, e.g. `ProductCreatedEvent` on `product-tp`/`product-sub` |
106
- | Feature flag | `Minimal.Share/Options/FeatureOptions.cs` + `FeatureManagement` section in every `appsettings*.json` | if the feature gated itself behind a flag |
110
+ | Schema constant | `<YourApp>.Domains/Share/DomainSchemas.cs` | if the feature added its own `const string` (the samples instead use literal schema strings — `"manual_sample"`/`"sample"` — directly in their mapper's `ToTable` call) |
111
+ | Broker topology | `<YourApp>.Infra/Extensions/ServiceBusSetup.cs` | the `azb.Produce<T>`/`azb.Consume<T>` pair, e.g. `ProductCreatedEvent` on `product-tp`/`product-sub` |
112
+ | Feature flag | `<YourApp>.Share/Options/FeatureOptions.cs` + `FeatureManagement` section in every `appsettings*.json` | if the feature gated itself behind a flag |
107
113
  | Auth scopes | the feature's own scopes class (e.g. `ProductScopes`) + its `foreach` registration in `Configs/Auth/AuthConfig.cs` | if the feature registered per-route scope policies |
108
- | Precondition codes | `Minimal.AppServices/Share/PreconditionCodes.cs` | if a validator added a `precondition.`-prefixed code for this feature |
109
- | Test-support visibility | `InternalsVisibleTo` in the owning project's `.csproj` (e.g. `Minimal.Api.csproj` grants `Minimal.App.TestSupport` and `Minimal.App.Tests` visibility onto `internal` scope classes) | if the feature's `internal` types need to be visible to test doubles |
110
- | EF migration | `Minimal.Infra/Migrations/` | see §4 — never hand-delete an applied migration |
114
+ | Precondition codes | `<YourApp>.AppServices/Share/PreconditionCodes.cs` | if a validator added a `precondition.`-prefixed code for this feature |
115
+ | Test-support visibility | `InternalsVisibleTo` in the owning project's `.csproj` (e.g. `<YourApp>.Api.csproj` grants `<YourApp>.App.TestSupport` and `<YourApp>.App.Tests` visibility onto `internal` scope classes) | if the feature's `internal` types need to be visible to test doubles |
116
+ | EF migration | `<YourApp>.Infra/Migrations/` | see §4 — never hand-delete an applied migration |
111
117
 
112
118
  Enumerate existing features at any time — no registry file to keep in sync:
113
119
 
114
120
  ```bash
115
- ls ApiEndpoints/Minimal.Domains/Features/
121
+ ls ApiEndpoints/<YourApp>.Domains/Features/
116
122
  ```
117
123
 
118
124
  ## 4. Migration rules on removal
@@ -120,11 +126,11 @@ ls ApiEndpoints/Minimal.Domains/Features/
120
126
  The tables outlive the code. Decide by whether the feature's migration has been applied anywhere:
121
127
 
122
128
  - **Not applied and it is the newest migration** —
123
- `dotnet ef migrations remove -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj` (run from
129
+ `dotnet ef migrations remove -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj` (run from
124
130
  `ApiEndpoints/`).
125
131
  - **Applied, or newer migrations sit on top of it** — do NOT touch the old migration. Delete the
126
132
  entity and mapper, then
127
- `dotnet ef migrations add Drop<Feature> -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj`
133
+ `dotnet ef migrations add Drop<Feature> -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj`
128
134
  and let EF emit the drop. Rewriting applied history corrupts `__EFMigrationsHistory` for every
129
135
  environment already running it.
130
136
 
@@ -16,12 +16,12 @@ and the database tables all live outside the feature folders.
16
16
  ## Inputs
17
17
 
18
18
  `$ARGUMENTS` — the feature folder name (PascalCase, as it appears under
19
- `Minimal.Domains/Features/`), plus optional `--dry-run`.
19
+ `<YourApp>.Domains/Features/`), plus optional `--dry-run`.
20
20
 
21
21
  If no feature is named, list the candidates and stop:
22
22
 
23
23
  ```bash
24
- ls ApiEndpoints/Minimal.Domains/Features/
24
+ ls ApiEndpoints/<YourApp>.Domains/Features/
25
25
  ```
26
26
 
27
27
  ## Required reading
@@ -32,7 +32,7 @@ ls ApiEndpoints/Minimal.Domains/Features/
32
32
 
33
33
  ## Phase 0 — Confirm and inventory (always, even under `--dry-run`)
34
34
 
35
- 1. Resolve the feature: confirm `ApiEndpoints/Minimal.Domains/Features/<Feature>/` exists. If it
35
+ 1. Resolve the feature: confirm `ApiEndpoints/<YourApp>.Domains/Features/<Feature>/` exists. If it
36
36
  does not, list the candidates and STOP — do not guess at a near-match.
37
37
  2. Inventory every path that will be deleted, using the §2 footprint. Report actual matches only:
38
38
  ```bash
@@ -58,24 +58,24 @@ Do NOT reorder. Each step removes only things that nothing later in the list dep
58
58
  intermediate build failure points at real coupling rather than at the ordering.
59
59
 
60
60
  1. Docs — `docs/features/<slug>/` (or wherever the solution keeps feature docs).
61
- 2. BDD — `ApiEndpoints/Minimal.App.BDDTests/Features/<Plural>/` (both `*.feature` and the
61
+ 2. BDD — `ApiEndpoints/<YourApp>.App.BDDTests/Features/<Plural>/` (both `*.feature` and the
62
62
  generated `*.feature.cs`, plus `Steps/`).
63
- 3. Tests — `Minimal.App.Tests/Unit/<Feature>/` and `Minimal.App.Tests/Integration/<Feature>/`.
64
- Also grep `Minimal.App.Tests/Architecture/` — a convention test may assert on this feature by name.
65
- 4. Api — `Minimal.Api/ApiEndpoints/<Feature>/`.
66
- 5. AppServices — `Minimal.AppServices/<Feature>/`.
67
- 6. Infra — `Minimal.Infra/Features/<Feature>/`.
68
- 7. Domains — `Minimal.Domains/Features/<Feature>/`.
63
+ 3. Tests — `<YourApp>.App.Tests/Unit/<Feature>/` and `<YourApp>.App.Tests/Integration/<Feature>/`.
64
+ Also grep `<YourApp>.App.Tests/Architecture/` — a convention test may assert on this feature by name.
65
+ 4. Api — `<YourApp>.Api/ApiEndpoints/<Feature>/`.
66
+ 5. AppServices — `<YourApp>.AppServices/<Feature>/`.
67
+ 6. Infra — `<YourApp>.Infra/Features/<Feature>/`.
68
+ 7. Domains — `<YourApp>.Domains/Features/<Feature>/`.
69
69
 
70
70
  ## Phase 2 — Out-of-folder touchpoints
71
71
 
72
72
  Work the §3 table. For each, edit surgically — remove the feature's lines, never the whole file:
73
73
 
74
- 1. `Minimal.Domains/Share/DomainSchemas.cs` — drop the feature's `const string`, if it added one.
74
+ 1. `<YourApp>.Domains/Share/DomainSchemas.cs` — drop the feature's `const string`, if it added one.
75
75
  Leave `Migration` and `Profile` alone unless this feature owned one of them.
76
- 2. `Minimal.Infra/Extensions/ServiceBusSetup.cs` — drop the matching `azb.Produce<T>` /
76
+ 2. `<YourApp>.Infra/Extensions/ServiceBusSetup.cs` — drop the matching `azb.Produce<T>` /
77
77
  `azb.Consume<T>` pair and the now-unused `using`.
78
- 3. `Minimal.Share/Options/FeatureOptions.cs` + every `appsettings*.json` `FeatureManagement` section
78
+ 3. `<YourApp>.Share/Options/FeatureOptions.cs` + every `appsettings*.json` `FeatureManagement` section
79
79
  — drop the flag property and its JSON key together. A key with no property silently no-ops, so
80
80
  an orphan here fails no test; delete both halves or neither.
81
81
  4. Docs cross-links — the docs index page and any feature index that linked the slice.
@@ -84,8 +84,8 @@ Work the §3 table. For each, edit surgically — remove the feature's lines, ne
84
84
 
85
85
  Apply §4 of the lifecycle skill. State which branch you took and why:
86
86
 
87
- - Feature's migration is the newest and unapplied → `cd ApiEndpoints && dotnet ef migrations remove -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj`
88
- - Otherwise → `cd ApiEndpoints && dotnet ef migrations add Drop<Feature> -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj` and verify the generated `Up`
87
+ - Feature's migration is the newest and unapplied → `cd ApiEndpoints && dotnet ef migrations remove -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj`
88
+ - Otherwise → `cd ApiEndpoints && dotnet ef migrations add Drop<Feature> -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj` and verify the generated `Up`
89
89
  contains the expected `DropTable` calls and nothing else.
90
90
 
91
91
  Read the generated migration before moving on. A drop migration that also touches an unrelated table
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dknet-messaging-events
3
- description: Explains how this template wires SlimMessageBus as its command/query/event backbone, how the two domain-event styles (manual AddEvent vs declared RaisesEvent) reach a subscriber, and how to forward an event to an external Azure Service Bus topic. Use whenever adding a domain event, wiring an internal or external event consumer, or reasoning about how a command/query travels from an endpoint to its handler.
3
+ description: Explains how this template wires SlimMessageBus as its command/query/event backbone, how the three domain-event raise styles ([RaisesEvent], AddEvent<TEvent>(), AddEvent(instance)) reach a subscriber, and how to forward an event to an external Azure Service Bus topic. Use whenever adding a domain event, wiring an internal or external event consumer, or reasoning about how a command/query travels from an endpoint to its handler.
4
4
  ---
5
5
 
6
6
  # DKNet messaging and events (SlimMessageBus)
@@ -31,12 +31,12 @@ Handlers never call `SaveChanges`. `AddSlimBusEfCoreInterceptor<CoreDbContext>()
31
31
  successfully.
32
32
 
33
33
  **Auto-discovery, no per-message registration.** `AutoDeclareFrom(serviceAssembly)` scans the
34
- `Minimal.AppServices` assembly and declares every request/handler pair it finds by convention;
34
+ `<YourApp>.AppServices` assembly and declares every request/handler pair it finds by convention;
35
35
  `AddServicesFromAssembly(serviceAssembly)` registers the discovered handler classes in DI. Adding a
36
36
  new `*Request` + `*Handler` pair needs no wiring beyond writing the two classes — see the
37
37
  `dknet-crud` skill for how requests, validators, and handlers are shaped.
38
38
 
39
- ## Wiring: `Minimal.Infra/Extensions/ServiceBusSetup.cs`
39
+ ## Wiring: `<YourApp>.Infra/Extensions/ServiceBusSetup.cs`
40
40
 
41
41
  ```csharp
42
42
  public static IServiceCollection AddServiceBus(
@@ -89,13 +89,20 @@ header envelope or JSON round-trip. `EnableBlockingPublish = false` means `bus.P
89
89
  domain event does not wait for every subscriber to finish before returning — a slow or hung internal
90
90
  consumer does not block the HTTP response.
91
91
 
92
- ## Domain events: two styles, same publisher
92
+ ## Domain events: three raise styles, same publisher
93
93
 
94
- Both styles are covered in full in the `dknet-entity` skill; here only what matters for
95
- messaging.
94
+ All are covered in full in the `dknet-entity` and `dknet-ddd-principles` skills; here only what
95
+ matters for messaging. Prefer them in this order — `[RaisesEvent]`, then `AddEvent<TEvent>()`, then
96
+ `AddEvent(instance)`.
96
97
 
97
- - **Manual** — `PurchaseOrder`'s constructor calls `AddEvent(new PurchaseOrderCreatedEvent(...))` by
98
- hand; the record is a plain hand-written type next to the entity.
98
+ - **Manual, instance** — `PurchaseOrder`'s constructor calls
99
+ `AddEvent(new PurchaseOrderCreatedEvent(...))` by hand; the record is a plain hand-written type
100
+ next to the entity. Last resort: use it only when the payload is not a projection of the entity.
101
+ - **Manual, type-only** — `AddEvent<TEvent>()` queues the event *type*; the publisher maps the
102
+ entity onto it via `IMapper` when the save succeeds, so there is no hand-written payload. It
103
+ **requires an `IMapper` registration** — without one the publisher throws `EventException` instead
104
+ of dropping the event. Use it when the decision to raise needs real logic but the payload does
105
+ not.
99
106
  - **Declared** — `Product` carries `[RaisesEvent(EventOperations.Created, Include = [...])]` and
100
107
  `[RaisesEvent(EventOperations.Updated, nameof(Price))]`; DKNet's EF Core save hook raises the event
101
108
  itself after a successful save. Composed names fold the narrowing property in:
@@ -104,7 +111,7 @@ messaging.
104
111
  property's value actually changed on that save — calling `ChangePrice` with the price it already
105
112
  holds raises nothing.
106
113
 
107
- Either way, the entity only **queues** the event. `Minimal.Infra/Services/EventPublisher.cs` is what
114
+ Either way, the entity only **queues** the event. `<YourApp>.Infra/Services/EventPublisher.cs` is what
108
115
  actually calls the bus:
109
116
 
110
117
  ```csharp
@@ -128,13 +135,13 @@ a specific order across multiple handlers of the same event, and multiple consum
128
135
  allowed (both an internal and an external consumer subscribe to the same `ProductCreatedEvent`, see
129
136
  below).
130
137
 
131
- **Consumers are always hand-written.** Neither `[RaisesEvent]` nor `AddEvent` generates a consumer —
132
- only the raise side is automatic for the declared style.
138
+ **Consumers are always hand-written.** Neither `[RaisesEvent]` nor either `AddEvent` overload
139
+ generates a consumer — only the raise side is automatic for the declared style.
133
140
 
134
- Internal consumers live in `Minimal.AppServices/<Feature>/V1/Events/`:
141
+ Internal consumers live in `<YourApp>.AppServices/<Feature>/V1/Events/`:
135
142
 
136
143
  ```csharp
137
- // Minimal.AppServices/ManualSample/V1/Events/PurchaseOrderCreatedEventHandler.cs
144
+ // <YourApp>.AppServices/ManualSample/V1/Events/PurchaseOrderCreatedEventHandler.cs
138
145
  internal sealed class PurchaseOrderCreatedEventHandler(ILogger<PurchaseOrderCreatedEventHandler> logger)
139
146
  : Fluents.EventsConsumers.IHandler<PurchaseOrderCreatedEvent>
140
147
  {
@@ -152,7 +159,7 @@ internal sealed class PurchaseOrderCreatedEventHandler(ILogger<PurchaseOrderCrea
152
159
  ```
153
160
 
154
161
  ```csharp
155
- // Minimal.AppServices/AutomatedSample/V1/Events/ProductEventHandlers.cs
162
+ // <YourApp>.AppServices/AutomatedSample/V1/Events/ProductEventHandlers.cs
156
163
  internal sealed class ProductCreatedEventHandler(ILogger<ProductCreatedEventHandler> logger)
157
164
  : Fluents.EventsConsumers.IHandler<ProductCreatedEvent>
158
165
  {
@@ -171,7 +178,7 @@ A second child bus, `"AzureBus"`, is added only when **both** conditions hold:
171
178
 
172
179
  | Condition | Where |
173
180
  |---|---|
174
- | `FeatureManagement:EnableServiceBus` is `true` | `Minimal.Share/Options/FeatureOptions.cs` |
181
+ | `FeatureManagement:EnableServiceBus` is `true` | `<YourApp>.Share/Options/FeatureOptions.cs` |
175
182
  | `ConnectionStrings:AzureBus` is a non-empty connection string | checked in `AddServiceBus` |
176
183
 
177
184
  ```csharp
@@ -228,12 +235,12 @@ The same event type — `ProductCreatedEvent` — flows on both buses. There is
228
235
  event record. The `Produce`/`Consume` declaration in `AddAzureBus` is what forwards an
229
236
  already-declared internal event externally; nothing about the event itself changes.
230
237
 
231
- External consumers live in `Minimal.Infra/Features/<Feature>/ExternalEvents/`, are `internal
238
+ External consumers live in `<YourApp>.Infra/Features/<Feature>/ExternalEvents/`, are `internal
232
239
  sealed`, and are discovered by the same `AddServicesFromAssembly(typeof(InfraSetup).Assembly)` call
233
240
  inside `AddAzureBus` — no separate registration:
234
241
 
235
242
  ```csharp
236
- // Minimal.Infra/Features/AutomatedSample/ExternalEvents/ProductCreatedNotificationHandler.cs
243
+ // <YourApp>.Infra/Features/AutomatedSample/ExternalEvents/ProductCreatedNotificationHandler.cs
237
244
  internal sealed class ProductCreatedNotificationHandler(ILogger<ProductCreatedNotificationHandler> logger)
238
245
  : Fluents.EventsConsumers.IHandler<ProductCreatedEvent>
239
246
  {
@@ -259,7 +266,7 @@ internal sealed class ProductCreatedNotificationHandler(ILogger<ProductCreatedNo
259
266
  .WithConsumer<THandler>());
260
267
  ```
261
268
  2. Write `THandler` as a `Fluents.EventsConsumers.IHandler<TEvent>` under
262
- `Minimal.Infra/Features/<Feature>/ExternalEvents/`. External-system concerns belong in `Infra`,
269
+ `<YourApp>.Infra/Features/<Feature>/ExternalEvents/`. External-system concerns belong in `Infra`,
263
270
  never `AppServices`.
264
271
  3. Nothing else — `azb.AddServicesFromAssembly(typeof(InfraSetup).Assembly)` already picks up the
265
272
  new handler by assembly scan.
@@ -274,7 +281,7 @@ azb.Consume<TExternalEvent>(o => o.Path("<their-topic-name>")
274
281
  .WithConsumer<THandler>());
275
282
  ```
276
283
 
277
- `THandler` still goes in `Minimal.Infra/Features/<Feature>/ExternalEvents/` and still needs no
284
+ `THandler` still goes in `<YourApp>.Infra/Features/<Feature>/ExternalEvents/` and still needs no
278
285
  manual DI registration. Do not add a matching `azb.Produce<TExternalEvent>(...)` — that would make
279
286
  this service claim ownership of an event type it does not raise.
280
287
 
@@ -289,13 +296,13 @@ from Azure Service Bus: `ProductCreatedEvent` is still published in-memory and h
289
296
 
290
297
  ## Local development
291
298
 
292
- `Minimal.AppHost/AppHost.cs` (Aspire orchestration) wires only Redis and PostgreSQL today:
299
+ `<YourApp>.AppHost/AppHost.cs` (Aspire orchestration) wires only Redis and PostgreSQL today:
293
300
 
294
301
  ```csharp
295
302
  var cache = builder.AddRedis("Redis");
296
303
  var postgres = builder.AddPostgres("Postgres");
297
304
  ...
298
- builder.AddProject("Api", "../Minimal.Api/Minimal.Api.csproj")
305
+ builder.AddProject("Api", "../<YourApp>.Api/<YourApp>.Api.csproj")
299
306
  .WithReference(cache, "Redis")
300
307
  .WithReference(apDb, "AppDb")
301
308
  //.WaitFor(bus)
@@ -304,7 +311,7 @@ builder.AddProject("Api", "../Minimal.Api/Minimal.Api.csproj")
304
311
  ```
305
312
 
306
313
  The `.WaitFor(bus)` line is commented out and no `bus` resource is added above it — no Azure Service
307
- Bus emulator is wired into `AppHost.cs` as shipped. `Minimal.AppHost/Configs/busConfig.json` exists
314
+ Bus emulator is wired into `AppHost.cs` as shipped. `<YourApp>.AppHost/Configs/busConfig.json` exists
308
315
  and is copied to the build output, but nothing in `AppHost.cs` references it — it's a config file
309
316
  waiting for an emulator resource, not something a consumer touches to run the app today.
310
317
 
@@ -314,7 +321,7 @@ DKNet also carries an `Aspire.Hosting.ServiceBus` project that runs the emulator
314
321
 
315
322
  ## Testing events
316
323
 
317
- **BDD — log-capture pattern.** `Minimal.App.TestSupport/TestLogCapture.cs` is an `ILoggerProvider`
324
+ **BDD — log-capture pattern.** `<YourApp>.App.TestSupport/TestLogCapture.cs` is an `ILoggerProvider`
318
325
  that queues every formatted log line into an in-memory collection, registered as an additional
319
326
  provider alongside the host's normal logging. A scenario asserts on the resulting text instead of on
320
327
  internal call order:
@@ -381,15 +388,15 @@ that path as untested until you add integration coverage against a real namespac
381
388
  - **What you might expect:** an `[RaisesEvent(EventOperations.Updated, ...)]` fires on every call to
382
389
  the method that touches that property. **What actually happens:** it only fires when the value
383
390
  actually changed on that save — see `dknet-entity` for the mechanics.
384
- - **What you might expect:** placing a new event consumer in `Minimal.Api` gets it discovered like
385
- the others. **What actually happens:** discovery only scans the `Minimal.AppServices` assembly
386
- (internal) and the `Minimal.Infra` assembly (external, inside `AddAzureBus`). A consumer in
387
- `Minimal.Api` is never registered.
391
+ - **What you might expect:** placing a new event consumer in `<YourApp>.Api` gets it discovered like
392
+ the others. **What actually happens:** discovery only scans the `<YourApp>.AppServices` assembly
393
+ (internal) and the `<YourApp>.Infra` assembly (external, inside `AddAzureBus`). A consumer in
394
+ `<YourApp>.Api` is never registered.
388
395
  - **What you might expect:** setting `EnableServiceBus: true` is enough to start producing to Azure.
389
396
  **What actually happens:** `ConnectionStrings:AzureBus` must also be a non-empty string. Either one
390
397
  missing and the `AzureBus` child bus, and everything registered only on it, silently does not exist
391
398
  — no error, no log, just no external traffic.
392
399
  - **What you might expect:** an external consumer belongs next to the internal one, in
393
- `Minimal.AppServices/<Feature>/V1/Events/`. **What actually happens:** external-system consumers
394
- belong in `Minimal.Infra/Features/<Feature>/ExternalEvents/` — that is the assembly `AddAzureBus`
400
+ `<YourApp>.AppServices/<Feature>/V1/Events/`. **What actually happens:** external-system consumers
401
+ belong in `<YourApp>.Infra/Features/<Feature>/ExternalEvents/` — that is the assembly `AddAzureBus`
395
402
  scans, and it keeps the external-system dependency out of `AppServices`.
@@ -5,7 +5,7 @@ description: Add DKNet's Core, EF Core, messaging/CQRS, or blob-storage NuGet pa
5
5
 
6
6
  # Skill: Adopting DKNet Packages in an Existing Project
7
7
 
8
- This skill is for a project that already exists with its own namespaces and folder layout — it does not assume `dotnet new dknet-minimal` was run, and never references `Minimal.*` types. If you're scaffolding a brand-new solution from the template instead, use **dknet-project-structure** and the other `dknet-*` skills.
8
+ This skill is for a project that already exists with its own namespaces and folder layout — it does not assume `dotnet new dknet-minimal` was run, and never references the template's `<YourApp>.*` types. If you're scaffolding a brand-new solution from the template instead, use **dknet-project-structure** and the other `dknet-*` skills.
9
9
 
10
10
  Each package below is independent — install only what the feature needs. All packages target **.NET 10.0+** (EF Core packages additionally need **EF Core 10.0+**); consult `Directory.Packages.props` (or your project's own central version file) before adding a version attribute per-project.
11
11
 
@@ -232,7 +232,7 @@ Swapping providers later (e.g. `.Local` in dev, `.AzureStorage` in production) o
232
232
 
233
233
  - [ ] Only the packages the feature actually needs were added (no blanket "add everything")
234
234
  - [ ] EF Core additions layer onto the existing `DbContext`/provider — no assumption of a specific database engine
235
- - [ ] No `Minimal.*` namespace or template folder path (`Minimal.Domains`, `Minimal.AppServices`, …) appears anywhere in the guidance followed
235
+ - [ ] No template-generated `<YourApp>.*` namespace or template folder path (`<YourApp>.Domains`, `<YourApp>.AppServices`, …) appears anywhere in the guidance followed
236
236
  - [ ] Repositories/specs/handlers registered in DI (`AddSpecRepo`, `AddSlimBusEfCoreInterceptor`, `AddAzureStorageAdapter`, etc.) — nothing relies on auto-discovery unless the package documents it
237
237
  - [ ] For blob storage, exactly one provider package installed alongside `Abstractions`
238
238
  - [ ] `dotnet build` passes with the new package references
@@ -245,7 +245,7 @@ Swapping providers later (e.g. `.Local` in dev, `.AzureStorage` in production) o
245
245
  | Building a dynamic predicate directly against `DbContext` without `.AsExpandable()` | Required for LinqKit to translate the expression; the `IRepositorySpec` extensions already apply it |
246
246
  | Installing more than one blob storage provider package for the same `IBlobService` | Register exactly one — the last registration wins and the others are dead weight |
247
247
  | Assuming a specific EF Core provider (SQL Server, Postgres, …) is required | Every package here is provider-agnostic; it only needs a working `DbContext` |
248
- | Copying `Minimal.*` namespaces/paths from the template's docs | This skill — and any project using it — has its own namespaces; the template's layout doesn't apply |
248
+ | Copying the template's `<YourApp>.*` namespaces/paths from the template's docs | This skill — and any project using it — has its own namespaces; the template's layout doesn't apply |
249
249
 
250
250
  ## Next Steps
251
251
 
@@ -11,7 +11,7 @@ instead — this skill only covers cross-cutting platform wiring.
11
11
 
12
12
  ## Start-up order
13
13
 
14
- `Minimal.Api/Program.cs`, in the order it actually runs:
14
+ `<YourApp>.Api/Program.cs`, in the order it actually runs:
15
15
 
16
16
  ```csharp
17
17
  var builder = WebApplication.CreateBuilder(args);
@@ -114,7 +114,7 @@ registration (needed because `ConfigureHttpJsonOptions` has no service-provider
114
114
 
115
115
  ## `FeatureManagement` flags
116
116
 
117
- Section name is `FeatureManagement` (`FeatureOptions.Name`), bound in `Minimal.Api/Program.cs` via
117
+ Section name is `FeatureManagement` (`FeatureOptions.Name`), bound in `<YourApp>.Api/Program.cs` via
118
118
  `GetSection(FeatureOptions.Name).Get<FeatureOptions>()`. **Every JSON key must spell a
119
119
  `FeatureOptions` property name exactly** — `Get<FeatureOptions>()` silently ignores an unknown key
120
120
  instead of failing, so a typo no-ops rather than erroring.
@@ -131,7 +131,7 @@ instead of failing, so a typo no-ops rather than erroring.
131
131
  | `EnableRateLimit` | `true` | `true` | `false` | `false` | `Configs/RateLimits/RateLimitConfig.cs` |
132
132
  | `EnableRequestBounds` | `true` | `true` | **`false`** | — | `Configs/RequestBoundsConfig.cs` |
133
133
  | `EnableSecurityHeaders` | `true` | `true` | **`false`** | — | `Configs/SecurityHeadersConfig.cs` |
134
- | `EnableServiceBus` | `false` | `true` | `false` | — | `Minimal.Infra/Extensions/ServiceBusSetup.cs` (Azure child bus only) |
134
+ | `EnableServiceBus` | `false` | `true` | `false` | — | `<YourApp>.Infra/Extensions/ServiceBusSetup.cs` (Azure child bus only) |
135
135
  | `EnableSwagger` | `false` | `false` | `true` | — | `Configs/Swagger/SwaggerConfig.cs` |
136
136
  | `EnableVersioning` | `true` | `true` | — | — | `Configs/VersioningConfig.cs` |
137
137
  | `RequireAuthorization` | `false` | **`true`** | `false` | `false` | `Configs/Auth/AuthConfig.cs` |
@@ -149,7 +149,7 @@ alone does nothing — the Azure child bus is added only when the flag is `true`
149
149
  `ConnectionStrings:AzureBus` is non-empty; the in-memory child bus that carries internal
150
150
  command/event dispatch is unconditional.
151
151
 
152
- **Adding a flag**: add the `bool` property to `Minimal.Share/Options/FeatureOptions.cs`, add the
152
+ **Adding a flag**: add the `bool` property to `<YourApp>.Share/Options/FeatureOptions.cs`, add the
153
153
  same-spelled key to every `appsettings*.json` that needs a non-default value, and consume it either
154
154
  as `features.YourFlag` inside `AppConfig.cs`/`ServiceConfigs.cs` (both already receive a
155
155
  `FeatureOptions features` parameter) or via `IOptions<FeatureOptions>` injected anywhere else in DI.
@@ -158,7 +158,7 @@ as `features.YourFlag` inside `AppConfig.cs`/`ServiceConfigs.cs` (both already r
158
158
 
159
159
  | Section | Keys | Shipped default | Reads |
160
160
  |---|---|---|---|
161
- | `ConnectionStrings` | `AppDb`, `Redis`, `AzureBus`, `AzureAppConfig` | all `""` except overlays | `AppDb` → `Minimal.Infra/Extensions/InfraSetup.cs`, `DbMigration.cs`; `Redis` → `CacheConfig.cs`, `AppConfig.cs` (idempotency store); `AzureBus` → `ServiceBusSetup.cs`; `AzureAppConfig` → `AzureAppConfigSetup.cs` |
161
+ | `ConnectionStrings` | `AppDb`, `Redis`, `AzureBus`, `AzureAppConfig` | all `""` except overlays | `AppDb` → `<YourApp>.Infra/Extensions/InfraSetup.cs`, `DbMigration.cs`; `Redis` → `CacheConfig.cs`, `AppConfig.cs` (idempotency store); `AzureBus` → `ServiceBusSetup.cs`; `AzureAppConfig` → `AzureAppConfigSetup.cs` |
162
162
  | `Authentication:Schemes:Bearer` | `MetadataAddress`, `ValidAudiences`, `ValidIssuer` | placeholder tenant/audience | bound by ASP.NET Core's own `AddJwtBearer()`; registered only when `RequireAuthorization` is on |
163
163
  | `Cors` | `AllowedOrigins` (`[]`), `AllowedMethods` (`GET,POST,PUT,PATCH`), `AllowedHeaders` (`Authorization,Content-Type,Accept,X-Idempotency-Key`) | empty origins ⇒ CORS not wired at all | `Configs/CrosConfig.cs` |
164
164
  | `Security` | `TrustedProxies` (IP list), `TrustedNetworks` (CIDR list) | both `[]` | `Configs/ForwardedHeadersConfig.cs` — both empty ⇒ `ForwardedHeaders.None` |
@@ -167,10 +167,10 @@ as `features.YourFlag` inside `AppConfig.cs`/`ServiceConfigs.cs` (both already r
167
167
  | `RateLimit` | `DefaultRequestLimit`, `DefaultConcurrentLimit`, `TimeWindowInSeconds` | class default `2/2/1s`; base file `100/20/1s`; Development `1/1/10s` | `Configs/RateLimits/RateLimitConfig.cs` |
168
168
  | `AzureAppConfiguration` | `KeyPrefix`, `Label`, `CacheExpirationInSeconds`, `LoadFeatureFlags`, `FeatureFlagPrefix` | ships in base file | **dead** — see below |
169
169
  | `AzureAppConfig` | `ConnectionStringName` (`AzureAppConfig`), `Label` (`null`→`SharedConsts.ApiName`), `LoadFeatureFlags`, `FeatureFlagPrefix`, `RefreshIntervalInMinutes` | not shipped in base file | `Configs/AzureAppConfig/AzureAppConfigSetup.cs` — the last three properties are declared but never read; refresh is hard-coded to 30 minutes |
170
- | `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME` | flat keys, not a section | `http://localhost:4317`; `Minimal.Api` | `Configs/LogConfigs.cs` reads only the endpoint key as a presence check; `OTEL_SERVICE_NAME` is never read by template code |
170
+ | `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME` | flat keys, not a section | `http://localhost:4317`; `<YourApp>.Api` | `Configs/LogConfigs.cs` reads only the endpoint key as a presence check; `OTEL_SERVICE_NAME` is never read by template code |
171
171
  | `AzureMonitor:ConnectionString` | — | `""` | `Configs/LogConfigs.cs` — non-blank adds `UseAzureMonitor()` |
172
172
  | `DKNet:ListQuery` | `DefaultPageSize`, `MaxPageSize`, `DefaultActivityWindowMonths` | package defaults (1000/1000/3) | consumed by the generic `MapGetList<TEntity,TKey,TDto>` route |
173
- | `SampleData:RecordsPerEntity` | — | `10000` | `Minimal.AppHost/appsettings.json`, read by `AppHost.cs` only — never present in the API |
173
+ | `SampleData:RecordsPerEntity` | — | `10000` | `<YourApp>.AppHost/appsettings.json`, read by `AppHost.cs` only — never present in the API |
174
174
 
175
175
  `AzureAppConfiguration` (note the longer name) is a real trap: it ships in the base
176
176
  `appsettings.json` but binds to nothing — `AzureAppConfigOptions.Name` is `"AzureAppConfig"`, a
@@ -265,7 +265,7 @@ option nor an option's value; `JobRegistry.Jobs` maps recognized names (case-ins
265
265
  message-bus connection opened.
266
266
 
267
267
  ```bash
268
- dotnet run --project ApiEndpoints/Minimal.Api -- migration
268
+ dotnet run --project ApiEndpoints/<YourApp>.Api -- migration
269
269
  ```
270
270
 
271
271
  `RunDbMigrationWhenAppStart` is the in-process alternative: same `MigrationJob.RunAsync` call, made
@@ -277,13 +277,13 @@ Keep a job as small as `MigrationJob`: it receives the builder as it stood right
277
277
  `AddLogConfig`, with no DI container built yet, so it constructs what it needs directly from
278
278
  `builder.Configuration`/`builder.Services.BuildServiceProvider()` rather than resolving from a host.
279
279
 
280
- ## Aspire (`Minimal.AppHost`)
280
+ ## Aspire (`<YourApp>.AppHost`)
281
281
 
282
282
  `AppHost.cs` provisions `Redis` and `Postgres` (with an `AppDb` database), starts the `Api` project
283
- by path (`../Minimal.Api/Minimal.Api.csproj` — a literal path string, not `AddProject<T>`, so it
283
+ by path (`../<YourApp>.Api/<YourApp>.Api.csproj` — a literal path string, not `AddProject<T>`, so it
284
284
  survives `sourceName` rewriting even for a dotted name), and calls `.WaitFor(cache).WaitFor(apDb)`.
285
285
  Azure Service Bus is **not** wired — `.WaitFor(bus)` is commented out in source; only Redis and
286
- PostgreSQL resources exist. `Minimal.AppHost/Configs/busConfig.json` (an Azure Service Bus emulator
286
+ PostgreSQL resources exist. `<YourApp>.AppHost/Configs/busConfig.json` (an Azure Service Bus emulator
287
287
  topology file for the `product-tp`/`product-sub` topic) sits in the project but nothing in
288
288
  `AppHost.cs` references it — it is not currently wired to any resource.
289
289
 
@@ -297,15 +297,15 @@ role-gated filtering is visible on a freshly started host.
297
297
 
298
298
  ```bash
299
299
  # Full stack — Redis + PostgreSQL via Docker, sample data generated
300
- dotnet run --project ApiEndpoints/Minimal.AppHost
300
+ dotnet run --project ApiEndpoints/<YourApp>.AppHost
301
301
 
302
302
  # API only — no containers, needs ConnectionStrings:AppDb supplied yourself
303
- dotnet run --project ApiEndpoints/Minimal.Api
303
+ dotnet run --project ApiEndpoints/<YourApp>.Api
304
304
  ```
305
305
 
306
306
  ## Test hosts
307
307
 
308
- `Minimal.App.TestSupport/TestApiFactoryBase.cs` is the shared `WebApplicationFactory<Program>` both
308
+ `<YourApp>.App.TestSupport/TestApiFactoryBase.cs` is the shared `WebApplicationFactory<Program>` both
309
309
  xUnit and BDD suites subclass. It always: `UseEnvironment("Testing")`; pushes
310
310
  `FeatureManagement:RunDbMigrationWhenAppStart=false`, `EnableSwagger=false`,
311
311
  `EnableAzureAppConfig=false`, `ConnectionStrings:AppDb=UseInMemory` through