agentic-team-templates 0.13.2 → 0.15.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/README.md +6 -1
- package/package.json +1 -1
- package/src/index.js +91 -13
- package/src/index.test.js +95 -1
- package/templates/cpp-expert/.cursorrules/concurrency.md +211 -0
- package/templates/cpp-expert/.cursorrules/error-handling.md +170 -0
- package/templates/cpp-expert/.cursorrules/memory-and-ownership.md +220 -0
- package/templates/cpp-expert/.cursorrules/modern-cpp.md +211 -0
- package/templates/cpp-expert/.cursorrules/overview.md +87 -0
- package/templates/cpp-expert/.cursorrules/performance.md +223 -0
- package/templates/cpp-expert/.cursorrules/testing.md +230 -0
- package/templates/cpp-expert/.cursorrules/tooling.md +312 -0
- package/templates/cpp-expert/CLAUDE.md +242 -0
- package/templates/csharp-expert/.cursorrules/aspnet-core.md +311 -0
- package/templates/csharp-expert/.cursorrules/async-patterns.md +206 -0
- package/templates/csharp-expert/.cursorrules/dependency-injection.md +206 -0
- package/templates/csharp-expert/.cursorrules/error-handling.md +235 -0
- package/templates/csharp-expert/.cursorrules/language-features.md +204 -0
- package/templates/csharp-expert/.cursorrules/overview.md +92 -0
- package/templates/csharp-expert/.cursorrules/performance.md +251 -0
- package/templates/csharp-expert/.cursorrules/testing.md +282 -0
- package/templates/csharp-expert/.cursorrules/tooling.md +254 -0
- package/templates/csharp-expert/CLAUDE.md +360 -0
- package/templates/java-expert/.cursorrules/concurrency.md +209 -0
- package/templates/java-expert/.cursorrules/error-handling.md +205 -0
- package/templates/java-expert/.cursorrules/modern-java.md +216 -0
- package/templates/java-expert/.cursorrules/overview.md +81 -0
- package/templates/java-expert/.cursorrules/performance.md +239 -0
- package/templates/java-expert/.cursorrules/persistence.md +262 -0
- package/templates/java-expert/.cursorrules/spring-boot.md +262 -0
- package/templates/java-expert/.cursorrules/testing.md +272 -0
- package/templates/java-expert/.cursorrules/tooling.md +301 -0
- package/templates/java-expert/CLAUDE.md +325 -0
- package/templates/javascript-expert/.cursorrules/overview.md +5 -3
- package/templates/javascript-expert/.cursorrules/typescript-deep-dive.md +348 -0
- package/templates/javascript-expert/CLAUDE.md +34 -3
- package/templates/kotlin-expert/.cursorrules/coroutines.md +237 -0
- package/templates/kotlin-expert/.cursorrules/error-handling.md +149 -0
- package/templates/kotlin-expert/.cursorrules/frameworks.md +227 -0
- package/templates/kotlin-expert/.cursorrules/language-features.md +231 -0
- package/templates/kotlin-expert/.cursorrules/overview.md +77 -0
- package/templates/kotlin-expert/.cursorrules/performance.md +185 -0
- package/templates/kotlin-expert/.cursorrules/testing.md +213 -0
- package/templates/kotlin-expert/.cursorrules/tooling.md +258 -0
- package/templates/kotlin-expert/CLAUDE.md +276 -0
- package/templates/swift-expert/.cursorrules/concurrency.md +230 -0
- package/templates/swift-expert/.cursorrules/error-handling.md +213 -0
- package/templates/swift-expert/.cursorrules/language-features.md +246 -0
- package/templates/swift-expert/.cursorrules/overview.md +88 -0
- package/templates/swift-expert/.cursorrules/performance.md +260 -0
- package/templates/swift-expert/.cursorrules/swiftui.md +260 -0
- package/templates/swift-expert/.cursorrules/testing.md +286 -0
- package/templates/swift-expert/.cursorrules/tooling.md +285 -0
- package/templates/swift-expert/CLAUDE.md +275 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# C# Tooling and Ecosystem
|
|
2
|
+
|
|
3
|
+
The .NET toolchain, project configuration, and CI/CD patterns.
|
|
4
|
+
|
|
5
|
+
## Essential CLI Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Project management
|
|
9
|
+
dotnet new webapi -n MyApp.Api # Create new project
|
|
10
|
+
dotnet new sln -n MyApp # Create solution
|
|
11
|
+
dotnet sln add src/MyApp.Api # Add project to solution
|
|
12
|
+
dotnet add reference ../MyApp.Domain # Add project reference
|
|
13
|
+
dotnet add package Serilog # Add NuGet package
|
|
14
|
+
|
|
15
|
+
# Build and run
|
|
16
|
+
dotnet build --warnaserror # Build with warnings as errors
|
|
17
|
+
dotnet run --project src/MyApp.Api # Run specific project
|
|
18
|
+
dotnet watch --project src/MyApp.Api # Hot reload
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
dotnet test # Run all tests
|
|
22
|
+
dotnet test --filter "Category=Unit" # Filter tests
|
|
23
|
+
dotnet test --collect:"XPlat Code Coverage" # With coverage
|
|
24
|
+
dotnet test --logger "console;verbosity=detailed"
|
|
25
|
+
|
|
26
|
+
# Analysis
|
|
27
|
+
dotnet format # Apply code style
|
|
28
|
+
dotnet format --verify-no-changes # CI check
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Project Configuration
|
|
32
|
+
|
|
33
|
+
### Directory.Build.props
|
|
34
|
+
|
|
35
|
+
Shared settings across all projects in the solution:
|
|
36
|
+
|
|
37
|
+
```xml
|
|
38
|
+
<Project>
|
|
39
|
+
<PropertyGroup>
|
|
40
|
+
<TargetFramework>net9.0</TargetFramework>
|
|
41
|
+
<Nullable>enable</Nullable>
|
|
42
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
43
|
+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
44
|
+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
|
45
|
+
<AnalysisLevel>latest-recommended</AnalysisLevel>
|
|
46
|
+
</PropertyGroup>
|
|
47
|
+
|
|
48
|
+
<!-- Central package management -->
|
|
49
|
+
<PropertyGroup>
|
|
50
|
+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
|
51
|
+
</PropertyGroup>
|
|
52
|
+
</Project>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Directory.Packages.props
|
|
56
|
+
|
|
57
|
+
Central package version management (single source of truth):
|
|
58
|
+
|
|
59
|
+
```xml
|
|
60
|
+
<Project>
|
|
61
|
+
<ItemGroup>
|
|
62
|
+
<!-- Core -->
|
|
63
|
+
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
|
64
|
+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" />
|
|
65
|
+
|
|
66
|
+
<!-- Testing -->
|
|
67
|
+
<PackageVersion Include="xunit" Version="2.9.0" />
|
|
68
|
+
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
|
|
69
|
+
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
|
70
|
+
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.0.0" />
|
|
71
|
+
|
|
72
|
+
<!-- Analysis -->
|
|
73
|
+
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.0.0" />
|
|
74
|
+
<PackageVersion Include="Roslynator.Analyzers" Version="4.12.0" />
|
|
75
|
+
</ItemGroup>
|
|
76
|
+
</Project>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## .editorconfig
|
|
80
|
+
|
|
81
|
+
```ini
|
|
82
|
+
root = true
|
|
83
|
+
|
|
84
|
+
[*.cs]
|
|
85
|
+
# Naming conventions
|
|
86
|
+
dotnet_naming_rule.private_fields_should_be_camel_case.severity = error
|
|
87
|
+
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
|
|
88
|
+
dotnet_naming_rule.private_fields_should_be_camel_case.style = underscore_camel_case
|
|
89
|
+
|
|
90
|
+
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
|
91
|
+
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
|
92
|
+
|
|
93
|
+
dotnet_naming_style.underscore_camel_case.required_prefix = _
|
|
94
|
+
dotnet_naming_style.underscore_camel_case.capitalization = camel_case
|
|
95
|
+
|
|
96
|
+
# Code style
|
|
97
|
+
csharp_style_var_for_built_in_types = false:warning
|
|
98
|
+
csharp_style_var_when_type_is_apparent = true:suggestion
|
|
99
|
+
csharp_style_var_elsewhere = false:suggestion
|
|
100
|
+
|
|
101
|
+
csharp_prefer_simple_using_statement = true:warning
|
|
102
|
+
csharp_style_namespace_declarations = file_scoped:warning
|
|
103
|
+
csharp_style_prefer_primary_constructors = true:suggestion
|
|
104
|
+
|
|
105
|
+
# Formatting
|
|
106
|
+
dotnet_sort_system_directives_first = true
|
|
107
|
+
csharp_new_line_before_open_brace = all
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Analyzers
|
|
111
|
+
|
|
112
|
+
```xml
|
|
113
|
+
<!-- In .csproj or Directory.Build.props -->
|
|
114
|
+
<ItemGroup>
|
|
115
|
+
<!-- Roslyn analyzers -->
|
|
116
|
+
<PackageReference Include="SonarAnalyzer.CSharp" PrivateAssets="all" />
|
|
117
|
+
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" />
|
|
118
|
+
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" />
|
|
119
|
+
|
|
120
|
+
<!-- Security analyzers -->
|
|
121
|
+
<PackageReference Include="SecurityCodeScan.VS2019" PrivateAssets="all" />
|
|
122
|
+
</ItemGroup>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Docker
|
|
126
|
+
|
|
127
|
+
```dockerfile
|
|
128
|
+
# Multi-stage build for minimal image
|
|
129
|
+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
|
130
|
+
WORKDIR /src
|
|
131
|
+
COPY Directory.Build.props Directory.Packages.props ./
|
|
132
|
+
COPY **/*.csproj ./
|
|
133
|
+
RUN dotnet restore
|
|
134
|
+
|
|
135
|
+
COPY . .
|
|
136
|
+
RUN dotnet publish src/MyApp.Api -c Release -o /app --no-restore
|
|
137
|
+
|
|
138
|
+
# Runtime image — minimal, non-root
|
|
139
|
+
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime
|
|
140
|
+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|
141
|
+
USER appuser
|
|
142
|
+
WORKDIR /app
|
|
143
|
+
COPY --from=build /app .
|
|
144
|
+
EXPOSE 8080
|
|
145
|
+
ENV ASPNETCORE_URLS=http://+:8080
|
|
146
|
+
ENTRYPOINT ["dotnet", "MyApp.Api.dll"]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## CI/CD (GitHub Actions)
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
name: CI
|
|
153
|
+
|
|
154
|
+
on:
|
|
155
|
+
push:
|
|
156
|
+
branches: [main]
|
|
157
|
+
pull_request:
|
|
158
|
+
branches: [main]
|
|
159
|
+
|
|
160
|
+
jobs:
|
|
161
|
+
build-and-test:
|
|
162
|
+
runs-on: ubuntu-latest
|
|
163
|
+
|
|
164
|
+
services:
|
|
165
|
+
postgres:
|
|
166
|
+
image: postgres:16-alpine
|
|
167
|
+
env:
|
|
168
|
+
POSTGRES_PASSWORD: test
|
|
169
|
+
POSTGRES_DB: testdb
|
|
170
|
+
ports:
|
|
171
|
+
- 5432:5432
|
|
172
|
+
|
|
173
|
+
steps:
|
|
174
|
+
- uses: actions/checkout@v4
|
|
175
|
+
|
|
176
|
+
- uses: actions/setup-dotnet@v4
|
|
177
|
+
with:
|
|
178
|
+
dotnet-version: '9.0.x'
|
|
179
|
+
|
|
180
|
+
- name: Restore
|
|
181
|
+
run: dotnet restore
|
|
182
|
+
|
|
183
|
+
- name: Build
|
|
184
|
+
run: dotnet build --no-restore --warnaserror
|
|
185
|
+
|
|
186
|
+
- name: Format check
|
|
187
|
+
run: dotnet format --verify-no-changes --no-restore
|
|
188
|
+
|
|
189
|
+
- name: Test
|
|
190
|
+
run: dotnet test --no-build --collect:"XPlat Code Coverage" --logger trx
|
|
191
|
+
env:
|
|
192
|
+
ConnectionStrings__TestDb: "Host=localhost;Database=testdb;Username=postgres;Password=test"
|
|
193
|
+
|
|
194
|
+
- name: Upload coverage
|
|
195
|
+
uses: codecov/codecov-action@v4
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## NuGet Package Publishing
|
|
199
|
+
|
|
200
|
+
```xml
|
|
201
|
+
<!-- For library projects -->
|
|
202
|
+
<PropertyGroup>
|
|
203
|
+
<PackageId>MyCompany.MyLibrary</PackageId>
|
|
204
|
+
<Version>1.0.0</Version>
|
|
205
|
+
<Description>A useful library</Description>
|
|
206
|
+
<Authors>Your Name</Authors>
|
|
207
|
+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
|
208
|
+
<PackageReadmeFile>README.md</PackageReadmeFile>
|
|
209
|
+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
210
|
+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
|
211
|
+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
|
212
|
+
<IncludeSymbols>true</IncludeSymbols>
|
|
213
|
+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
|
214
|
+
</PropertyGroup>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
dotnet pack -c Release
|
|
219
|
+
dotnet nuget push bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key $NUGET_KEY
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Logging with Serilog
|
|
223
|
+
|
|
224
|
+
```csharp
|
|
225
|
+
// Program.cs
|
|
226
|
+
builder.Host.UseSerilog((context, config) =>
|
|
227
|
+
config.ReadFrom.Configuration(context.Configuration)
|
|
228
|
+
.Enrich.FromLogContext()
|
|
229
|
+
.Enrich.WithMachineName()
|
|
230
|
+
.Enrich.WithEnvironmentName()
|
|
231
|
+
.WriteTo.Console(new CompactJsonFormatter())
|
|
232
|
+
.WriteTo.Seq(context.Configuration["Seq:Url"]!));
|
|
233
|
+
|
|
234
|
+
// Structured logging — always use templates, never interpolation
|
|
235
|
+
logger.LogInformation("Processing order {OrderId} for {CustomerId}", order.Id, order.CustomerId);
|
|
236
|
+
// NOT: logger.LogInformation($"Processing order {order.Id}"); // Defeats structured logging
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Anti-Patterns
|
|
240
|
+
|
|
241
|
+
```csharp
|
|
242
|
+
// Never: hardcoded connection strings
|
|
243
|
+
var conn = "Host=localhost;Database=mydb;Password=secret";
|
|
244
|
+
// Use configuration and user-secrets for development
|
|
245
|
+
|
|
246
|
+
// Never: version conflicts across projects
|
|
247
|
+
// Use Directory.Packages.props for central version management
|
|
248
|
+
|
|
249
|
+
// Never: shipping debug builds
|
|
250
|
+
// Always build Release for production: dotnet publish -c Release
|
|
251
|
+
|
|
252
|
+
// Never: skipping dotnet format in CI
|
|
253
|
+
// Code style drift makes reviews harder and diffs noisier
|
|
254
|
+
```
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# C# Expert Development Guide
|
|
2
|
+
|
|
3
|
+
Principal-level guidelines for C# engineering. Deep .NET runtime knowledge, modern language features, and production-grade patterns.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This guide applies to:
|
|
10
|
+
- Web APIs and services (ASP.NET Core, Minimal APIs)
|
|
11
|
+
- Desktop and cross-platform applications (WPF, MAUI, Avalonia)
|
|
12
|
+
- Cloud-native services (Azure Functions, Worker Services)
|
|
13
|
+
- Libraries and NuGet packages
|
|
14
|
+
- Real-time systems (SignalR, gRPC)
|
|
15
|
+
- Background processing (Hosted Services, message consumers)
|
|
16
|
+
|
|
17
|
+
### Core Philosophy
|
|
18
|
+
|
|
19
|
+
C# is a language of deliberate design. Every feature exists for a reason — use the right tool for the job.
|
|
20
|
+
|
|
21
|
+
- **Type safety is your first line of defense.** Nullable reference types enabled, warnings as errors.
|
|
22
|
+
- **Composition over inheritance.** Interfaces, extension methods, and dependency injection — not deep class hierarchies.
|
|
23
|
+
- **Async all the way down.** Never block on async code. Never use `.Result` or `.Wait()` in application code.
|
|
24
|
+
- **The framework does the heavy lifting.** ASP.NET Core's middleware pipeline, DI container, and configuration system are battle-tested — use them.
|
|
25
|
+
- **Measure before you optimize.** BenchmarkDotNet and dotnet-counters before rewriting anything.
|
|
26
|
+
- **If you don't know, say so.** Admitting uncertainty is professional. Guessing at runtime behavior you haven't verified is not.
|
|
27
|
+
|
|
28
|
+
### Key Principles
|
|
29
|
+
|
|
30
|
+
1. **Nullable Reference Types Are Non-Negotiable** — `<Nullable>enable</Nullable>` in every project
|
|
31
|
+
2. **Prefer Records for Data** — Immutable by default, value semantics, concise syntax
|
|
32
|
+
3. **Dependency Injection Is the Architecture** — Constructor injection, interface segregation, composition root
|
|
33
|
+
4. **Errors Are Explicit** — Result patterns for expected failures, exceptions for exceptional conditions
|
|
34
|
+
5. **Tests Describe Behavior** — Not implementation details
|
|
35
|
+
|
|
36
|
+
### Project Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Solution/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── MyApp.Api/ # ASP.NET Core host (thin — wiring only)
|
|
42
|
+
│ │ ├── Program.cs
|
|
43
|
+
│ │ ├── Endpoints/
|
|
44
|
+
│ │ └── Middleware/
|
|
45
|
+
│ ├── MyApp.Application/ # Use cases (no framework deps)
|
|
46
|
+
│ │ ├── Commands/
|
|
47
|
+
│ │ ├── Queries/
|
|
48
|
+
│ │ └── Interfaces/
|
|
49
|
+
│ ├── MyApp.Domain/ # Core domain (zero dependencies)
|
|
50
|
+
│ │ ├── Entities/
|
|
51
|
+
│ │ ├── ValueObjects/
|
|
52
|
+
│ │ └── Events/
|
|
53
|
+
│ └── MyApp.Infrastructure/ # External concerns (DB, HTTP)
|
|
54
|
+
│ ├── Persistence/
|
|
55
|
+
│ └── Services/
|
|
56
|
+
├── tests/
|
|
57
|
+
│ ├── MyApp.UnitTests/
|
|
58
|
+
│ ├── MyApp.IntegrationTests/
|
|
59
|
+
│ └── MyApp.ArchitectureTests/
|
|
60
|
+
├── Directory.Build.props
|
|
61
|
+
├── .editorconfig
|
|
62
|
+
└── MyApp.sln
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Language Features
|
|
68
|
+
|
|
69
|
+
### Nullable Reference Types
|
|
70
|
+
|
|
71
|
+
```csharp
|
|
72
|
+
public async Task<User?> FindByEmailAsync(string email)
|
|
73
|
+
{
|
|
74
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(email);
|
|
75
|
+
return await _repository.FindByEmailAsync(email);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Never disable nullable warnings project-wide
|
|
80
|
+
- `string` means non-null. `string?` means nullable
|
|
81
|
+
- Use `ArgumentNullException.ThrowIfNull()` at public API boundaries
|
|
82
|
+
|
|
83
|
+
### Records
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
// Immutable data with value semantics
|
|
87
|
+
public record CreateUserRequest(string Name, string Email);
|
|
88
|
+
public readonly record struct Coordinate(double Latitude, double Longitude);
|
|
89
|
+
|
|
90
|
+
// Non-destructive mutation
|
|
91
|
+
var updated = original with { Email = "new@example.com" };
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Pattern Matching
|
|
95
|
+
|
|
96
|
+
```csharp
|
|
97
|
+
public static decimal CalculateDiscount(Order order) => order switch
|
|
98
|
+
{
|
|
99
|
+
{ Total: > 1000, Customer.IsPremium: true } => order.Total * 0.15m,
|
|
100
|
+
{ Total: > 500 } => order.Total * 0.10m,
|
|
101
|
+
{ Customer.IsPremium: true } => order.Total * 0.05m,
|
|
102
|
+
_ => 0m
|
|
103
|
+
};
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Spans and Memory
|
|
107
|
+
|
|
108
|
+
```csharp
|
|
109
|
+
public static bool StartsWithDigit(ReadOnlySpan<char> input)
|
|
110
|
+
=> !input.IsEmpty && char.IsDigit(input[0]);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Async Patterns
|
|
116
|
+
|
|
117
|
+
### The Golden Rules
|
|
118
|
+
|
|
119
|
+
1. Async all the way down — never mix sync and async
|
|
120
|
+
2. Never block on async — no `.Result`, `.Wait()`, `.GetAwaiter().GetResult()`
|
|
121
|
+
3. Always pass `CancellationToken`
|
|
122
|
+
4. `ConfigureAwait(false)` in library code only
|
|
123
|
+
5. Return `Task`, not `void` — `async void` is only for event handlers
|
|
124
|
+
|
|
125
|
+
### CancellationToken
|
|
126
|
+
|
|
127
|
+
```csharp
|
|
128
|
+
public async Task<OrderResult> ProcessOrderAsync(
|
|
129
|
+
CreateOrderRequest request, CancellationToken cancellationToken)
|
|
130
|
+
{
|
|
131
|
+
var user = await _userService.GetByIdAsync(request.UserId, cancellationToken);
|
|
132
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
133
|
+
var order = Order.Create(user, request.Items);
|
|
134
|
+
await _repository.SaveAsync(order, cancellationToken);
|
|
135
|
+
return new OrderResult(order.Id);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Concurrent Operations
|
|
140
|
+
|
|
141
|
+
```csharp
|
|
142
|
+
var userTask = _userService.GetByIdAsync(userId, ct);
|
|
143
|
+
var ordersTask = _orderService.GetRecentAsync(userId, ct);
|
|
144
|
+
await Task.WhenAll(userTask, ordersTask);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Channels
|
|
148
|
+
|
|
149
|
+
```csharp
|
|
150
|
+
private readonly Channel<DomainEvent> _channel =
|
|
151
|
+
Channel.CreateBounded<DomainEvent>(new BoundedChannelOptions(1000)
|
|
152
|
+
{
|
|
153
|
+
FullMode = BoundedChannelFullMode.Wait,
|
|
154
|
+
SingleReader = true
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Dependency Injection
|
|
161
|
+
|
|
162
|
+
### Service Lifetimes
|
|
163
|
+
|
|
164
|
+
- **Transient**: New instance every time. Lightweight, stateless services.
|
|
165
|
+
- **Scoped**: One per request. DbContext, Unit of Work.
|
|
166
|
+
- **Singleton**: One for app lifetime. Caches, config, HTTP clients.
|
|
167
|
+
|
|
168
|
+
### The Captive Dependency Problem
|
|
169
|
+
|
|
170
|
+
A singleton must NEVER depend on a scoped or transient service.
|
|
171
|
+
|
|
172
|
+
```csharp
|
|
173
|
+
// WRONG: singleton captures scoped DbContext
|
|
174
|
+
// RIGHT: use IServiceScopeFactory to create scopes on demand
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Options Pattern
|
|
178
|
+
|
|
179
|
+
```csharp
|
|
180
|
+
builder.Services
|
|
181
|
+
.AddOptions<SmtpOptions>()
|
|
182
|
+
.BindConfiguration(SmtpOptions.SectionName)
|
|
183
|
+
.ValidateDataAnnotations()
|
|
184
|
+
.ValidateOnStart();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Error Handling
|
|
190
|
+
|
|
191
|
+
### Two Categories
|
|
192
|
+
|
|
193
|
+
1. **Exceptions** — Programming errors, infrastructure failures
|
|
194
|
+
2. **Result patterns** — Validation failures, business rule violations
|
|
195
|
+
|
|
196
|
+
### Guard Clauses
|
|
197
|
+
|
|
198
|
+
```csharp
|
|
199
|
+
ArgumentNullException.ThrowIfNull(request);
|
|
200
|
+
ArgumentException.ThrowIfNullOrWhiteSpace(request.Email);
|
|
201
|
+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(request.Quantity);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Result Pattern
|
|
205
|
+
|
|
206
|
+
```csharp
|
|
207
|
+
var result = await service.RegisterAsync(request, ct);
|
|
208
|
+
return result.Match(
|
|
209
|
+
user => Results.Created($"/users/{user.Id}", user),
|
|
210
|
+
error => error.Code switch
|
|
211
|
+
{
|
|
212
|
+
"VALIDATION" => Results.BadRequest(error),
|
|
213
|
+
"CONFLICT" => Results.Conflict(error),
|
|
214
|
+
_ => Results.Problem(error.Message)
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Testing
|
|
221
|
+
|
|
222
|
+
### Framework Stack
|
|
223
|
+
|
|
224
|
+
| Tool | Purpose |
|
|
225
|
+
|------|---------|
|
|
226
|
+
| xUnit | Test framework |
|
|
227
|
+
| NSubstitute | Mocking |
|
|
228
|
+
| FluentAssertions | Readable assertions |
|
|
229
|
+
| Bogus | Test data generation |
|
|
230
|
+
| Testcontainers | Real databases in tests |
|
|
231
|
+
| ArchUnitNET | Architecture tests |
|
|
232
|
+
|
|
233
|
+
### Unit Test Structure
|
|
234
|
+
|
|
235
|
+
```csharp
|
|
236
|
+
[Fact]
|
|
237
|
+
public async Task CreateOrder_WithValidItems_ReturnsOrder()
|
|
238
|
+
{
|
|
239
|
+
// Arrange
|
|
240
|
+
var request = new CreateOrderRequest("customer-1", [new("sku-1", 2)]);
|
|
241
|
+
_inventory.CheckAvailabilityAsync("sku-1", 2, Arg.Any<CancellationToken>())
|
|
242
|
+
.Returns(true);
|
|
243
|
+
|
|
244
|
+
// Act
|
|
245
|
+
var result = await _sut.CreateAsync(request, CancellationToken.None);
|
|
246
|
+
|
|
247
|
+
// Assert
|
|
248
|
+
result.IsSuccess.Should().BeTrue();
|
|
249
|
+
result.Value!.CustomerId.Should().Be("customer-1");
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Integration Tests
|
|
254
|
+
|
|
255
|
+
```csharp
|
|
256
|
+
public class OrderEndpointTests : IClassFixture<WebApplicationFactory<Program>>
|
|
257
|
+
{
|
|
258
|
+
[Fact]
|
|
259
|
+
public async Task POST_orders_returns_created_for_valid_request()
|
|
260
|
+
{
|
|
261
|
+
var response = await _client.PostAsJsonAsync("/orders", request);
|
|
262
|
+
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Performance
|
|
270
|
+
|
|
271
|
+
### Profile First
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
dotnet-counters monitor --process-id <pid>
|
|
275
|
+
dotnet-trace collect --process-id <pid>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Key Patterns
|
|
279
|
+
|
|
280
|
+
- `AsNoTracking()` for read-only EF Core queries
|
|
281
|
+
- `ArrayPool<T>.Shared` for buffer reuse
|
|
282
|
+
- `FrozenDictionary` for read-heavy, write-once lookups
|
|
283
|
+
- `ObjectPool<T>` for expensive-to-create objects
|
|
284
|
+
- Compiled EF Core queries for hot paths
|
|
285
|
+
- Project to DTOs in LINQ — don't load full entities
|
|
286
|
+
- Use `struct` for small, immutable value types (< 16 bytes guideline)
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## ASP.NET Core
|
|
291
|
+
|
|
292
|
+
### Minimal APIs
|
|
293
|
+
|
|
294
|
+
```csharp
|
|
295
|
+
var group = app.MapGroup("/orders").RequireAuthorization();
|
|
296
|
+
group.MapGet("/{id:int}", GetOrderById);
|
|
297
|
+
group.MapPost("/", CreateOrder);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Health Checks
|
|
301
|
+
|
|
302
|
+
```csharp
|
|
303
|
+
builder.Services.AddHealthChecks()
|
|
304
|
+
.AddDbContextCheck<AppDbContext>("database")
|
|
305
|
+
.AddRedis(connectionString, "redis");
|
|
306
|
+
|
|
307
|
+
app.MapHealthChecks("/healthz");
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Rate Limiting
|
|
311
|
+
|
|
312
|
+
```csharp
|
|
313
|
+
builder.Services.AddRateLimiter(options =>
|
|
314
|
+
options.AddFixedWindowLimiter("api", config =>
|
|
315
|
+
{
|
|
316
|
+
config.PermitLimit = 100;
|
|
317
|
+
config.Window = TimeSpan.FromMinutes(1);
|
|
318
|
+
}));
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Tooling
|
|
324
|
+
|
|
325
|
+
### Essential Stack
|
|
326
|
+
|
|
327
|
+
| Tool | Purpose |
|
|
328
|
+
|------|---------|
|
|
329
|
+
| dotnet CLI | Build, test, publish |
|
|
330
|
+
| dotnet format | Code style enforcement |
|
|
331
|
+
| Roslyn analyzers | Static analysis |
|
|
332
|
+
| BenchmarkDotNet | Performance benchmarks |
|
|
333
|
+
| Serilog | Structured logging |
|
|
334
|
+
| Central Package Management | Version consistency |
|
|
335
|
+
|
|
336
|
+
### CI Essentials
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
dotnet restore
|
|
340
|
+
dotnet build --warnaserror
|
|
341
|
+
dotnet format --verify-no-changes
|
|
342
|
+
dotnet test --collect:"XPlat Code Coverage"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Definition of Done
|
|
348
|
+
|
|
349
|
+
A C# feature is complete when:
|
|
350
|
+
|
|
351
|
+
- [ ] `dotnet build --warnaserror` passes with zero warnings
|
|
352
|
+
- [ ] `dotnet test` passes with no failures
|
|
353
|
+
- [ ] Nullable reference types produce no warnings
|
|
354
|
+
- [ ] No `#pragma warning disable` without inline justification
|
|
355
|
+
- [ ] Async methods don't block (no `.Result`, `.Wait()`, `.GetAwaiter().GetResult()`)
|
|
356
|
+
- [ ] All public APIs have XML documentation comments
|
|
357
|
+
- [ ] Error paths are tested
|
|
358
|
+
- [ ] DI registrations verified (no missing services at runtime)
|
|
359
|
+
- [ ] No `TODO` without an associated issue
|
|
360
|
+
- [ ] Code reviewed and approved
|