@dennisrongo/skills 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/bin/claude-skills.js +189 -0
- package/lib/install.js +111 -0
- package/lib/list.js +63 -0
- package/lib/paths.js +39 -0
- package/lib/remove.js +52 -0
- package/lib/skills.js +121 -0
- package/package.json +48 -0
- package/skills/_template/SKILL.md +34 -0
- package/skills/conventional-commits/SKILL.md +136 -0
- package/skills/diagnose/SKILL.md +140 -0
- package/skills/dotnet-onion-api/SKILL.md +267 -0
- package/skills/dotnet-onion-api/references/anti-patterns.md +155 -0
- package/skills/dotnet-onion-api/references/solution-layout.md +113 -0
- package/skills/dotnet-onion-api/references/templates/appsettings.md +75 -0
- package/skills/dotnet-onion-api/references/templates/base-controller.cs.md +90 -0
- package/skills/dotnet-onion-api/references/templates/csproj-files.md +178 -0
- package/skills/dotnet-onion-api/references/templates/dbcontext.cs.md +149 -0
- package/skills/dotnet-onion-api/references/templates/exception-middleware.cs.md +101 -0
- package/skills/dotnet-onion-api/references/templates/feature-slice.md +349 -0
- package/skills/dotnet-onion-api/references/templates/program-cs.md +171 -0
- package/skills/dotnet-onion-api/references/templates/worker-program.cs.md +166 -0
- package/skills/grill-with-docs/SKILL.md +203 -0
- package/skills/handoff/SKILL.md +155 -0
- package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/skills/improve-codebase-architecture/SKILL.md +121 -0
- package/skills/nextjs-app-router/SKILL.md +328 -0
- package/skills/nextjs-app-router/references/anti-patterns.md +203 -0
- package/skills/nextjs-app-router/references/folder-layout.md +151 -0
- package/skills/nextjs-app-router/references/good-patterns.md +212 -0
- package/skills/nextjs-app-router/references/templates/api-base.md +62 -0
- package/skills/nextjs-app-router/references/templates/api-slice.md +75 -0
- package/skills/nextjs-app-router/references/templates/auth-slice.md +95 -0
- package/skills/nextjs-app-router/references/templates/ci-and-hooks.md +94 -0
- package/skills/nextjs-app-router/references/templates/components-json.md +41 -0
- package/skills/nextjs-app-router/references/templates/env-and-utils.md +110 -0
- package/skills/nextjs-app-router/references/templates/eslint-prettier.md +82 -0
- package/skills/nextjs-app-router/references/templates/feature-slice.md +184 -0
- package/skills/nextjs-app-router/references/templates/form-with-zod.md +192 -0
- package/skills/nextjs-app-router/references/templates/middleware.md +88 -0
- package/skills/nextjs-app-router/references/templates/next-config.md +42 -0
- package/skills/nextjs-app-router/references/templates/package.md +99 -0
- package/skills/nextjs-app-router/references/templates/providers-and-store.md +79 -0
- package/skills/nextjs-app-router/references/templates/root-layout.md +146 -0
- package/skills/nextjs-app-router/references/templates/route-group-layouts.md +90 -0
- package/skills/nextjs-app-router/references/templates/tailwind-config.md +89 -0
- package/skills/nextjs-app-router/references/templates/testing.md +137 -0
- package/skills/nextjs-app-router/references/templates/tsconfig.md +40 -0
- package/skills/plan-and-build/SKILL.md +214 -0
- package/skills/pr-review/SKILL.md +132 -0
- package/skills/write-a-skill/SKILL.md +191 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dotnet-onion-api
|
|
3
|
+
description: Scaffold a new .NET solution (Web API + Worker microservices) using ONION architecture and EF Core, codifying battle-tested layered patterns and explicitly avoiding the common pitfalls of legacy stored-procedure-centric codebases. Use this skill whenever the user asks to "create a new dotnet project", "scaffold a .NET API", "new C# solution", "add a worker microservice", "add a feature end-to-end", or mentions "ONION", "Clean Architecture", "Onion Architecture", or "the layered .NET patterns I like". Three modes — (1) full solution scaffold, (2) add a feature slice through all layers, (3) add a BackgroundService worker microservice.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# .NET ONION API Scaffolder
|
|
7
|
+
|
|
8
|
+
Generate a production-grade .NET solution that keeps the **good** layered patterns (Api → Application → Infrastructure → Domain separation, base classes for cross-cutting concerns, extension-method wiring in `Program.cs`, JWT, AutoMapper, auto-DI, unified error responses) and eliminates the **bad** ones often seen in legacy .NET codebases (stored-procedure-centric reflection repositories, EF6 on netstandard2.1, polling console-app workers, swallowed exceptions, mutable per-request state on base service classes, mixed ADO/Dapper/EF6 data access, missing `CancellationToken` plumbing).
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
Trigger on any of:
|
|
13
|
+
|
|
14
|
+
- "create a new dotnet project / .NET solution / C# project"
|
|
15
|
+
- "scaffold a .NET Web API"
|
|
16
|
+
- "new microservice in .NET" / "add a worker service"
|
|
17
|
+
- "use the patterns I like" / "use my layered .NET conventions" (in a .NET context)
|
|
18
|
+
- "ONION architecture" / "Clean Architecture" / "layered .NET project"
|
|
19
|
+
- "add a feature end-to-end" / "add a slice" (API + service + repository + EF entity + tests)
|
|
20
|
+
- The user pastes a feature spec and asks you to wire it through the layers of a .NET solution
|
|
21
|
+
|
|
22
|
+
If unsure whether the user wants a brand-new solution vs. an addition to an existing one, ask once — don't guess.
|
|
23
|
+
|
|
24
|
+
## Three operating modes
|
|
25
|
+
|
|
26
|
+
Pick the mode from the user's request. If ambiguous, ask.
|
|
27
|
+
|
|
28
|
+
| Mode | Trigger | Output |
|
|
29
|
+
|------|---------|--------|
|
|
30
|
+
| **`scaffold-solution`** | "new project", "scaffold solution", empty directory | Full ONION solution: Domain, Application, Infrastructure, Api, Workers (optional), Tests. |
|
|
31
|
+
| **`add-feature`** | "add `<Entity>` end-to-end", "wire up `<feature>` through all layers" | Entity + EF config + repository (port + adapter) + use-case service + DTO + controller + AutoMapper profile + unit test. |
|
|
32
|
+
| **`add-worker`** | "new worker", "add microservice for queue X" | New `Workers.<Name>` project (BackgroundService) referencing Application + Infrastructure, with queue/service-bus consumer and graceful shutdown. |
|
|
33
|
+
|
|
34
|
+
## Workflow
|
|
35
|
+
|
|
36
|
+
### Step 1 — Determine the target framework (don't hard-code)
|
|
37
|
+
|
|
38
|
+
The user explicitly does **not** want a hard-coded `<TargetFramework>` baked into the skill. Before generating `.csproj` files:
|
|
39
|
+
|
|
40
|
+
1. Check the user's environment first: run `dotnet --list-sdks` to see installed SDKs.
|
|
41
|
+
2. If a current LTS SDK is installed, prefer the highest installed LTS (`net8.0`, `net10.0`, etc.).
|
|
42
|
+
3. If unsure which is the current LTS, fetch the latest .NET support policy via context7 (`mcp__plugin_context7_context7__resolve-library-id` → `query-docs` for ".NET release schedule" / "dotnet support policy") or WebFetch `https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core`. Quote the version you picked back to the user before generating.
|
|
43
|
+
4. Pin EF Core, ASP.NET Core, and `Microsoft.Extensions.*` package versions to the **latest stable for that TFM** — look them up via context7 (`Microsoft.EntityFrameworkCore`, `Microsoft.AspNetCore.Authentication.JwtBearer`, etc.) rather than guessing. Never hand-paste a version you don't have a source for.
|
|
44
|
+
5. State the chosen TFM and package versions in your reply before writing files, so the user can object before scaffolding.
|
|
45
|
+
|
|
46
|
+
### Step 2 — Gather inputs (ask once, in one batch)
|
|
47
|
+
|
|
48
|
+
For `scaffold-solution`, use `AskUserQuestion` to collect:
|
|
49
|
+
|
|
50
|
+
- **Solution name** (e.g. `Acme.Billing`). Used for namespace root and `.sln`.
|
|
51
|
+
- **First feature/entity** (optional, e.g. `Customer`) — if provided, also run `add-feature` for it after scaffold.
|
|
52
|
+
- **Auth**: JWT bearer with HS256 (symmetric) **or** JWT with asymmetric (RS256/JWKS) **or** none-yet.
|
|
53
|
+
- **Include any Workers now?** If yes, ask for their names (comma-separated, e.g. `EmailSender, PdfPrinter`).
|
|
54
|
+
- **Optional extras**: Serilog + OpenTelemetry? Dockerfile? GitHub Actions CI? Testcontainers integration tests? (Default: skip — only add if asked.)
|
|
55
|
+
|
|
56
|
+
For `add-feature`: entity name, properties (name + C# type + nullability), whether it needs CRUD controller or only specific endpoints.
|
|
57
|
+
|
|
58
|
+
For `add-worker`: worker name, trigger source (Azure Storage Queue / Service Bus / Timer), message DTO type (if known).
|
|
59
|
+
|
|
60
|
+
### Step 3 — Generate files
|
|
61
|
+
|
|
62
|
+
Use the **templates in `references/templates/`** as the source of truth for file contents. Apply these rules:
|
|
63
|
+
|
|
64
|
+
- Use `Write` for new files. Never use `Edit` on files you're creating fresh.
|
|
65
|
+
- Replace all `{{Solution}}`, `{{Feature}}`, `{{Worker}}` placeholders consistently.
|
|
66
|
+
- Create directories before files. On Windows shell use PowerShell `New-Item -ItemType Directory -Force`.
|
|
67
|
+
- After scaffolding, run `dotnet sln add` for every project and `dotnet build` to verify the solution compiles. Report build output to the user.
|
|
68
|
+
- Do **not** run `dotnet new` to create the projects — write the `.csproj` and `.cs` files directly from templates so the layout matches exactly.
|
|
69
|
+
|
|
70
|
+
### Step 4 — Verify and report
|
|
71
|
+
|
|
72
|
+
- Run `dotnet build` from the solution root. If it fails, fix and rebuild — never leave a broken scaffold.
|
|
73
|
+
- Run `dotnet test` if any test project exists.
|
|
74
|
+
- Reply with a short summary: solution path, projects created, TFM chosen, package versions, next steps (e.g. "run `dotnet ef migrations add Initial -p src/{{Solution}}.Infrastructure -s src/{{Solution}}.Api`").
|
|
75
|
+
|
|
76
|
+
## Solution layout (canonical)
|
|
77
|
+
|
|
78
|
+
See [`references/solution-layout.md`](references/solution-layout.md) for the full tree and dependency rules.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
{{Solution}}/
|
|
82
|
+
{{Solution}}.sln
|
|
83
|
+
src/
|
|
84
|
+
{{Solution}}.Domain # entities, value objects, domain events. NO project refs.
|
|
85
|
+
{{Solution}}.Application # use-case services + ports (interfaces) + DTOs + validators. refs Domain.
|
|
86
|
+
{{Solution}}.Infrastructure # EF Core DbContext, repository adapters, external clients (Azure, email, auth). refs Application+Domain.
|
|
87
|
+
{{Solution}}.Api # Controllers, Middleware, Filters, Program.cs. refs Application+Infrastructure.
|
|
88
|
+
{{Solution}}.Workers.<Name> # BackgroundService microservices. refs Application+Infrastructure.
|
|
89
|
+
{{Solution}}.Contracts # (optional) public DTOs / API contracts shared with clients.
|
|
90
|
+
tests/
|
|
91
|
+
{{Solution}}.UnitTests # xUnit + NSubstitute. Tests Application use-cases with mocked ports.
|
|
92
|
+
{{Solution}}.IntegrationTests# WebApplicationFactory + Testcontainers (SQL Server). Real DB + real pipeline.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Dependency rule (enforced):** outer → inner only. Domain has zero project references. Application references Domain only. Infrastructure may reference both. Api/Workers reference Application + Infrastructure but never each other.
|
|
96
|
+
|
|
97
|
+
## Required code patterns
|
|
98
|
+
|
|
99
|
+
Use these exact patterns when generating files. Full templates are in `references/templates/`.
|
|
100
|
+
|
|
101
|
+
### Keep
|
|
102
|
+
|
|
103
|
+
- **`BaseController`** with `[ApiController]`, `[Route("api/[controller]")]`, `[Authorize]`, injected `IUserContext` — see [`references/templates/base-controller.cs.md`](references/templates/base-controller.cs.md).
|
|
104
|
+
- **Base use-case service** with constructor-injected `IUserContext` (no public mutable user property — a common bug in legacy bases).
|
|
105
|
+
- **Thin `Program.cs`** that calls only extension methods (`AddApplication`, `AddInfrastructure`, `AddApiServices`, `AddJwtAuth`, `AddSwaggerDocs`, `AddCorsPolicies`). See [`references/templates/program-cs.md`](references/templates/program-cs.md).
|
|
106
|
+
- **Auto-registration** via `Scrutor` for ports → adapters (replaces `NetCore.AutoRegisterDi`, modern + maintained). Singletons/options registered explicitly.
|
|
107
|
+
- **Strongly-typed `AppSettings` + `ConnectionStrings`** bound via `IOptions<T>` (don't register the raw POCO as singleton — use `services.Configure<T>(...)` and inject `IOptions<T>`).
|
|
108
|
+
- **AutoMapper** with assembly scan: `services.AddAutoMapper(typeof(ApplicationAssemblyMarker).Assembly)`.
|
|
109
|
+
- **Centralized exception middleware** with env-aware response — see [`references/templates/exception-middleware.cs.md`](references/templates/exception-middleware.cs.md).
|
|
110
|
+
- **Unified validation error response** via `InvalidModelStateResponseFactory`.
|
|
111
|
+
- **JWT auth wiring** in a `RegisterAuth` extension method.
|
|
112
|
+
|
|
113
|
+
### Eliminate (anti-patterns)
|
|
114
|
+
|
|
115
|
+
Every one of these is forbidden in generated code. See [`references/anti-patterns.md`](references/anti-patterns.md) for the rationale of each.
|
|
116
|
+
|
|
117
|
+
- ❌ Stored-procedure-first data access. **Use EF Core with LINQ**; only drop to raw SQL via `FromSqlInterpolated`/`ExecuteSqlInterpolated` for legitimate perf/legacy reasons, and never with reflection-based parameter mapping.
|
|
118
|
+
- ❌ `dynamic` / `ExpandoObject` for query parameters.
|
|
119
|
+
- ❌ Reflection-based `DataRow → object` mappers. EF Core handles this.
|
|
120
|
+
- ❌ EF6 + `netstandard2.1`. Use **EF Core (latest)** on the chosen TFM.
|
|
121
|
+
- ❌ Empty `catch {}` blocks. Either handle the exception meaningfully or let it propagate to the middleware.
|
|
122
|
+
- ❌ Mutable `public User { get; set; }` on a service base class — request-scoped state belongs in the scoped `IUserContext` only.
|
|
123
|
+
- ❌ Polling `while (true) { Task.Delay(5s) }` console-app workers. Use `BackgroundService` with `CancellationToken stoppingToken` and SDK-native receive loops — see [`references/templates/worker-program.cs.md`](references/templates/worker-program.cs.md).
|
|
124
|
+
- ❌ Booting hosted services with `serviceProvider.GetService<T>()` in `Program.cs`. Register them via `services.AddHostedService<T>()`.
|
|
125
|
+
- ❌ Auto-registering everything as `Scoped` indiscriminately. Use Scrutor's lifetime selectors, and register Azure SDK clients / `IHttpClientFactory` clients / options as singletons explicitly.
|
|
126
|
+
- ❌ Newtonsoft.Json + `DefaultContractResolver` (PascalCase). Use **System.Text.Json** with `JsonNamingPolicy.CamelCase` by default. Add Newtonsoft only if a specific dependency demands it.
|
|
127
|
+
- ❌ Missing `CancellationToken` parameters. Every async public method takes `CancellationToken ct` as the **last** parameter and forwards it.
|
|
128
|
+
- ❌ Per-tenant repositories under `Repositories/{TenantName}/`. Multi-tenant behavior goes through a strategy injected via DI, not folder forks.
|
|
129
|
+
- ❌ Hard-coded multi-tenant magic fallbacks (e.g. defaulting `ClientId` to a literal string when claims are missing). Multi-tenancy comes from `IUserContext` or fails fast.
|
|
130
|
+
- ❌ Commented-out dead code in generated files.
|
|
131
|
+
- ❌ Controllers that bypass `BaseController` (consistency is mandatory; if a public endpoint needs `[AllowAnonymous]`, declare it on the action).
|
|
132
|
+
|
|
133
|
+
## Operating-mode playbooks
|
|
134
|
+
|
|
135
|
+
### Mode 1 — `scaffold-solution`
|
|
136
|
+
|
|
137
|
+
1. Pick TFM and package versions per **Step 1**. Quote them.
|
|
138
|
+
2. Ask the inputs per **Step 2**. Wait for answers.
|
|
139
|
+
3. Generate, in this order:
|
|
140
|
+
1. `.sln` file (use `dotnet new sln -n {{Solution}}` *only* to produce the sln; everything else is hand-written from templates).
|
|
141
|
+
2. `src/{{Solution}}.Domain/` (csproj + `DomainAssemblyMarker.cs` + sample `Entity` base if relevant).
|
|
142
|
+
3. `src/{{Solution}}.Application/` (csproj + assembly marker + `Common/` with `IUserContext`, `Result<T>` if requested, `IUnitOfWork` port, `IRepository<T>` port).
|
|
143
|
+
4. `src/{{Solution}}.Infrastructure/` (csproj + `Persistence/AppDbContext.cs` + `Persistence/EntityConfigurations/` folder + `Persistence/UnitOfWork.cs` + `Auth/UserContext.cs` + `DependencyInjection.cs` with `AddInfrastructure`).
|
|
144
|
+
5. `src/{{Solution}}.Api/` (csproj + `Program.cs` + `Extensions/` folder + `Middlewares/ExceptionHandlerMiddleware.cs` + `Controllers/BaseController.cs` + `appsettings.json`/`appsettings.Development.json`).
|
|
145
|
+
6. `src/{{Solution}}.Workers.<Name>/` per worker requested.
|
|
146
|
+
7. `tests/{{Solution}}.UnitTests/` (csproj + xUnit + NSubstitute + AutoFixture).
|
|
147
|
+
8. `tests/{{Solution}}.IntegrationTests/` (csproj + `Microsoft.AspNetCore.Mvc.Testing` + `Testcontainers.MsSql`) — only if user asked for it.
|
|
148
|
+
4. `dotnet sln {{Solution}}.sln add` every project (one command, all projects).
|
|
149
|
+
5. `dotnet build` — must succeed.
|
|
150
|
+
6. If a first feature was requested, immediately run **Mode 2** for that entity.
|
|
151
|
+
7. Report.
|
|
152
|
+
|
|
153
|
+
### Mode 2 — `add-feature`
|
|
154
|
+
|
|
155
|
+
For entity `{{Feature}}` (e.g. `Customer`):
|
|
156
|
+
|
|
157
|
+
1. **Domain**: `src/{{Solution}}.Domain/{{Feature}}s/{{Feature}}.cs` — POCO entity with a private parameterless ctor for EF, a public ctor for invariants, and behavior methods (avoid anemic models). Add domain events only if asked.
|
|
158
|
+
2. **Application**:
|
|
159
|
+
- Port: `src/{{Solution}}.Application/{{Feature}}s/I{{Feature}}Repository.cs` (interface with CRUD methods that take `CancellationToken`).
|
|
160
|
+
- DTOs: `Application/{{Feature}}s/Dtos/{{Feature}}Dto.cs`, `Create{{Feature}}Request.cs`, `Update{{Feature}}Request.cs`.
|
|
161
|
+
- Use-case service: `Application/{{Feature}}s/{{Feature}}Service.cs` + interface `I{{Feature}}Service.cs`. Service depends on `I{{Feature}}Repository`, `IUnitOfWork`, `IMapper`. Pure orchestration — no EF references.
|
|
162
|
+
- AutoMapper profile: `Application/{{Feature}}s/Mapping/{{Feature}}Profile.cs`.
|
|
163
|
+
- FluentValidation validator (only if user asked for FluentValidation; otherwise rely on DataAnnotations + the validation factory).
|
|
164
|
+
3. **Infrastructure**:
|
|
165
|
+
- EF configuration: `Infrastructure/Persistence/EntityConfigurations/{{Feature}}Configuration.cs` (implements `IEntityTypeConfiguration<{{Feature}}>`).
|
|
166
|
+
- Repository adapter: `Infrastructure/Persistence/Repositories/{{Feature}}Repository.cs` (implements `I{{Feature}}Repository` using `AppDbContext`).
|
|
167
|
+
- Register the DbSet on `AppDbContext`.
|
|
168
|
+
4. **Api**:
|
|
169
|
+
- Controller: `Api/Controllers/{{Feature}}sController.cs` inheriting `BaseController`, injecting `I{{Feature}}Service`. Standard REST endpoints, returning DTOs only.
|
|
170
|
+
5. **Tests**:
|
|
171
|
+
- `tests/{{Solution}}.UnitTests/{{Feature}}s/{{Feature}}ServiceTests.cs` — xUnit + NSubstitute, covers the service's happy path + one validation/edge case.
|
|
172
|
+
|
|
173
|
+
Template for the full slice is in [`references/templates/feature-slice.md`](references/templates/feature-slice.md).
|
|
174
|
+
|
|
175
|
+
After generating: `dotnet build` then `dotnet test`. Both must pass.
|
|
176
|
+
|
|
177
|
+
### Mode 3 — `add-worker`
|
|
178
|
+
|
|
179
|
+
For worker `{{Worker}}` (e.g. `EmailSender`):
|
|
180
|
+
|
|
181
|
+
1. Create `src/{{Solution}}.Workers.{{Worker}}/`:
|
|
182
|
+
- `csproj` per [`references/templates/worker-csproj.md`](references/templates/worker-csproj.md).
|
|
183
|
+
- `Program.cs` using `Host.CreateApplicationBuilder` per [`references/templates/worker-program.cs.md`](references/templates/worker-program.cs.md).
|
|
184
|
+
- `Worker.cs` — `BackgroundService` subclass; loop driven by `stoppingToken`; respects graceful shutdown.
|
|
185
|
+
- `appsettings.json` + `appsettings.Development.json`.
|
|
186
|
+
2. Reference `Application` + `Infrastructure` (never `Api`).
|
|
187
|
+
3. Add to `.sln`. `dotnet build`.
|
|
188
|
+
4. **No `while (true) { ... await Task.Delay(5s) }`** — use the SDK's receive loop (e.g. `await foreach (var msg in receiver.ReceiveMessagesAsync(stoppingToken))` for Service Bus, or `await queueClient.ReceiveMessagesAsync(maxMessages, ct: stoppingToken)` inside a `while (!stoppingToken.IsCancellationRequested)` loop).
|
|
189
|
+
|
|
190
|
+
## NuGet packages (resolve latest stable at scaffold time)
|
|
191
|
+
|
|
192
|
+
Look these up via context7 — do not hand-paste versions:
|
|
193
|
+
|
|
194
|
+
**Api project**
|
|
195
|
+
- `Microsoft.AspNetCore.Authentication.JwtBearer`
|
|
196
|
+
- `Microsoft.AspNetCore.OpenApi`
|
|
197
|
+
- `Swashbuckle.AspNetCore`
|
|
198
|
+
- `AutoMapper.Extensions.Microsoft.DependencyInjection` (or `AutoMapper` 13+ which self-registers)
|
|
199
|
+
- `Scrutor` (assembly-scanning DI)
|
|
200
|
+
- `Serilog.AspNetCore` + `Serilog.Sinks.Console` (only if user opted into Serilog)
|
|
201
|
+
|
|
202
|
+
**Application project**
|
|
203
|
+
- `MediatR` only if user explicitly asks for CQRS; default is plain service classes
|
|
204
|
+
- `FluentValidation` only if requested
|
|
205
|
+
|
|
206
|
+
**Infrastructure project**
|
|
207
|
+
- `Microsoft.EntityFrameworkCore.SqlServer`
|
|
208
|
+
- `Microsoft.EntityFrameworkCore.Design` (PrivateAssets="all")
|
|
209
|
+
- `Microsoft.EntityFrameworkCore.Tools` (PrivateAssets="all")
|
|
210
|
+
- `Microsoft.Data.SqlClient`
|
|
211
|
+
- `Azure.Storage.Blobs`, `Azure.Storage.Queues`, `Azure.Messaging.ServiceBus` (only if used)
|
|
212
|
+
|
|
213
|
+
**Worker projects**
|
|
214
|
+
- `Microsoft.Extensions.Hosting`
|
|
215
|
+
- Same Azure SDK packages as Infrastructure (only what the worker actually uses)
|
|
216
|
+
|
|
217
|
+
**Test projects**
|
|
218
|
+
- `Microsoft.NET.Test.Sdk`
|
|
219
|
+
- `xunit`, `xunit.runner.visualstudio`
|
|
220
|
+
- `NSubstitute` (preferred over Moq — cleaner API, actively maintained)
|
|
221
|
+
- `FluentAssertions`
|
|
222
|
+
- `Testcontainers.MsSql` (integration tests only)
|
|
223
|
+
- `Microsoft.AspNetCore.Mvc.Testing` (integration tests only)
|
|
224
|
+
|
|
225
|
+
## Verification checklist before reporting "done"
|
|
226
|
+
|
|
227
|
+
- [ ] `dotnet build` exits 0.
|
|
228
|
+
- [ ] `dotnet test` exits 0 (if tests were generated).
|
|
229
|
+
- [ ] No `.csproj` references violate the ONION rule (verify with `grep`/`Select-String` for cross-layer `ProjectReference`).
|
|
230
|
+
- [ ] No file contains any pattern from the **Eliminate** list.
|
|
231
|
+
- [ ] `Program.cs` is under ~50 lines (everything else is in extension methods).
|
|
232
|
+
- [ ] Domain project's `.csproj` has zero `<ProjectReference>` entries.
|
|
233
|
+
|
|
234
|
+
If any check fails, fix before reporting. Don't claim success with a known-broken scaffold.
|
|
235
|
+
|
|
236
|
+
## Examples
|
|
237
|
+
|
|
238
|
+
### Example 1: Fresh solution
|
|
239
|
+
|
|
240
|
+
**User:** "Scaffold a new dotnet API project called `Acme.Billing` using my ONION patterns. Add a `Customer` feature too."
|
|
241
|
+
|
|
242
|
+
**Claude:**
|
|
243
|
+
1. Runs `dotnet --list-sdks`, checks context7 for current LTS, picks (e.g.) `net8.0`.
|
|
244
|
+
2. Reports chosen TFM + package versions; waits for confirmation if anything looks off.
|
|
245
|
+
3. Asks the Step-2 questions (auth flavor, workers, extras).
|
|
246
|
+
4. Generates the full solution + the `Customer` feature slice.
|
|
247
|
+
5. Runs `dotnet build` and `dotnet test`.
|
|
248
|
+
6. Reports the tree and the EF migration command.
|
|
249
|
+
|
|
250
|
+
### Example 2: Add a feature
|
|
251
|
+
|
|
252
|
+
**User:** "Add an `Invoice` feature end-to-end to the existing solution."
|
|
253
|
+
|
|
254
|
+
**Claude:** Runs Mode 2 only — generates Domain entity, Application port + service + DTOs, Infrastructure config + repository, Api controller, unit test. Builds and tests.
|
|
255
|
+
|
|
256
|
+
### Example 3: Add a worker
|
|
257
|
+
|
|
258
|
+
**User:** "Add a worker that processes the `print-jobs` Azure Storage queue."
|
|
259
|
+
|
|
260
|
+
**Claude:** Runs Mode 3 — generates `Workers.PrintJobs` project with a `BackgroundService` that receives messages with `stoppingToken`, calls into an Application service, deletes on success. Wires it into the .sln. Builds.
|
|
261
|
+
|
|
262
|
+
## Notes
|
|
263
|
+
|
|
264
|
+
- **Don't over-engineer**: don't add MediatR, CQRS, MassTransit, Polly, MinimalAPI conversions, Result<T> patterns, or domain events unless the user asks. Pragmatic > pure.
|
|
265
|
+
- **Don't rewrite the user's existing codebase** as part of this skill. This skill is for *new* scaffolds, not migrations. If the user wants a migration plan, that's a different conversation.
|
|
266
|
+
- **Multi-tenancy**: if the new project needs it, generate a `TenantContext` mirroring `IUserContext` and use EF Core query filters (`HasQueryFilter`) rather than per-tenant repositories.
|
|
267
|
+
- **Always quote the TFM and package versions you chose before writing files** — the user explicitly asked not to hard-code them.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Anti-patterns — what NOT to generate
|
|
2
|
+
|
|
3
|
+
Each item lists a common legacy pattern, why it's wrong, and what to do instead. The skill must never emit any of these in generated code.
|
|
4
|
+
|
|
5
|
+
## 1. Stored procedures as the dominant data path
|
|
6
|
+
|
|
7
|
+
**Anti-pattern:** Every repository calls a custom `Database`/`BaseData` wrapper around ADO.NET, executing `usp_Foo`-style stored procedures; business logic lives in SQL.
|
|
8
|
+
|
|
9
|
+
**Why bad:** Untyped, no refactoring tooling, untestable, no LINQ, no compile-time safety, hides domain logic in the DB.
|
|
10
|
+
|
|
11
|
+
**Do instead:** EF Core + LINQ for all reads/writes. Drop to `FromSqlInterpolated`/`ExecuteSqlInterpolated` *only* for legitimate perf hotspots or legacy sproc reuse, and use the interpolation form (parameters are bound automatically). Never use reflection-based parameter mapping.
|
|
12
|
+
|
|
13
|
+
## 2. `dynamic` / `ExpandoObject` parameter mapping
|
|
14
|
+
|
|
15
|
+
**Anti-pattern:** A `GetParameters(dynamic item)` helper that walks properties or ExpandoObject keys and switch-maps types to `SqlDbType`.
|
|
16
|
+
|
|
17
|
+
**Why bad:** Runtime errors, no IntelliSense, silent truncation (`SqlDbType.VarChar` defaults to 8000), terrible for code review.
|
|
18
|
+
|
|
19
|
+
**Do instead:** Strongly-typed request/DTO records. EF Core binds them.
|
|
20
|
+
|
|
21
|
+
## 3. Reflection-based `DataRow → object` mappers
|
|
22
|
+
|
|
23
|
+
**Anti-pattern:** A `MakeObjectFromDataRow` helper with a giant switch on `PropertyType.Name`, custom date parsing, swallowed exceptions on `SetValue`.
|
|
24
|
+
|
|
25
|
+
**Why bad:** Slow, ignores nullability, eats errors, can't be debugged.
|
|
26
|
+
|
|
27
|
+
**Do instead:** EF Core materializes entities. For ad-hoc projections, use `Select(x => new MyDto { ... })` in LINQ — fully typed.
|
|
28
|
+
|
|
29
|
+
## 4. EF6 on `netstandard2.1`
|
|
30
|
+
|
|
31
|
+
**Anti-pattern:** `EntityFramework 6.4.4` referenced from `netstandard2.1` libraries, EF Core nowhere.
|
|
32
|
+
|
|
33
|
+
**Why bad:** EF6 is maintenance-mode; no async-streaming, no compiled queries, no `IAsyncEnumerable`, no migrations bundles.
|
|
34
|
+
|
|
35
|
+
**Do instead:** **EF Core (latest stable for the chosen TFM)**, modern provider (`Microsoft.EntityFrameworkCore.SqlServer`).
|
|
36
|
+
|
|
37
|
+
## 5. Empty `catch {}`
|
|
38
|
+
|
|
39
|
+
**Anti-pattern:** Wrapping a SQL call (or any operation) in `try { ... } catch (Exception) { }` and returning a sentinel like `-1`.
|
|
40
|
+
|
|
41
|
+
**Why bad:** Bugs become silent. Failures masquerade as success.
|
|
42
|
+
|
|
43
|
+
**Do instead:** Let exceptions propagate to the centralized exception middleware. If a specific exception is genuinely recoverable, catch *that* type and log/handle explicitly.
|
|
44
|
+
|
|
45
|
+
## 6. Mutable public state on a service base class
|
|
46
|
+
|
|
47
|
+
**Anti-pattern:** `public class BaseService { public User User { get; set; } }` — a public setter on a scoped service.
|
|
48
|
+
|
|
49
|
+
**Why bad:** Anyone can stomp it. Risks cross-request leaks if lifetime gets botched.
|
|
50
|
+
|
|
51
|
+
**Do instead:** Inject `IUserContext` (scoped). No setters. The service reads `UserContext.UserId` etc. on demand.
|
|
52
|
+
|
|
53
|
+
## 7. Polling-loop workers as console apps
|
|
54
|
+
|
|
55
|
+
**Anti-pattern:**
|
|
56
|
+
```csharp
|
|
57
|
+
while (true)
|
|
58
|
+
{
|
|
59
|
+
var msgs = await queueClient.ReceiveMessagesAsync(maxMessages: 1);
|
|
60
|
+
if (msgs.Value.Length == 0) { await Task.Delay(5000); continue; }
|
|
61
|
+
...
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Why bad:** No graceful shutdown, no DI lifetime management, no health checks, can't be hosted alongside other services, no `IConfiguration` change tokens, no structured logging out of the box.
|
|
66
|
+
|
|
67
|
+
**Do instead:** `BackgroundService` with `protected override async Task ExecuteAsync(CancellationToken stoppingToken)`. Use `stoppingToken` in every `await`. Use `Host.CreateApplicationBuilder` + `services.AddHostedService<Worker>()`.
|
|
68
|
+
|
|
69
|
+
## 8. Booting hosted services with `serviceProvider.GetService<T>()`
|
|
70
|
+
|
|
71
|
+
**Anti-pattern:** Resolving a "listener" type from the service provider in `Program.cs` solely for its constructor side effects (starting a background loop).
|
|
72
|
+
|
|
73
|
+
**Why bad:** Brittle (forgets to resolve = silently dead). No lifetime hook. Can't unit test the hosted lifecycle.
|
|
74
|
+
|
|
75
|
+
**Do instead:** Implement `IHostedService` (or inherit `BackgroundService`) and register with `services.AddHostedService<MyListener>()`. The host owns start/stop.
|
|
76
|
+
|
|
77
|
+
## 9. Indiscriminate `Scoped` auto-registration
|
|
78
|
+
|
|
79
|
+
**Anti-pattern:** `RegisterAssemblyPublicNonGenericClasses(...).AsPublicImplementedInterfaces(ServiceLifetime.Scoped)` for every assembly, including ones that contain singletons.
|
|
80
|
+
|
|
81
|
+
**Why bad:** Singletons (Azure SDK clients, third-party notification clients, options) accidentally become scoped or vice versa. Memory leaks, perf issues, race conditions.
|
|
82
|
+
|
|
83
|
+
**Do instead:** Use Scrutor with lifetime selectors and exclude marker interfaces for non-scoped types:
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
services.Scan(s => s
|
|
87
|
+
.FromAssemblyOf<ApplicationAssemblyMarker>()
|
|
88
|
+
.AddClasses(c => c.Where(t => t.Name.EndsWith("Service")))
|
|
89
|
+
.AsImplementedInterfaces()
|
|
90
|
+
.WithScopedLifetime());
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Register singletons (Azure SDK clients, HttpClient factories, options) explicitly.
|
|
94
|
+
|
|
95
|
+
## 10. Newtonsoft.Json + PascalCase by default
|
|
96
|
+
|
|
97
|
+
**Anti-pattern:** `AddNewtonsoftJson(... new DefaultContractResolver())` with a TODO comment "use camel case later".
|
|
98
|
+
|
|
99
|
+
**Why bad:** PascalCase breaks JS/TS clients by convention; Newtonsoft is slower, larger, and not the framework default any more.
|
|
100
|
+
|
|
101
|
+
**Do instead:** Stick with System.Text.Json (default). Configure `JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase` if you want to be explicit (it's the default anyway in ASP.NET Core). Only add Newtonsoft for specific compatibility needs.
|
|
102
|
+
|
|
103
|
+
## 11. Missing `CancellationToken`
|
|
104
|
+
|
|
105
|
+
**Anti-pattern:** Async methods with no `CancellationToken` parameter, so client disconnects don't cancel server-side work.
|
|
106
|
+
|
|
107
|
+
**Do instead:** Every public async method takes `CancellationToken ct = default` as the **last** parameter and forwards it to every awaited call (`ToListAsync(ct)`, `SaveChangesAsync(ct)`, etc.). Controllers accept the framework-provided `HttpContext.RequestAborted` implicitly when you add `CancellationToken ct` to the action signature.
|
|
108
|
+
|
|
109
|
+
## 12. Per-tenant repository folders
|
|
110
|
+
|
|
111
|
+
**Anti-pattern:** `FormRepositories/TenantA/`, `FormRepositories/TenantB/` — branching tenant logic at the repository layer with parallel folders per customer.
|
|
112
|
+
|
|
113
|
+
**Why bad:** Doesn't scale, duplicates code, hides shared behavior, requires registering different implementations per tenant.
|
|
114
|
+
|
|
115
|
+
**Do instead:** One repository. Multi-tenant variations live behind a `ITenantStrategy` (or similar) port resolved at runtime, or via EF Core query filters tied to the current tenant ID.
|
|
116
|
+
|
|
117
|
+
## 13. Hard-coded multi-tenant fallback
|
|
118
|
+
|
|
119
|
+
**Anti-pattern:** A base data class constructor that assigns a literal default to the tenant/client identifier before reading claims (e.g. `DataContext.ClientId = "default";`).
|
|
120
|
+
|
|
121
|
+
**Why bad:** Silently routes anonymous/broken requests to a default tenant — security hole.
|
|
122
|
+
|
|
123
|
+
**Do instead:** `IUserContext.ClientId` either has a value or you reject the request. No magic defaults. If you legitimately have a public path, branch explicitly.
|
|
124
|
+
|
|
125
|
+
## 14. Commented-out dead code
|
|
126
|
+
|
|
127
|
+
**Anti-pattern:** Large `// var existingFactor = await ...` blocks left in repository code "in case we need it later".
|
|
128
|
+
|
|
129
|
+
**Why bad:** Confuses readers, rots, never accurate again.
|
|
130
|
+
|
|
131
|
+
**Do instead:** Delete it. If you might need it later, that's what git is for.
|
|
132
|
+
|
|
133
|
+
## 15. Controllers that bypass the base class
|
|
134
|
+
|
|
135
|
+
**Anti-pattern:** A controller (often `AuthController`) inheriting `ControllerBase` directly to escape the `[Authorize]` default — losing every other cross-cutting filter in the process.
|
|
136
|
+
|
|
137
|
+
**Why bad:** Inconsistent cross-cutting concerns; easy to forget a filter.
|
|
138
|
+
|
|
139
|
+
**Do instead:** All controllers inherit `BaseController`. For public endpoints, decorate the *action* with `[AllowAnonymous]`.
|
|
140
|
+
|
|
141
|
+
## 16. Mixed data access (EF6 + Dapper + ADO.NET sprocs)
|
|
142
|
+
|
|
143
|
+
**Anti-pattern:** All three coexist in the same codebase, often in the same project.
|
|
144
|
+
|
|
145
|
+
**Why bad:** Three sets of conventions, three ways to fail, three connection-management stories.
|
|
146
|
+
|
|
147
|
+
**Do instead:** EF Core is the default. Dapper is acceptable as a perf escape hatch for read-heavy queries — register it side-by-side but don't sprinkle it everywhere.
|
|
148
|
+
|
|
149
|
+
## 17. Registering POCO config as a singleton
|
|
150
|
+
|
|
151
|
+
**Anti-pattern:** `services.AddSingleton(_ => builder.Configuration.GetSection("AppSettings").Get<AppSettings>())`.
|
|
152
|
+
|
|
153
|
+
**Why bad:** Doesn't react to `IOptionsMonitor` changes; couples consumers to the POCO; bypasses the options validation pipeline.
|
|
154
|
+
|
|
155
|
+
**Do instead:** `services.AddOptions<AppSettings>().Bind(config.GetSection("AppSettings")).ValidateDataAnnotations().ValidateOnStart();` — inject `IOptions<AppSettings>` (or `IOptionsSnapshot<T>` if you need reload).
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Solution layout
|
|
2
|
+
|
|
3
|
+
Canonical folder + project structure for every solution this skill generates.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
{{Solution}}/
|
|
7
|
+
├── {{Solution}}.sln
|
|
8
|
+
├── .editorconfig
|
|
9
|
+
├── .gitignore
|
|
10
|
+
├── Directory.Build.props # optional: shared <LangVersion>, <Nullable>, analyzers
|
|
11
|
+
├── global.json # pins the SDK band (major.minor.x), not the exact patch
|
|
12
|
+
├── src/
|
|
13
|
+
│ ├── {{Solution}}.Domain/
|
|
14
|
+
│ │ ├── {{Solution}}.Domain.csproj
|
|
15
|
+
│ │ ├── DomainAssemblyMarker.cs
|
|
16
|
+
│ │ └── <Entity>/Entity.cs ...
|
|
17
|
+
│ ├── {{Solution}}.Application/
|
|
18
|
+
│ │ ├── {{Solution}}.Application.csproj
|
|
19
|
+
│ │ ├── ApplicationAssemblyMarker.cs
|
|
20
|
+
│ │ ├── DependencyInjection.cs # AddApplication(services)
|
|
21
|
+
│ │ ├── Common/
|
|
22
|
+
│ │ │ ├── IUserContext.cs
|
|
23
|
+
│ │ │ ├── IUnitOfWork.cs
|
|
24
|
+
│ │ │ └── Exceptions/ # NotFoundException, ValidationException, ConflictException
|
|
25
|
+
│ │ └── <Feature>/
|
|
26
|
+
│ │ ├── I{{Feature}}Repository.cs # port
|
|
27
|
+
│ │ ├── I{{Feature}}Service.cs
|
|
28
|
+
│ │ ├── {{Feature}}Service.cs
|
|
29
|
+
│ │ ├── Dtos/...
|
|
30
|
+
│ │ └── Mapping/{{Feature}}Profile.cs
|
|
31
|
+
│ ├── {{Solution}}.Infrastructure/
|
|
32
|
+
│ │ ├── {{Solution}}.Infrastructure.csproj
|
|
33
|
+
│ │ ├── DependencyInjection.cs # AddInfrastructure(services, config)
|
|
34
|
+
│ │ ├── Auth/
|
|
35
|
+
│ │ │ └── UserContext.cs # IUserContext implementation (reads claims via IHttpContextAccessor)
|
|
36
|
+
│ │ └── Persistence/
|
|
37
|
+
│ │ ├── AppDbContext.cs
|
|
38
|
+
│ │ ├── UnitOfWork.cs # IUnitOfWork adapter wrapping AppDbContext.SaveChangesAsync
|
|
39
|
+
│ │ ├── Migrations/ # generated by `dotnet ef migrations add`
|
|
40
|
+
│ │ ├── EntityConfigurations/
|
|
41
|
+
│ │ │ └── {{Feature}}Configuration.cs
|
|
42
|
+
│ │ └── Repositories/
|
|
43
|
+
│ │ └── {{Feature}}Repository.cs
|
|
44
|
+
│ ├── {{Solution}}.Api/
|
|
45
|
+
│ │ ├── {{Solution}}.Api.csproj
|
|
46
|
+
│ │ ├── Program.cs
|
|
47
|
+
│ │ ├── appsettings.json
|
|
48
|
+
│ │ ├── appsettings.Development.json
|
|
49
|
+
│ │ ├── Extensions/
|
|
50
|
+
│ │ │ ├── ApiBehaviorExtensions.cs # ConfigureControllers, ConfigureApiBehavior
|
|
51
|
+
│ │ │ ├── AuthExtensions.cs # AddJwtAuth
|
|
52
|
+
│ │ │ ├── CorsExtensions.cs
|
|
53
|
+
│ │ │ └── SwaggerExtensions.cs
|
|
54
|
+
│ │ ├── Middlewares/
|
|
55
|
+
│ │ │ └── ExceptionHandlerMiddleware.cs
|
|
56
|
+
│ │ ├── Filters/ # action filters if needed
|
|
57
|
+
│ │ ├── Models/
|
|
58
|
+
│ │ │ ├── ApiException.cs
|
|
59
|
+
│ │ │ └── ApiValidationErrorResponse.cs
|
|
60
|
+
│ │ └── Controllers/
|
|
61
|
+
│ │ ├── BaseController.cs
|
|
62
|
+
│ │ └── {{Feature}}sController.cs
|
|
63
|
+
│ └── {{Solution}}.Workers.{{Worker}}/
|
|
64
|
+
│ ├── {{Solution}}.Workers.{{Worker}}.csproj
|
|
65
|
+
│ ├── Program.cs
|
|
66
|
+
│ ├── Worker.cs # BackgroundService
|
|
67
|
+
│ ├── appsettings.json
|
|
68
|
+
│ └── appsettings.Development.json
|
|
69
|
+
└── tests/
|
|
70
|
+
├── {{Solution}}.UnitTests/
|
|
71
|
+
│ ├── {{Solution}}.UnitTests.csproj
|
|
72
|
+
│ └── <Feature>/{{Feature}}ServiceTests.cs
|
|
73
|
+
└── {{Solution}}.IntegrationTests/ # optional
|
|
74
|
+
├── {{Solution}}.IntegrationTests.csproj
|
|
75
|
+
├── ApiFactory.cs # WebApplicationFactory<Program> + Testcontainers MsSql
|
|
76
|
+
└── <Feature>/{{Feature}}EndpointTests.cs
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Dependency rules (enforced)
|
|
80
|
+
|
|
81
|
+
| From → To | Domain | Application | Infrastructure | Api | Workers | Contracts |
|
|
82
|
+
|---------------------|--------|-------------|----------------|-----|---------|-----------|
|
|
83
|
+
| **Domain** | — | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
84
|
+
| **Application** | ✅ | — | ❌ | ❌ | ❌ | ✅ (opt) |
|
|
85
|
+
| **Infrastructure** | ✅ | ✅ | — | ❌ | ❌ | ❌ |
|
|
86
|
+
| **Api** | ✅ | ✅ | ✅ | — | ❌ | ✅ (opt) |
|
|
87
|
+
| **Workers.<Name>** | ✅ | ✅ | ✅ | ❌ | — | ✅ (opt) |
|
|
88
|
+
| **Tests** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
89
|
+
|
|
90
|
+
- Domain has **zero** `<ProjectReference>` entries. Verify after scaffold.
|
|
91
|
+
- Api and Workers never reference each other.
|
|
92
|
+
- `Contracts` (if generated) only contains DTOs and has no project refs.
|
|
93
|
+
|
|
94
|
+
## Namespace convention
|
|
95
|
+
|
|
96
|
+
- Root namespace = solution name, e.g. `Acme.Billing`.
|
|
97
|
+
- Project namespaces match folders: `Acme.Billing.Application.Customers`, `Acme.Billing.Infrastructure.Persistence.Repositories`, etc.
|
|
98
|
+
- Assembly markers live at the project root for `typeof(...).Assembly` lookups (AutoMapper, Scrutor scans).
|
|
99
|
+
|
|
100
|
+
## `global.json`
|
|
101
|
+
|
|
102
|
+
Pin the SDK *band*, not the exact patch — so the solution builds on whichever patch the developer has installed:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"sdk": {
|
|
107
|
+
"version": "8.0.0",
|
|
108
|
+
"rollForward": "latestFeature"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Set `version` to the major.minor.0 of the TFM chosen in Step 1.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# `appsettings.json` templates
|
|
2
|
+
|
|
3
|
+
## `src/{{Solution}}.Api/appsettings.json`
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"Logging": {
|
|
8
|
+
"LogLevel": {
|
|
9
|
+
"Default": "Information",
|
|
10
|
+
"Microsoft.AspNetCore": "Warning"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"ConnectionStrings": {
|
|
14
|
+
"Default": "Server=(localdb)\\MSSQLLocalDB;Database={{Solution}};Trusted_Connection=True;TrustServerCertificate=True"
|
|
15
|
+
},
|
|
16
|
+
"AppSettings": {
|
|
17
|
+
"SecretToken": "REPLACE_ME_WITH_A_LONG_RANDOM_STRING_AT_LEAST_32_CHARS",
|
|
18
|
+
"AllowedOrigins": [ "https://localhost:5173" ]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## `src/{{Solution}}.Api/appsettings.Development.json`
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"Logging": {
|
|
28
|
+
"LogLevel": {
|
|
29
|
+
"Default": "Debug",
|
|
30
|
+
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Strongly-typed binding (in `AddApiServices`)
|
|
37
|
+
|
|
38
|
+
```csharp
|
|
39
|
+
services.AddOptions<AppSettings>()
|
|
40
|
+
.Bind(config.GetSection("AppSettings"))
|
|
41
|
+
.ValidateDataAnnotations()
|
|
42
|
+
.ValidateOnStart();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## `AppSettings.cs` POCO (lives in Api project — Api is the only consumer of API-specific settings)
|
|
46
|
+
|
|
47
|
+
```csharp
|
|
48
|
+
using System.ComponentModel.DataAnnotations;
|
|
49
|
+
|
|
50
|
+
namespace {{Solution}}.Api.Configuration;
|
|
51
|
+
|
|
52
|
+
public sealed class AppSettings
|
|
53
|
+
{
|
|
54
|
+
[Required, MinLength(32)]
|
|
55
|
+
public string SecretToken { get; set; } = string.Empty;
|
|
56
|
+
|
|
57
|
+
public string[] AllowedOrigins { get; set; } = Array.Empty<string>();
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Secrets
|
|
62
|
+
|
|
63
|
+
In `Development`, the `SecretToken` must come from User Secrets, not `appsettings.Development.json` (don't commit secrets):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
dotnet user-secrets init --project src/{{Solution}}.Api
|
|
67
|
+
dotnet user-secrets set "AppSettings:SecretToken" "<long-random-string>" --project src/{{Solution}}.Api
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
In `Production`, set via environment variables or your secret store (Key Vault, etc.). The double-underscore form binds to nested keys:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
AppSettings__SecretToken=...
|
|
74
|
+
ConnectionStrings__Default=...
|
|
75
|
+
```
|