@malamute/ai-rules 1.0.0 → 1.2.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 +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/appsettings*.json"
|
|
4
|
+
- "**/Options/**/*.cs"
|
|
5
|
+
- "**/*Options.cs"
|
|
6
|
+
- "**/*Settings.cs"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# .NET Configuration
|
|
10
|
+
|
|
11
|
+
## Options Pattern
|
|
12
|
+
|
|
13
|
+
```csharp
|
|
14
|
+
// Options/DatabaseOptions.cs
|
|
15
|
+
public class DatabaseOptions
|
|
16
|
+
{
|
|
17
|
+
public const string SectionName = "Database";
|
|
18
|
+
|
|
19
|
+
public string ConnectionString { get; set; } = string.Empty;
|
|
20
|
+
public int CommandTimeout { get; set; } = 30;
|
|
21
|
+
public int MaxRetryCount { get; set; } = 3;
|
|
22
|
+
public bool EnableSensitiveDataLogging { get; set; } = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Options/JwtOptions.cs
|
|
26
|
+
public class JwtOptions
|
|
27
|
+
{
|
|
28
|
+
public const string SectionName = "Jwt";
|
|
29
|
+
|
|
30
|
+
public string Secret { get; set; } = string.Empty;
|
|
31
|
+
public string Issuer { get; set; } = string.Empty;
|
|
32
|
+
public string Audience { get; set; } = string.Empty;
|
|
33
|
+
public int ExpirationMinutes { get; set; } = 60;
|
|
34
|
+
public int RefreshExpirationDays { get; set; } = 7;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Options/CacheOptions.cs
|
|
38
|
+
public class CacheOptions
|
|
39
|
+
{
|
|
40
|
+
public const string SectionName = "Cache";
|
|
41
|
+
|
|
42
|
+
public string RedisConnectionString { get; set; } = string.Empty;
|
|
43
|
+
public int DefaultExpirationMinutes { get; set; } = 5;
|
|
44
|
+
public string InstanceName { get; set; } = "app";
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Options Validation
|
|
49
|
+
|
|
50
|
+
```csharp
|
|
51
|
+
// Options/DatabaseOptionsValidator.cs
|
|
52
|
+
using FluentValidation;
|
|
53
|
+
|
|
54
|
+
public class DatabaseOptionsValidator : AbstractValidator<DatabaseOptions>
|
|
55
|
+
{
|
|
56
|
+
public DatabaseOptionsValidator()
|
|
57
|
+
{
|
|
58
|
+
RuleFor(x => x.ConnectionString)
|
|
59
|
+
.NotEmpty().WithMessage("Database connection string is required");
|
|
60
|
+
|
|
61
|
+
RuleFor(x => x.CommandTimeout)
|
|
62
|
+
.InclusiveBetween(1, 300);
|
|
63
|
+
|
|
64
|
+
RuleFor(x => x.MaxRetryCount)
|
|
65
|
+
.InclusiveBetween(0, 10);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Using Data Annotations
|
|
70
|
+
public class JwtOptions
|
|
71
|
+
{
|
|
72
|
+
public const string SectionName = "Jwt";
|
|
73
|
+
|
|
74
|
+
[Required]
|
|
75
|
+
[MinLength(32)]
|
|
76
|
+
public string Secret { get; set; } = string.Empty;
|
|
77
|
+
|
|
78
|
+
[Required]
|
|
79
|
+
[Url]
|
|
80
|
+
public string Issuer { get; set; } = string.Empty;
|
|
81
|
+
|
|
82
|
+
[Required]
|
|
83
|
+
public string Audience { get; set; } = string.Empty;
|
|
84
|
+
|
|
85
|
+
[Range(1, 1440)]
|
|
86
|
+
public int ExpirationMinutes { get; set; } = 60;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Registration
|
|
91
|
+
|
|
92
|
+
```csharp
|
|
93
|
+
// Program.cs
|
|
94
|
+
builder.Services.AddOptions<DatabaseOptions>()
|
|
95
|
+
.Bind(builder.Configuration.GetSection(DatabaseOptions.SectionName))
|
|
96
|
+
.ValidateDataAnnotations()
|
|
97
|
+
.ValidateOnStart();
|
|
98
|
+
|
|
99
|
+
builder.Services.AddOptions<JwtOptions>()
|
|
100
|
+
.Bind(builder.Configuration.GetSection(JwtOptions.SectionName))
|
|
101
|
+
.ValidateDataAnnotations()
|
|
102
|
+
.ValidateOnStart();
|
|
103
|
+
|
|
104
|
+
// With FluentValidation
|
|
105
|
+
builder.Services.AddOptions<DatabaseOptions>()
|
|
106
|
+
.Bind(builder.Configuration.GetSection(DatabaseOptions.SectionName))
|
|
107
|
+
.Validate(options =>
|
|
108
|
+
{
|
|
109
|
+
var validator = new DatabaseOptionsValidator();
|
|
110
|
+
var result = validator.Validate(options);
|
|
111
|
+
return result.IsValid;
|
|
112
|
+
})
|
|
113
|
+
.ValidateOnStart();
|
|
114
|
+
|
|
115
|
+
// Named options
|
|
116
|
+
builder.Services.AddOptions<StorageOptions>("primary")
|
|
117
|
+
.Bind(builder.Configuration.GetSection("Storage:Primary"));
|
|
118
|
+
builder.Services.AddOptions<StorageOptions>("backup")
|
|
119
|
+
.Bind(builder.Configuration.GetSection("Storage:Backup"));
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Consuming Options
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
// Using IOptions<T> (singleton, no reload)
|
|
126
|
+
public class UserService
|
|
127
|
+
{
|
|
128
|
+
private readonly JwtOptions _jwtOptions;
|
|
129
|
+
|
|
130
|
+
public UserService(IOptions<JwtOptions> options)
|
|
131
|
+
{
|
|
132
|
+
_jwtOptions = options.Value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Using IOptionsSnapshot<T> (scoped, reloads on request)
|
|
137
|
+
public class AuthService
|
|
138
|
+
{
|
|
139
|
+
private readonly JwtOptions _jwtOptions;
|
|
140
|
+
|
|
141
|
+
public AuthService(IOptionsSnapshot<JwtOptions> options)
|
|
142
|
+
{
|
|
143
|
+
_jwtOptions = options.Value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Using IOptionsMonitor<T> (singleton, live reload)
|
|
148
|
+
public class CacheService
|
|
149
|
+
{
|
|
150
|
+
private readonly IOptionsMonitor<CacheOptions> _optionsMonitor;
|
|
151
|
+
|
|
152
|
+
public CacheService(IOptionsMonitor<CacheOptions> optionsMonitor)
|
|
153
|
+
{
|
|
154
|
+
_optionsMonitor = optionsMonitor;
|
|
155
|
+
|
|
156
|
+
// React to changes
|
|
157
|
+
_optionsMonitor.OnChange(options =>
|
|
158
|
+
{
|
|
159
|
+
_logger.LogInformation("Cache options changed");
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public void DoWork()
|
|
164
|
+
{
|
|
165
|
+
var options = _optionsMonitor.CurrentValue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Named options
|
|
170
|
+
public class StorageService
|
|
171
|
+
{
|
|
172
|
+
private readonly StorageOptions _primaryOptions;
|
|
173
|
+
private readonly StorageOptions _backupOptions;
|
|
174
|
+
|
|
175
|
+
public StorageService(IOptionsSnapshot<StorageOptions> options)
|
|
176
|
+
{
|
|
177
|
+
_primaryOptions = options.Get("primary");
|
|
178
|
+
_backupOptions = options.Get("backup");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## appsettings.json Structure
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"Database": {
|
|
188
|
+
"ConnectionString": "Host=localhost;Database=app;Username=user;Password=pass",
|
|
189
|
+
"CommandTimeout": 30,
|
|
190
|
+
"MaxRetryCount": 3
|
|
191
|
+
},
|
|
192
|
+
"Jwt": {
|
|
193
|
+
"Secret": "your-256-bit-secret-key-here-at-least-32-chars",
|
|
194
|
+
"Issuer": "https://api.example.com",
|
|
195
|
+
"Audience": "https://app.example.com",
|
|
196
|
+
"ExpirationMinutes": 60
|
|
197
|
+
},
|
|
198
|
+
"Cache": {
|
|
199
|
+
"RedisConnectionString": "localhost:6379",
|
|
200
|
+
"DefaultExpirationMinutes": 5,
|
|
201
|
+
"InstanceName": "myapp"
|
|
202
|
+
},
|
|
203
|
+
"Storage": {
|
|
204
|
+
"Primary": {
|
|
205
|
+
"Provider": "S3",
|
|
206
|
+
"BucketName": "primary-bucket"
|
|
207
|
+
},
|
|
208
|
+
"Backup": {
|
|
209
|
+
"Provider": "Azure",
|
|
210
|
+
"ContainerName": "backup-container"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"Logging": {
|
|
214
|
+
"LogLevel": {
|
|
215
|
+
"Default": "Information",
|
|
216
|
+
"Microsoft.AspNetCore": "Warning"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Environment-Specific Configuration
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
// appsettings.Development.json
|
|
226
|
+
{
|
|
227
|
+
"Database": {
|
|
228
|
+
"ConnectionString": "Host=localhost;Database=app_dev",
|
|
229
|
+
"EnableSensitiveDataLogging": true
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// appsettings.Production.json
|
|
234
|
+
{
|
|
235
|
+
"Database": {
|
|
236
|
+
"CommandTimeout": 60,
|
|
237
|
+
"MaxRetryCount": 5
|
|
238
|
+
},
|
|
239
|
+
"Logging": {
|
|
240
|
+
"LogLevel": {
|
|
241
|
+
"Default": "Warning"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## User Secrets (Development)
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Initialize
|
|
251
|
+
dotnet user-secrets init
|
|
252
|
+
|
|
253
|
+
# Set secrets
|
|
254
|
+
dotnet user-secrets set "Database:ConnectionString" "Host=localhost;Database=app;Password=secret"
|
|
255
|
+
dotnet user-secrets set "Jwt:Secret" "my-super-secret-key-for-development"
|
|
256
|
+
|
|
257
|
+
# List secrets
|
|
258
|
+
dotnet user-secrets list
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Environment Variables
|
|
262
|
+
|
|
263
|
+
```csharp
|
|
264
|
+
// Program.cs
|
|
265
|
+
builder.Configuration
|
|
266
|
+
.AddJsonFile("appsettings.json", optional: false)
|
|
267
|
+
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
|
|
268
|
+
.AddEnvironmentVariables()
|
|
269
|
+
.AddUserSecrets<Program>(optional: true);
|
|
270
|
+
|
|
271
|
+
// Environment variables override JSON
|
|
272
|
+
// Database__ConnectionString -> Database:ConnectionString
|
|
273
|
+
// Use double underscore for nested keys
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Configuration Providers
|
|
277
|
+
|
|
278
|
+
```csharp
|
|
279
|
+
// Azure Key Vault
|
|
280
|
+
builder.Configuration.AddAzureKeyVault(
|
|
281
|
+
new Uri($"https://{vaultName}.vault.azure.net/"),
|
|
282
|
+
new DefaultAzureCredential());
|
|
283
|
+
|
|
284
|
+
// AWS Secrets Manager
|
|
285
|
+
builder.Configuration.AddSecretsManager(configurator: options =>
|
|
286
|
+
{
|
|
287
|
+
options.SecretFilter = entry => entry.Name.StartsWith("myapp/");
|
|
288
|
+
options.KeyGenerator = (entry, key) => key.Replace("myapp/", "").Replace("/", ":");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// HashiCorp Vault
|
|
292
|
+
builder.Configuration.AddVault(options =>
|
|
293
|
+
{
|
|
294
|
+
options.Address = "https://vault.example.com";
|
|
295
|
+
options.Token = Environment.GetEnvironmentVariable("VAULT_TOKEN");
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Feature Flags
|
|
300
|
+
|
|
301
|
+
```csharp
|
|
302
|
+
// Options/FeatureFlags.cs
|
|
303
|
+
public class FeatureFlags
|
|
304
|
+
{
|
|
305
|
+
public const string SectionName = "Features";
|
|
306
|
+
|
|
307
|
+
public bool EnableNewDashboard { get; set; }
|
|
308
|
+
public bool EnableBetaFeatures { get; set; }
|
|
309
|
+
public bool EnableAnalytics { get; set; }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Usage
|
|
313
|
+
public class DashboardController
|
|
314
|
+
{
|
|
315
|
+
private readonly FeatureFlags _features;
|
|
316
|
+
|
|
317
|
+
public DashboardController(IOptions<FeatureFlags> options)
|
|
318
|
+
{
|
|
319
|
+
_features = options.Value;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
[HttpGet]
|
|
323
|
+
public IActionResult Get()
|
|
324
|
+
{
|
|
325
|
+
if (_features.EnableNewDashboard)
|
|
326
|
+
{
|
|
327
|
+
return View("NewDashboard");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return View("Dashboard");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Configuration Extension Methods
|
|
336
|
+
|
|
337
|
+
```csharp
|
|
338
|
+
// Extensions/ConfigurationExtensions.cs
|
|
339
|
+
public static class ConfigurationExtensions
|
|
340
|
+
{
|
|
341
|
+
public static T GetRequiredOptions<T>(this IConfiguration configuration, string sectionName)
|
|
342
|
+
where T : new()
|
|
343
|
+
{
|
|
344
|
+
var options = new T();
|
|
345
|
+
var section = configuration.GetSection(sectionName);
|
|
346
|
+
|
|
347
|
+
if (!section.Exists())
|
|
348
|
+
{
|
|
349
|
+
throw new InvalidOperationException($"Configuration section '{sectionName}' not found");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
section.Bind(options);
|
|
353
|
+
return options;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Extensions/ServiceCollectionExtensions.cs
|
|
358
|
+
public static class ServiceCollectionExtensions
|
|
359
|
+
{
|
|
360
|
+
public static IServiceCollection AddAppOptions(
|
|
361
|
+
this IServiceCollection services,
|
|
362
|
+
IConfiguration configuration)
|
|
363
|
+
{
|
|
364
|
+
services.AddOptions<DatabaseOptions>()
|
|
365
|
+
.Bind(configuration.GetSection(DatabaseOptions.SectionName))
|
|
366
|
+
.ValidateDataAnnotations()
|
|
367
|
+
.ValidateOnStart();
|
|
368
|
+
|
|
369
|
+
services.AddOptions<JwtOptions>()
|
|
370
|
+
.Bind(configuration.GetSection(JwtOptions.SectionName))
|
|
371
|
+
.ValidateDataAnnotations()
|
|
372
|
+
.ValidateOnStart();
|
|
373
|
+
|
|
374
|
+
services.AddOptions<CacheOptions>()
|
|
375
|
+
.Bind(configuration.GetSection(CacheOptions.SectionName))
|
|
376
|
+
.ValidateDataAnnotations()
|
|
377
|
+
.ValidateOnStart();
|
|
378
|
+
|
|
379
|
+
return services;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Program.cs
|
|
384
|
+
builder.Services.AddAppOptions(builder.Configuration);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Anti-patterns
|
|
388
|
+
|
|
389
|
+
```csharp
|
|
390
|
+
// BAD: Accessing configuration directly
|
|
391
|
+
public class UserService
|
|
392
|
+
{
|
|
393
|
+
public UserService(IConfiguration configuration)
|
|
394
|
+
{
|
|
395
|
+
var connectionString = configuration["Database:ConnectionString"]; // No type safety!
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// GOOD: Use Options pattern
|
|
400
|
+
public UserService(IOptions<DatabaseOptions> options)
|
|
401
|
+
{
|
|
402
|
+
var connectionString = options.Value.ConnectionString;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// BAD: Hardcoding secrets
|
|
406
|
+
public class JwtService
|
|
407
|
+
{
|
|
408
|
+
private readonly string _secret = "hardcoded-secret"; // Security risk!
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// GOOD: Use configuration/secrets
|
|
412
|
+
public JwtService(IOptions<JwtOptions> options)
|
|
413
|
+
{
|
|
414
|
+
_secret = options.Value.Secret;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// BAD: Not validating options
|
|
418
|
+
builder.Services.Configure<DatabaseOptions>(config.GetSection("Database"));
|
|
419
|
+
// Missing validation - could fail at runtime
|
|
420
|
+
|
|
421
|
+
// GOOD: Validate on start
|
|
422
|
+
builder.Services.AddOptions<DatabaseOptions>()
|
|
423
|
+
.Bind(config.GetSection("Database"))
|
|
424
|
+
.ValidateDataAnnotations()
|
|
425
|
+
.ValidateOnStart(); // Fails fast if invalid
|
|
426
|
+
```
|