@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.
Files changed (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,489 @@
1
+ ---
2
+ paths:
3
+ - "**/*Middleware.cs"
4
+ - "**/Middleware/**/*.cs"
5
+ ---
6
+
7
+ # .NET Middleware
8
+
9
+ ## Basic Middleware
10
+
11
+ ```csharp
12
+ // Middleware/RequestLoggingMiddleware.cs
13
+ public class RequestLoggingMiddleware
14
+ {
15
+ private readonly RequestDelegate _next;
16
+ private readonly ILogger<RequestLoggingMiddleware> _logger;
17
+
18
+ public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
19
+ {
20
+ _next = next;
21
+ _logger = logger;
22
+ }
23
+
24
+ public async Task InvokeAsync(HttpContext context)
25
+ {
26
+ var stopwatch = Stopwatch.StartNew();
27
+
28
+ try
29
+ {
30
+ await _next(context);
31
+ }
32
+ finally
33
+ {
34
+ stopwatch.Stop();
35
+
36
+ _logger.LogInformation(
37
+ "{Method} {Path} responded {StatusCode} in {ElapsedMs}ms",
38
+ context.Request.Method,
39
+ context.Request.Path,
40
+ context.Response.StatusCode,
41
+ stopwatch.ElapsedMilliseconds);
42
+ }
43
+ }
44
+ }
45
+
46
+ // Extension method
47
+ public static class RequestLoggingMiddlewareExtensions
48
+ {
49
+ public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
50
+ {
51
+ return builder.UseMiddleware<RequestLoggingMiddleware>();
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Correlation ID Middleware
57
+
58
+ ```csharp
59
+ // Middleware/CorrelationIdMiddleware.cs
60
+ public class CorrelationIdMiddleware
61
+ {
62
+ private const string CorrelationIdHeader = "X-Correlation-ID";
63
+ private readonly RequestDelegate _next;
64
+
65
+ public CorrelationIdMiddleware(RequestDelegate next)
66
+ {
67
+ _next = next;
68
+ }
69
+
70
+ public async Task InvokeAsync(HttpContext context)
71
+ {
72
+ var correlationId = GetOrCreateCorrelationId(context);
73
+
74
+ // Add to response headers
75
+ context.Response.OnStarting(() =>
76
+ {
77
+ context.Response.Headers.TryAdd(CorrelationIdHeader, correlationId);
78
+ return Task.CompletedTask;
79
+ });
80
+
81
+ // Store in Items for access throughout request
82
+ context.Items["CorrelationId"] = correlationId;
83
+
84
+ // Add to logging scope
85
+ using (_logger.BeginScope(new Dictionary<string, object>
86
+ {
87
+ ["CorrelationId"] = correlationId
88
+ }))
89
+ {
90
+ await _next(context);
91
+ }
92
+ }
93
+
94
+ private static string GetOrCreateCorrelationId(HttpContext context)
95
+ {
96
+ if (context.Request.Headers.TryGetValue(CorrelationIdHeader, out var existingId))
97
+ {
98
+ return existingId.ToString();
99
+ }
100
+
101
+ return Guid.NewGuid().ToString();
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Exception Handling Middleware
107
+
108
+ ```csharp
109
+ // Middleware/ExceptionHandlingMiddleware.cs
110
+ public class ExceptionHandlingMiddleware
111
+ {
112
+ private readonly RequestDelegate _next;
113
+ private readonly ILogger<ExceptionHandlingMiddleware> _logger;
114
+ private readonly IHostEnvironment _env;
115
+
116
+ public ExceptionHandlingMiddleware(
117
+ RequestDelegate next,
118
+ ILogger<ExceptionHandlingMiddleware> logger,
119
+ IHostEnvironment env)
120
+ {
121
+ _next = next;
122
+ _logger = logger;
123
+ _env = env;
124
+ }
125
+
126
+ public async Task InvokeAsync(HttpContext context)
127
+ {
128
+ try
129
+ {
130
+ await _next(context);
131
+ }
132
+ catch (Exception ex)
133
+ {
134
+ await HandleExceptionAsync(context, ex);
135
+ }
136
+ }
137
+
138
+ private async Task HandleExceptionAsync(HttpContext context, Exception exception)
139
+ {
140
+ var (statusCode, title, detail) = exception switch
141
+ {
142
+ ValidationException ex => (
143
+ StatusCodes.Status400BadRequest,
144
+ "Validation Error",
145
+ string.Join("; ", ex.Errors.Select(e => e.ErrorMessage))),
146
+
147
+ NotFoundException ex => (
148
+ StatusCodes.Status404NotFound,
149
+ "Not Found",
150
+ ex.Message),
151
+
152
+ UnauthorizedAccessException => (
153
+ StatusCodes.Status401Unauthorized,
154
+ "Unauthorized",
155
+ "Authentication required"),
156
+
157
+ ForbiddenException => (
158
+ StatusCodes.Status403Forbidden,
159
+ "Forbidden",
160
+ "Insufficient permissions"),
161
+
162
+ ConflictException ex => (
163
+ StatusCodes.Status409Conflict,
164
+ "Conflict",
165
+ ex.Message),
166
+
167
+ _ => (
168
+ StatusCodes.Status500InternalServerError,
169
+ "Internal Server Error",
170
+ _env.IsDevelopment() ? exception.Message : "An error occurred")
171
+ };
172
+
173
+ _logger.LogError(exception, "Exception occurred: {Message}", exception.Message);
174
+
175
+ context.Response.StatusCode = statusCode;
176
+ context.Response.ContentType = "application/problem+json";
177
+
178
+ var problemDetails = new ProblemDetails
179
+ {
180
+ Status = statusCode,
181
+ Title = title,
182
+ Detail = detail,
183
+ Instance = context.Request.Path
184
+ };
185
+
186
+ if (_env.IsDevelopment())
187
+ {
188
+ problemDetails.Extensions["stackTrace"] = exception.StackTrace;
189
+ }
190
+
191
+ await context.Response.WriteAsJsonAsync(problemDetails);
192
+ }
193
+ }
194
+ ```
195
+
196
+ ## Rate Limiting Middleware
197
+
198
+ ```csharp
199
+ // Middleware/RateLimitingMiddleware.cs
200
+ public class RateLimitingMiddleware
201
+ {
202
+ private readonly RequestDelegate _next;
203
+ private readonly IDistributedCache _cache;
204
+ private readonly RateLimitOptions _options;
205
+
206
+ public RateLimitingMiddleware(
207
+ RequestDelegate next,
208
+ IDistributedCache cache,
209
+ IOptions<RateLimitOptions> options)
210
+ {
211
+ _next = next;
212
+ _cache = cache;
213
+ _options = options.Value;
214
+ }
215
+
216
+ public async Task InvokeAsync(HttpContext context)
217
+ {
218
+ var clientId = GetClientIdentifier(context);
219
+ var key = $"rate-limit:{clientId}";
220
+
221
+ var currentCount = await GetRequestCountAsync(key);
222
+
223
+ if (currentCount >= _options.MaxRequests)
224
+ {
225
+ context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
226
+ context.Response.Headers.RetryAfter = _options.WindowSeconds.ToString();
227
+ await context.Response.WriteAsJsonAsync(new
228
+ {
229
+ error = "Too many requests",
230
+ retryAfter = _options.WindowSeconds
231
+ });
232
+ return;
233
+ }
234
+
235
+ await IncrementRequestCountAsync(key);
236
+
237
+ context.Response.Headers.Append("X-RateLimit-Limit", _options.MaxRequests.ToString());
238
+ context.Response.Headers.Append("X-RateLimit-Remaining", (options.MaxRequests - currentCount - 1).ToString());
239
+
240
+ await _next(context);
241
+ }
242
+
243
+ private string GetClientIdentifier(HttpContext context)
244
+ {
245
+ // Prefer authenticated user ID, fallback to IP
246
+ return context.User.Identity?.IsAuthenticated == true
247
+ ? context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous"
248
+ : context.Connection.RemoteIpAddress?.ToString() ?? "anonymous";
249
+ }
250
+
251
+ private async Task<int> GetRequestCountAsync(string key)
252
+ {
253
+ var value = await _cache.GetStringAsync(key);
254
+ return int.TryParse(value, out var count) ? count : 0;
255
+ }
256
+
257
+ private async Task IncrementRequestCountAsync(string key)
258
+ {
259
+ var count = await GetRequestCountAsync(key) + 1;
260
+ await _cache.SetStringAsync(
261
+ key,
262
+ count.ToString(),
263
+ new DistributedCacheEntryOptions
264
+ {
265
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(_options.WindowSeconds)
266
+ });
267
+ }
268
+ }
269
+
270
+ public class RateLimitOptions
271
+ {
272
+ public int MaxRequests { get; set; } = 100;
273
+ public int WindowSeconds { get; set; } = 60;
274
+ }
275
+ ```
276
+
277
+ ## Security Headers Middleware
278
+
279
+ ```csharp
280
+ // Middleware/SecurityHeadersMiddleware.cs
281
+ public class SecurityHeadersMiddleware
282
+ {
283
+ private readonly RequestDelegate _next;
284
+
285
+ public SecurityHeadersMiddleware(RequestDelegate next)
286
+ {
287
+ _next = next;
288
+ }
289
+
290
+ public async Task InvokeAsync(HttpContext context)
291
+ {
292
+ // Security headers
293
+ context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
294
+ context.Response.Headers.Append("X-Frame-Options", "DENY");
295
+ context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");
296
+ context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
297
+ context.Response.Headers.Append(
298
+ "Content-Security-Policy",
299
+ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';");
300
+ context.Response.Headers.Append(
301
+ "Permissions-Policy",
302
+ "camera=(), microphone=(), geolocation=()");
303
+
304
+ // Remove server header
305
+ context.Response.Headers.Remove("Server");
306
+ context.Response.Headers.Remove("X-Powered-By");
307
+
308
+ await _next(context);
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## Request Timeout Middleware
314
+
315
+ ```csharp
316
+ // Middleware/RequestTimeoutMiddleware.cs
317
+ public class RequestTimeoutMiddleware
318
+ {
319
+ private readonly RequestDelegate _next;
320
+ private readonly TimeSpan _timeout;
321
+
322
+ public RequestTimeoutMiddleware(RequestDelegate next, TimeSpan timeout)
323
+ {
324
+ _next = next;
325
+ _timeout = timeout;
326
+ }
327
+
328
+ public async Task InvokeAsync(HttpContext context)
329
+ {
330
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted);
331
+ cts.CancelAfter(_timeout);
332
+
333
+ context.RequestAborted = cts.Token;
334
+
335
+ try
336
+ {
337
+ await _next(context);
338
+ }
339
+ catch (OperationCanceledException) when (cts.IsCancellationRequested)
340
+ {
341
+ context.Response.StatusCode = StatusCodes.Status408RequestTimeout;
342
+ await context.Response.WriteAsJsonAsync(new { error = "Request timeout" });
343
+ }
344
+ }
345
+ }
346
+ ```
347
+
348
+ ## Tenant Resolution Middleware
349
+
350
+ ```csharp
351
+ // Middleware/TenantMiddleware.cs
352
+ public class TenantMiddleware
353
+ {
354
+ private readonly RequestDelegate _next;
355
+
356
+ public TenantMiddleware(RequestDelegate next)
357
+ {
358
+ _next = next;
359
+ }
360
+
361
+ public async Task InvokeAsync(HttpContext context, ITenantResolver tenantResolver)
362
+ {
363
+ var tenantId = ResolveTenantId(context);
364
+
365
+ if (string.IsNullOrEmpty(tenantId))
366
+ {
367
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
368
+ await context.Response.WriteAsJsonAsync(new { error = "Tenant not specified" });
369
+ return;
370
+ }
371
+
372
+ var tenant = await tenantResolver.ResolveAsync(tenantId);
373
+
374
+ if (tenant == null)
375
+ {
376
+ context.Response.StatusCode = StatusCodes.Status404NotFound;
377
+ await context.Response.WriteAsJsonAsync(new { error = "Tenant not found" });
378
+ return;
379
+ }
380
+
381
+ context.Items["Tenant"] = tenant;
382
+ context.Items["TenantId"] = tenant.Id;
383
+
384
+ await _next(context);
385
+ }
386
+
387
+ private static string? ResolveTenantId(HttpContext context)
388
+ {
389
+ // From header
390
+ if (context.Request.Headers.TryGetValue("X-Tenant-ID", out var headerValue))
391
+ {
392
+ return headerValue.ToString();
393
+ }
394
+
395
+ // From subdomain
396
+ var host = context.Request.Host.Host;
397
+ var subdomain = host.Split('.').FirstOrDefault();
398
+ if (!string.IsNullOrEmpty(subdomain) && subdomain != "www")
399
+ {
400
+ return subdomain;
401
+ }
402
+
403
+ // From claim
404
+ return context.User.FindFirst("tenant_id")?.Value;
405
+ }
406
+ }
407
+ ```
408
+
409
+ ## Middleware Registration Order
410
+
411
+ ```csharp
412
+ // Program.cs
413
+ var app = builder.Build();
414
+
415
+ // 1. Exception handling (first to catch all)
416
+ app.UseMiddleware<ExceptionHandlingMiddleware>();
417
+
418
+ // 2. Security headers
419
+ app.UseMiddleware<SecurityHeadersMiddleware>();
420
+
421
+ // 3. Correlation ID (before logging)
422
+ app.UseMiddleware<CorrelationIdMiddleware>();
423
+
424
+ // 4. Request logging
425
+ app.UseMiddleware<RequestLoggingMiddleware>();
426
+
427
+ // 5. Rate limiting
428
+ app.UseMiddleware<RateLimitingMiddleware>();
429
+
430
+ // 6. Built-in middleware
431
+ app.UseHttpsRedirection();
432
+ app.UseAuthentication();
433
+ app.UseAuthorization();
434
+
435
+ // 7. Tenant resolution (after auth)
436
+ app.UseMiddleware<TenantMiddleware>();
437
+
438
+ // 8. Endpoints
439
+ app.MapControllers();
440
+
441
+ app.Run();
442
+ ```
443
+
444
+ ## Anti-patterns
445
+
446
+ ```csharp
447
+ // BAD: Middleware with scoped dependencies in constructor
448
+ public class BadMiddleware
449
+ {
450
+ private readonly IDbContext _context; // Scoped service!
451
+
452
+ public BadMiddleware(RequestDelegate next, IDbContext context)
453
+ {
454
+ _context = context; // Will use same instance for all requests!
455
+ }
456
+ }
457
+
458
+ // GOOD: Inject scoped services in InvokeAsync
459
+ public async Task InvokeAsync(HttpContext context, IDbContext dbContext)
460
+ {
461
+ // dbContext is resolved per request
462
+ }
463
+
464
+ // BAD: Blocking in middleware
465
+ public async Task InvokeAsync(HttpContext context)
466
+ {
467
+ var result = _service.GetData().Result; // Deadlock risk!
468
+ }
469
+
470
+ // GOOD: Use async/await
471
+ public async Task InvokeAsync(HttpContext context)
472
+ {
473
+ var result = await _service.GetDataAsync();
474
+ }
475
+
476
+ // BAD: Modifying response after body written
477
+ public async Task InvokeAsync(HttpContext context)
478
+ {
479
+ await _next(context);
480
+ context.Response.Headers.Add("X-Custom", "value"); // May fail!
481
+ }
482
+
483
+ // GOOD: Use OnStarting callback
484
+ context.Response.OnStarting(() =>
485
+ {
486
+ context.Response.Headers.Add("X-Custom", "value");
487
+ return Task.CompletedTask;
488
+ });
489
+ ```