ai-developer-skill-os 8.1.7 → 8.1.9

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 (41) hide show
  1. package/.agents/README.md +3 -3
  2. package/.agents/skills/qk-code-review/SKILL.md +189 -0
  3. package/.agents/skills/qk-code-review/references/ai/ai-anti-patterns.md +28 -0
  4. package/.agents/skills/qk-code-review/references/ai/v8-schema-validation.md +65 -0
  5. package/.agents/skills/qk-code-review/references/cross-cutting/architecture-review-guide.md +212 -0
  6. package/.agents/skills/qk-code-review/references/cross-cutting/async-concurrency-patterns.md +515 -0
  7. package/.agents/skills/qk-code-review/references/cross-cutting/code-quality-universal.md +358 -0
  8. package/.agents/skills/qk-code-review/references/cross-cutting/code-review-best-practices.md +136 -0
  9. package/.agents/skills/qk-code-review/references/cross-cutting/common-bugs-checklist.md +124 -0
  10. package/.agents/skills/qk-code-review/references/cross-cutting/error-handling-principles.md +492 -0
  11. package/.agents/skills/qk-code-review/references/cross-cutting/n-plus-one-queries.md +309 -0
  12. package/.agents/skills/qk-code-review/references/cross-cutting/performance-review-guide.md +387 -0
  13. package/.agents/skills/qk-code-review/references/cross-cutting/security-review-guide.md +318 -0
  14. package/.agents/skills/qk-code-review/references/cross-cutting/sql-injection-prevention.md +308 -0
  15. package/.agents/skills/qk-code-review/references/cross-cutting/xss-prevention.md +264 -0
  16. package/.agents/skills/qk-code-review/references/languages/angular.md +768 -0
  17. package/.agents/skills/qk-code-review/references/languages/c.md +890 -0
  18. package/.agents/skills/qk-code-review/references/languages/cpp.md +893 -0
  19. package/.agents/skills/qk-code-review/references/languages/csharp.md +519 -0
  20. package/.agents/skills/qk-code-review/references/languages/css-less-sass.md +661 -0
  21. package/.agents/skills/qk-code-review/references/languages/django.md +985 -0
  22. package/.agents/skills/qk-code-review/references/languages/fastapi.md +580 -0
  23. package/.agents/skills/qk-code-review/references/languages/go.md +993 -0
  24. package/.agents/skills/qk-code-review/references/languages/java.md +409 -0
  25. package/.agents/skills/qk-code-review/references/languages/java8.md +586 -0
  26. package/.agents/skills/qk-code-review/references/languages/kotlin.md +1018 -0
  27. package/.agents/skills/qk-code-review/references/languages/nestjs.md +593 -0
  28. package/.agents/skills/qk-code-review/references/languages/php.md +684 -0
  29. package/.agents/skills/qk-code-review/references/languages/python.md +1073 -0
  30. package/.agents/skills/qk-code-review/references/languages/qt.md +757 -0
  31. package/.agents/skills/qk-code-review/references/languages/react.md +871 -0
  32. package/.agents/skills/qk-code-review/references/languages/ruby.md +964 -0
  33. package/.agents/skills/qk-code-review/references/languages/rust.md +846 -0
  34. package/.agents/skills/qk-code-review/references/languages/svelte.md +1064 -0
  35. package/.agents/skills/qk-code-review/references/languages/swift.md +936 -0
  36. package/.agents/skills/qk-code-review/references/languages/typescript.md +1016 -0
  37. package/.agents/skills/qk-code-review/references/languages/vue.md +924 -0
  38. package/.agents/skills/qk-code-review/references/languages/zig.md +440 -0
  39. package/README.md +3 -3
  40. package/bin/install.js +5 -2
  41. package/package.json +1 -1
@@ -0,0 +1,519 @@
1
+ # Hướng dẫn Review Code C# / .NET
2
+
3
+ > Hướng dẫn review code C# / .NET 8, bao quát các tính năng mới của C# 12, lập trình bất đồng bộ (Async), tối ưu hiệu năng EF Core, các best practice cho ASP.NET Core, Dependency Injection và LINQ.
4
+
5
+ ## Mục lục
6
+
7
+ - [Tính năng mới của C# 12](#tính-năng-mới-của-c-12)
8
+ - [Lập trình Bất đồng bộ (Async)](#lập-trình-bất-đồng-bộ)
9
+ - [Hiệu năng EF Core](#hiệu-năng-ef-core)
10
+ - [Best Practice ASP.NET Core](#best-practice-aspnet-core)
11
+ - [Dependency Injection](#dependency-injection)
12
+ - [Best Practice LINQ](#best-practice-linq)
13
+ - [Review Checklist](#review-checklist)
14
+
15
+ ---
16
+
17
+ ## Tính năng mới của C# 12
18
+
19
+ ### Primary Constructors (Cho các class không phải record)
20
+
21
+ ```csharp
22
+ // ❌ Constructor truyền thống với nhiều boilerplate code
23
+ public class ProductService
24
+ {
25
+ private readonly ProductDbContext _db;
26
+ private readonly ILogger<ProductService> _logger;
27
+
28
+ public ProductService(ProductDbContext db, ILogger<ProductService> logger)
29
+ {
30
+ _db = db;
31
+ _logger = logger;
32
+ }
33
+ }
34
+
35
+ // ✅ Primary Constructor —— Dependency Injection cực kỳ ngắn gọn
36
+ public class ProductService(ProductDbContext db, ILogger<ProductService> logger)
37
+ {
38
+ public async Task<Product?> GetAsync(int id)
39
+ => await db.Products.FindAsync(id);
40
+ }
41
+
42
+ // ⚠️ Lưu ý: tham số của primary constructor KHÔNG PHẢI là property, không thể gán lại
43
+ // ⚠️ Nếu cần lưu trữ lâu dài, hãy khai báo field rõ ràng
44
+ public class OrderService(OrderDbContext db)
45
+ {
46
+ private readonly OrderDbContext _db = db; // Bắt tường minh
47
+ }
48
+ ```
49
+
50
+ ### Collection Expressions (Biểu thức tập hợp)
51
+
52
+ ```csharp
53
+ // ❌ Khởi tạo tập hợp kiểu cũ
54
+ int[] nums = new int[] { 1, 2, 3 };
55
+ List<string> names = new List<string> { "alice", "bob" };
56
+
57
+ // ✅ Biểu thức tập hợp (Collection expressions)
58
+ int[] nums = [1, 2, 3];
59
+ List<string> names = ["alice", "bob"];
60
+ Span<char> span = ['a', 'b'];
61
+
62
+ // ✅ Spread operator (Toán tử trải)
63
+ int[] merged = [..nums, 4, 5];
64
+ ```
65
+
66
+ ### Default Lambda Parameters (Tham số mặc định cho Lambda)
67
+
68
+ ```csharp
69
+ // ❌ Overload lambda
70
+ var add = (int a, int b) => a + b;
71
+ var addDefault = (int a) => a + 1;
72
+
73
+ // ✅ Sử dụng tham số mặc định
74
+ var add = (int a, int b = 1) => a + b;
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Lập trình Bất đồng bộ (Async)
80
+
81
+ > 📖 Xem thêm các pattern đồng thời chung tại [Async & Concurrency Patterns](cross-cutting/async-concurrency-patterns.md)
82
+
83
+ ### `Task.Wait()` / `.Result` / `async void` là các Anti-pattern cực kỳ nghiêm trọng
84
+
85
+ ```csharp
86
+ // ❌ Task.Wait() —— Nguy cơ Deadlock (Block luồng đồng bộ bằng thao tác bất đồng bộ)
87
+ public ActionResult<Data> Get(int id)
88
+ {
89
+ var data = _service.GetDataAsync(id).Result; // Deadlock!
90
+ return Ok(data);
91
+ }
92
+
93
+ // ❌ async void —— Không thể catch Exception, làm crash toàn bộ Process
94
+ public async void HandleEvent()
95
+ {
96
+ await _service.ProcessAsync(); // Nếu có lỗi sẽ crash thẳng
97
+ }
98
+
99
+ // ✅ async Task —— Bất đồng bộ toàn chuỗi (Async all the way)
100
+ public async Task<ActionResult<Data>> Get(int id)
101
+ {
102
+ var data = await _service.GetDataAsync(id);
103
+ return Ok(data);
104
+ }
105
+ ```
106
+
107
+ ### `ConfigureAwait(false)` dành riêng cho code thư viện (Library code)
108
+
109
+ ```csharp
110
+ // ❌ Code thư viện giữ lại SynchronizationContext không cần thiết
111
+ public class LibraryService
112
+ {
113
+ public async Task<string> GetDataAsync()
114
+ {
115
+ var response = await _httpClient.GetAsync("/api/data");
116
+ return await response.Content.ReadAsStringAsync();
117
+ }
118
+ }
119
+
120
+ // ✅ Code thư viện dùng ConfigureAwait(false) để tránh deadlock
121
+ public class LibraryService
122
+ {
123
+ public async Task<string> GetDataAsync()
124
+ {
125
+ var response = await _httpClient.GetAsync("/api/data").ConfigureAwait(false);
126
+ return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### Truyền CancellationToken (CancellationToken Propagation)
132
+
133
+ ```csharp
134
+ // ❌ Bỏ qua CancellationToken
135
+ public async Task<List<User>> SearchAsync(string query)
136
+ {
137
+ return await _db.Users.Where(u => u.Name.Contains(query)).ToListAsync();
138
+ }
139
+
140
+ // ✅ Truyền CancellationToken xuyên suốt toàn chuỗi
141
+ public async Task<List<User>> SearchAsync(string query, CancellationToken ct = default)
142
+ {
143
+ return await _db.Users
144
+ .Where(u => u.Name.Contains(query))
145
+ .ToListAsync(ct);
146
+ }
147
+ ```
148
+
149
+ ### Async Disposal (Giải phóng tài nguyên bất đồng bộ)
150
+
151
+ ```csharp
152
+ // ❌ Giải phóng đồng bộ các tài nguyên bất đồng bộ
153
+ public class DataClient : IDisposable
154
+ {
155
+ public void Dispose()
156
+ {
157
+ _httpClient.Dispose(); // Có thể hủy luôn request đang chạy dang dở
158
+ }
159
+ }
160
+
161
+ // ✅ Dùng IAsyncDisposable
162
+ public class DataClient : IAsyncDisposable
163
+ {
164
+ public async ValueTask DisposeAsync()
165
+ {
166
+ await _stream.DisposeAsync();
167
+ }
168
+ }
169
+
170
+ // ✅ Ở phía gọi (caller), dùng await using
171
+ await using var client = new DataClient();
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Hiệu năng EF Core
177
+
178
+ ### Vấn đề N+1 Queries
179
+
180
+ > 📖 Xem thêm lý thuyết chung tại [N+1 Queries Guide](cross-cutting/n-plus-one-queries.md)
181
+
182
+ ```csharp
183
+ // ❌ Lỗi kinh điển N+1 —— Mỗi Blog query một lần để lấy Posts
184
+ foreach (var blog in await context.Blogs.ToListAsync())
185
+ {
186
+ foreach (var post in blog.Posts) // Chọc xuống DB mỗi vòng lặp!
187
+ {
188
+ Console.WriteLine(post.Title);
189
+ }
190
+ }
191
+
192
+ // ✅ Eager Loading + Projection
193
+ await foreach (var blog in context.Blogs
194
+ .Select(b => new { b.Url, b.Posts })
195
+ .AsAsyncEnumerable())
196
+ {
197
+ foreach (var post in blog.Posts)
198
+ Console.WriteLine(post.Title);
199
+ }
200
+ ```
201
+
202
+ ### Fetch thừa dữ liệu (Không dùng Projection)
203
+
204
+ ```csharp
205
+ // ❌ Lấy toàn bộ các cột —— Chỉ cần dùng Url nhưng kéo cả table về
206
+ var urls = await context.Blogs.ToListAsync();
207
+
208
+ // ✅ Chỉ select (chiếu) đúng các cột cần thiết
209
+ var urls = await context.Blogs
210
+ .Select(b => b.Url)
211
+ .ToListAsync();
212
+ ```
213
+
214
+ ### Quên Phân trang (Missing Pagination)
215
+
216
+ ```csharp
217
+ // ❌ Lấy nguyên mảng dữ liệu (Unbounded result set)
218
+ var posts = await context.Posts
219
+ .Where(p => p.Title.StartsWith("A"))
220
+ .ToListAsync(); // Có thể trả về hàng triệu dòng!
221
+
222
+ // ✅ Giới hạn số lượng kết quả
223
+ var posts = await context.Posts
224
+ .Where(p => p.Title.StartsWith("A"))
225
+ .OrderBy(p => p.Id)
226
+ .Skip((page - 1) * pageSize)
227
+ .Take(pageSize)
228
+ .ToListAsync();
229
+ ```
230
+
231
+ ### Cartesian Explosion (Bùng nổ dữ liệu do JOIN chéo)
232
+
233
+ ```csharp
234
+ // ❌ Dùng nhiều Include tạo ra dữ liệu trùng lặp khổng lồ
235
+ var blogs = await context.Blogs
236
+ .Include(b => b.Posts)
237
+ .Include(b => b.Tags)
238
+ .ToListAsync(); // Dữ liệu Blog bị nhân bản ra mỗi dòng
239
+
240
+ // ✅ Sử dụng AsSplitQuery để tách query
241
+ var blogs = await context.Blogs
242
+ .Include(b => b.Posts)
243
+ .Include(b => b.Tags)
244
+ .AsSplitQuery()
245
+ .ToListAsync();
246
+ ```
247
+
248
+ ### Quên dùng AsNoTracking cho câu truy vấn Read-only
249
+
250
+ ```csharp
251
+ // ❌ Theo dõi mặc định —— Kể cả chỉ đọc cũng tốn chi phí Tracking
252
+ var products = await context.Products.ToListAsync();
253
+
254
+ // ✅ AsNoTracking —— Bỏ qua Change Tracking, chạy nhanh hơn và tốn ít RAM hơn
255
+ var products = await context.Products
256
+ .AsNoTracking()
257
+ .ToListAsync();
258
+ ```
259
+
260
+ ### Dùng hàm (Function) trên Cột làm mất tác dụng Index
261
+
262
+ ```csharp
263
+ // ✅ Có thể xài index —— sargable
264
+ var posts1 = await context.Posts
265
+ .Where(p => p.Title.StartsWith("A"))
266
+ .ToListAsync();
267
+
268
+ // ❌ Không dùng được index —— Bắt buộc full table scan
269
+ var posts2 = await context.Posts
270
+ .Where(p => p.Title.EndsWith("A"))
271
+ .ToListAsync();
272
+
273
+ // ❌ Dùng hàm bao lấy cột —— Bắt buộc full table scan
274
+ var posts3 = await context.Posts
275
+ .Where(p => p.Title.ToLower() == "foo")
276
+ .ToListAsync();
277
+ ```
278
+
279
+ ### Truy cập DB Đồng bộ vs Bất đồng bộ
280
+
281
+ ```csharp
282
+ // ❌ Gọi DB đồng bộ —— Block luồng (Thread-blocking)
283
+ var products = context.Products.ToList();
284
+ context.SaveChanges();
285
+
286
+ // ✅ Gọi DB bất đồng bộ
287
+ var products = await context.Products.ToListAsync();
288
+ await context.SaveChangesAsync();
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Best Practice ASP.NET Core
294
+
295
+ ### Sử dụng sai HttpClient
296
+
297
+ ```csharp
298
+ // ❌ Tạo HttpClient mới cho mỗi Request —— Cạn kiệt Socket (Socket exhaustion)
299
+ using var client = new HttpClient();
300
+ var response = await client.GetAsync("https://api.example.com/data");
301
+
302
+ // ✅ Dùng IHttpClientFactory để tiêm (inject)
303
+ public class MyService
304
+ {
305
+ private readonly HttpClient _client;
306
+ public MyService(HttpClient client) => _client = client; // Lấy từ Factory
307
+ }
308
+ ```
309
+
310
+ ### Dùng HttpContext ở Background Thread
311
+
312
+ ```csharp
313
+ // ❌ Bắt lấy Scoped service trong background task —— Lúc chạy thì request đã kết thúc và service bị dispose
314
+ _ = Task.Run(async () =>
315
+ {
316
+ await context.SaveChangesAsync(); // ObjectDisposedException!
317
+ });
318
+
319
+ // ✅ Tạo scope mới hoàn toàn
320
+ _ = Task.Run(async () =>
321
+ {
322
+ await using var scope = serviceScopeFactory.CreateAsyncScope();
323
+ var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
324
+ await db.SaveChangesAsync();
325
+ });
326
+ ```
327
+
328
+ ### Đọc Request.Form đồng bộ
329
+
330
+ ```csharp
331
+ // ❌ Đọc Form đồng bộ —— Lỗi sync-over-async
332
+ var form = HttpContext.Request.Form;
333
+
334
+ // ✅ Đọc Form bất đồng bộ
335
+ var form = await HttpContext.Request.ReadFormAsync();
336
+ ```
337
+
338
+ ### Dùng Exception để điều khiển luồng (Control flow)
339
+
340
+ ```csharp
341
+ // ❌ Dùng exception để kiểm tra dữ liệu có tồn tại hay không —— Rất chậm
342
+ try
343
+ {
344
+ var user = await _db.Users.FirstAsync(u => u.Id == id);
345
+ }
346
+ catch (InvalidOperationException)
347
+ {
348
+ return NotFound();
349
+ }
350
+
351
+ // ✅ Dùng câu lệnh kiểm tra (if/else) thay vì Exception
352
+ var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == id);
353
+ if (user is null) return NotFound();
354
+ ```
355
+
356
+ ### Cài Header sau khi Body đã được ghi
357
+
358
+ ```csharp
359
+ // ❌ Body đã gửi nhưng lại đi set header —— Báo lỗi (Exception)
360
+ await next(context);
361
+ context.Response.Headers["X-Custom"] = "value"; // Sẽ văng lỗi!
362
+
363
+ // ✅ Dùng hàm callback OnStarting
364
+ context.Response.OnStarting(() =>
365
+ {
366
+ context.Response.Headers["X-Custom"] = "value";
367
+ return Task.CompletedTask;
368
+ });
369
+ await next(context);
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Dependency Injection
375
+
376
+ ### Inject Scoped Service vào Singleton
377
+
378
+ ```csharp
379
+ // ❌ Scoped service tiêm vào Singleton —— Lệch vòng đời (Captive dependency)
380
+ services.AddSingleton<BackgroundWorker>();
381
+ services.AddScoped<IUserRepository, UserRepository>();
382
+
383
+ // BackgroundWorker là Singleton, UserRepository là Scoped
384
+ // → UserRepository sẽ bị dùng chung cho mọi request hoặc bị dispose sớm
385
+
386
+ // ✅ Tạo scope từ IServiceProvider ngay bên trong Singleton
387
+ public class BackgroundWorker : BackgroundService
388
+ {
389
+ private readonly IServiceScopeFactory _scopeFactory;
390
+
391
+ public BackgroundWorker(IServiceScopeFactory scopeFactory)
392
+ => _scopeFactory = scopeFactory;
393
+
394
+ protected override async Task ExecuteAsync(CancellationToken ct)
395
+ {
396
+ await using var scope = _scopeFactory.CreateAsyncScope();
397
+ var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
398
+ }
399
+ }
400
+ ```
401
+
402
+ ---
403
+
404
+ ## Best Practice LINQ
405
+
406
+ ### Gọi LINQ sau khi đã ToList()
407
+
408
+ ```csharp
409
+ // ❌ ToList rồi mới filter —— Kéo toàn bộ bảng lên RAM
410
+ var results = context.Posts
411
+ .Where(p => p.Title.StartsWith("A"))
412
+ .ToList()
413
+ .Where(p => SomeClientFilter(p)); // Filter phía Client (bộ nhớ)
414
+
415
+ // ✅ Để cho Database lo khâu filter nhiều nhất có thể
416
+ var results = await context.Posts
417
+ .Where(p => p.Title.StartsWith("A") && SomeDbFilter(p))
418
+ .AsAsyncEnumerable()
419
+ .Where(p => SomeClientFilter(p)) // Chỉ filter trên tập kết quả nhỏ
420
+ .ToListAsync();
421
+ ```
422
+
423
+ ### Count() vs Any()
424
+
425
+ ```csharp
426
+ // ❌ Count() phải đếm toàn bộ dòng thỏa mãn
427
+ if (context.Users.Count() > 0) { /* ... */ }
428
+
429
+ // ✅ Any() hiệu quả hơn —— Ngừng tìm ngay khi thấy bản ghi đầu tiên
430
+ if (await context.Users.AnyAsync()) { /* ... */ }
431
+ ```
432
+
433
+ ### Duyệt (Enumerate) IEnumerable nhiều lần
434
+
435
+ ```csharp
436
+ // ❌ IEnumerable bị duyệt 2 lần
437
+ public void Process(IEnumerable<int> numbers)
438
+ {
439
+ if (numbers.Any()) // Duyệt lần 1
440
+ {
441
+ foreach (var n in numbers) // Duyệt lần 2 (có thể gọi DB lại lần nữa)
442
+ {
443
+ Console.WriteLine(n);
444
+ }
445
+ }
446
+ }
447
+
448
+ // ✅ Nếu cần dùng nhiều lần, hãy vật chất hóa (materialize) nó trước
449
+ public void Process(IEnumerable<int> numbers)
450
+ {
451
+ var list = numbers.ToList(); // Chỉ duyệt 1 lần duy nhất
452
+ if (list.Any())
453
+ {
454
+ foreach (var n in list)
455
+ {
456
+ Console.WriteLine(n);
457
+ }
458
+ }
459
+ }
460
+ ```
461
+
462
+ ### Side-effects (tác dụng phụ) bên trong Select
463
+
464
+ ```csharp
465
+ // ❌ Dùng Select để thực thi side-effect —— Không đoán trước được lúc nào nó chạy
466
+ var results = users.Select(u =>
467
+ {
468
+ _logger.LogInformation($"Processing {u.Name}"); // Side effect!
469
+ return u.Email;
470
+ }).ToList();
471
+
472
+ // ✅ Đưa Side-effect ra khỏi Select, bỏ vào vòng lặp foreach
473
+ foreach (var user in users)
474
+ {
475
+ _logger.LogInformation("Processing {Name}", user.Name);
476
+ }
477
+ var results = users.Select(u => u.Email).ToList();
478
+ ```
479
+
480
+ ---
481
+
482
+ ## Review Checklist (Bảng kiểm nhanh)
483
+
484
+ ### Tính năng mới C# 12
485
+ - [ ] Tham số của Primary constructor không bị gán lại lung tung.
486
+ - [ ] Cú pháp Collection expression (`[1, 2, 3]`) được dùng nhất quán.
487
+
488
+ ### Lập trình Bất đồng bộ
489
+ - [ ] Tuyệt đối KHÔNG có `Task.Wait()`, `.Result`, hay `async void`.
490
+ - [ ] Code thư viện luôn dùng `ConfigureAwait(false)`.
491
+ - [ ] `CancellationToken` được truyền đi xuyên suốt.
492
+ - [ ] Tài nguyên bất đồng bộ được quản lý bởi `IAsyncDisposable` / `await using`.
493
+ - [ ] Không mix lẫn lộn truy cập dữ liệu đồng bộ và bất đồng bộ.
494
+
495
+ ### EF Core
496
+ - [ ] Không có N+1 queries (Truy cập Navigation properties bên trong vòng lặp).
497
+ - [ ] Có dùng `Select()` (Projection) để tránh fetch thừa data.
498
+ - [ ] Lấy danh sách luôn có `Take()`/`Skip()` để Phân trang (Pagination).
499
+ - [ ] Sử dụng `AsSplitQuery()` nếu có quá nhiều `Include()`.
500
+ - [ ] Các query Read-only phải có `AsNoTracking()`.
501
+ - [ ] Cẩn thận với các Hàm thao tác trên Cột làm mất Index (Vd: `ToLower()`).
502
+ - [ ] Mọi cuộc gọi DB phải dùng hàm `Async`.
503
+
504
+ ### ASP.NET Core
505
+ - [ ] Dùng `IHttpClientFactory` thay vì `new HttpClient()`.
506
+ - [ ] Task chạy nền (Background tasks) tự sinh ra Scope mới.
507
+ - [ ] Dùng `ReadFormAsync` thay thế cho `Request.Form`.
508
+ - [ ] Không xài Exception để check null hoặc điều khiển luồng.
509
+ - [ ] Header HTTP được xử lý qua `OnStarting`.
510
+
511
+ ### Dependency Injection
512
+ - [ ] Không chích (Inject) đồ Scoped vào đồ Singleton.
513
+ - [ ] Background tasks luôn gọi CreateScope.
514
+
515
+ ### LINQ
516
+ - [ ] Hạn chế gọi LINQ sau khi đã `ToList()` nếu query chưa tối ưu.
517
+ - [ ] Ưu tiên `Any()` hơn là `Count() > 0`.
518
+ - [ ] `IEnumerable` được lưu ra List nếu cần duyệt qua nhiều lần.
519
+ - [ ] Hàm `Select()` hoàn toàn pure, không có side-effects (như log, lưu DB).