@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,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.cs"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# C# Async Patterns
|
|
7
|
+
|
|
8
|
+
## Task vs ValueTask
|
|
9
|
+
|
|
10
|
+
```csharp
|
|
11
|
+
// Use Task for most async operations
|
|
12
|
+
public async Task<User> GetUserAsync(int id)
|
|
13
|
+
|
|
14
|
+
// Use ValueTask when:
|
|
15
|
+
// 1. Method often completes synchronously
|
|
16
|
+
// 2. Hot path with many allocations
|
|
17
|
+
public async ValueTask<User?> GetCachedUserAsync(int id)
|
|
18
|
+
{
|
|
19
|
+
if (_cache.TryGetValue(id, out var user))
|
|
20
|
+
return user; // Synchronous path - no allocation
|
|
21
|
+
|
|
22
|
+
return await _repository.GetByIdAsync(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// BAD - ValueTask misuse (awaiting multiple times)
|
|
26
|
+
var task = GetValueTaskAsync();
|
|
27
|
+
await task;
|
|
28
|
+
await task; // Undefined behavior!
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Parallel Execution
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
// GOOD - parallel independent operations
|
|
35
|
+
var userTask = _userService.GetUserAsync(userId);
|
|
36
|
+
var ordersTask = _orderService.GetOrdersAsync(userId);
|
|
37
|
+
var prefsTask = _prefService.GetPreferencesAsync(userId);
|
|
38
|
+
|
|
39
|
+
await Task.WhenAll(userTask, ordersTask, prefsTask);
|
|
40
|
+
|
|
41
|
+
var user = await userTask;
|
|
42
|
+
var orders = await ordersTask;
|
|
43
|
+
var prefs = await prefsTask;
|
|
44
|
+
|
|
45
|
+
// BAD - sequential when parallel is possible
|
|
46
|
+
var user = await _userService.GetUserAsync(userId);
|
|
47
|
+
var orders = await _orderService.GetOrdersAsync(userId);
|
|
48
|
+
var prefs = await _prefService.GetPreferencesAsync(userId);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Task.WhenAll Error Handling
|
|
52
|
+
|
|
53
|
+
```csharp
|
|
54
|
+
// Handle all exceptions from parallel tasks
|
|
55
|
+
try
|
|
56
|
+
{
|
|
57
|
+
await Task.WhenAll(task1, task2, task3);
|
|
58
|
+
}
|
|
59
|
+
catch (Exception)
|
|
60
|
+
{
|
|
61
|
+
// Only first exception is thrown
|
|
62
|
+
// Check individual tasks for all errors
|
|
63
|
+
var exceptions = new[] { task1, task2, task3 }
|
|
64
|
+
.Where(t => t.IsFaulted)
|
|
65
|
+
.SelectMany(t => t.Exception!.InnerExceptions);
|
|
66
|
+
|
|
67
|
+
foreach (var ex in exceptions)
|
|
68
|
+
{
|
|
69
|
+
_logger.LogError(ex, "Task failed");
|
|
70
|
+
}
|
|
71
|
+
throw;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Cancellation
|
|
76
|
+
|
|
77
|
+
```csharp
|
|
78
|
+
// GOOD - check cancellation in loops
|
|
79
|
+
public async Task ProcessBatchAsync(
|
|
80
|
+
IEnumerable<Item> items,
|
|
81
|
+
CancellationToken cancellationToken)
|
|
82
|
+
{
|
|
83
|
+
foreach (var item in items)
|
|
84
|
+
{
|
|
85
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
86
|
+
await ProcessItemAsync(item, cancellationToken);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// GOOD - cancellation with timeout
|
|
91
|
+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
|
92
|
+
try
|
|
93
|
+
{
|
|
94
|
+
await LongOperationAsync(cts.Token);
|
|
95
|
+
}
|
|
96
|
+
catch (OperationCanceledException)
|
|
97
|
+
{
|
|
98
|
+
_logger.LogWarning("Operation timed out");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Link multiple cancellation tokens
|
|
102
|
+
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
|
103
|
+
requestToken,
|
|
104
|
+
applicationStoppingToken);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Async Streams
|
|
108
|
+
|
|
109
|
+
```csharp
|
|
110
|
+
// GOOD - IAsyncEnumerable for streaming data
|
|
111
|
+
public async IAsyncEnumerable<User> GetUsersStreamAsync(
|
|
112
|
+
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
113
|
+
{
|
|
114
|
+
await foreach (var user in _context.Users.AsAsyncEnumerable()
|
|
115
|
+
.WithCancellation(cancellationToken))
|
|
116
|
+
{
|
|
117
|
+
yield return user;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Consuming async stream
|
|
122
|
+
await foreach (var user in GetUsersStreamAsync(cancellationToken))
|
|
123
|
+
{
|
|
124
|
+
await ProcessUserAsync(user);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Avoid Async Pitfalls
|
|
129
|
+
|
|
130
|
+
```csharp
|
|
131
|
+
// BAD - async void (except event handlers)
|
|
132
|
+
public async void ProcessAsync() // Exceptions lost, can't await
|
|
133
|
+
{
|
|
134
|
+
await Task.Delay(1000);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// GOOD - return Task
|
|
138
|
+
public async Task ProcessAsync()
|
|
139
|
+
{
|
|
140
|
+
await Task.Delay(1000);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// BAD - blocking on async (.Result, .Wait())
|
|
144
|
+
public void Process()
|
|
145
|
+
{
|
|
146
|
+
var result = GetDataAsync().Result; // Deadlock risk!
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// BAD - unnecessary async/await
|
|
150
|
+
public async Task<int> GetCountAsync()
|
|
151
|
+
{
|
|
152
|
+
return await _repository.CountAsync(); // Just return the task
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// GOOD - return task directly when no additional await needed
|
|
156
|
+
public Task<int> GetCountAsync()
|
|
157
|
+
{
|
|
158
|
+
return _repository.CountAsync();
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Thread Safety
|
|
163
|
+
|
|
164
|
+
```csharp
|
|
165
|
+
// GOOD - use concurrent collections
|
|
166
|
+
private readonly ConcurrentDictionary<int, User> _cache = new();
|
|
167
|
+
|
|
168
|
+
// GOOD - SemaphoreSlim for async locking
|
|
169
|
+
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
|
170
|
+
|
|
171
|
+
public async Task<User> GetOrCreateUserAsync(int id)
|
|
172
|
+
{
|
|
173
|
+
await _semaphore.WaitAsync();
|
|
174
|
+
try
|
|
175
|
+
{
|
|
176
|
+
if (!_cache.TryGetValue(id, out var user))
|
|
177
|
+
{
|
|
178
|
+
user = await _repository.GetByIdAsync(id);
|
|
179
|
+
_cache[id] = user;
|
|
180
|
+
}
|
|
181
|
+
return user;
|
|
182
|
+
}
|
|
183
|
+
finally
|
|
184
|
+
{
|
|
185
|
+
_semaphore.Release();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// BAD - lock with async (will not compile correctly)
|
|
190
|
+
lock (_syncObject)
|
|
191
|
+
{
|
|
192
|
+
await SomeAsyncOperation(); // Can't await inside lock
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Channel for Producer/Consumer
|
|
197
|
+
|
|
198
|
+
```csharp
|
|
199
|
+
public class BackgroundProcessor
|
|
200
|
+
{
|
|
201
|
+
private readonly Channel<WorkItem> _channel =
|
|
202
|
+
Channel.CreateBounded<WorkItem>(new BoundedChannelOptions(100)
|
|
203
|
+
{
|
|
204
|
+
FullMode = BoundedChannelFullMode.Wait
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
public async ValueTask QueueWorkAsync(WorkItem item)
|
|
208
|
+
{
|
|
209
|
+
await _channel.Writer.WriteAsync(item);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public async Task ProcessAsync(CancellationToken stoppingToken)
|
|
213
|
+
{
|
|
214
|
+
await foreach (var item in _channel.Reader.ReadAllAsync(stoppingToken))
|
|
215
|
+
{
|
|
216
|
+
await ProcessItemAsync(item);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.cs"
|
|
4
|
+
- "**/*.csproj"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# C# Code Style Rules
|
|
8
|
+
|
|
9
|
+
## Nullability
|
|
10
|
+
|
|
11
|
+
Enable nullable reference types in all projects:
|
|
12
|
+
|
|
13
|
+
```xml
|
|
14
|
+
<PropertyGroup>
|
|
15
|
+
<Nullable>enable</Nullable>
|
|
16
|
+
</PropertyGroup>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```csharp
|
|
20
|
+
// GOOD - explicit nullability
|
|
21
|
+
public string Name { get; set; } = string.Empty;
|
|
22
|
+
public string? MiddleName { get; set; }
|
|
23
|
+
|
|
24
|
+
// BAD - ambiguous
|
|
25
|
+
public string Name { get; set; } // Warning: non-nullable not initialized
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Naming Conventions
|
|
29
|
+
|
|
30
|
+
| Element | Convention | Example |
|
|
31
|
+
|---------|------------|---------|
|
|
32
|
+
| Classes | PascalCase | `UserService` |
|
|
33
|
+
| Interfaces | IPascalCase | `IUserRepository` |
|
|
34
|
+
| Methods | PascalCase | `GetUserById` |
|
|
35
|
+
| Properties | PascalCase | `FirstName` |
|
|
36
|
+
| Private fields | _camelCase | `_userRepository` |
|
|
37
|
+
| Local variables | camelCase | `currentUser` |
|
|
38
|
+
| Constants | PascalCase | `MaxRetryCount` |
|
|
39
|
+
| Async methods | Suffix `Async` | `GetUserAsync` |
|
|
40
|
+
|
|
41
|
+
```csharp
|
|
42
|
+
// GOOD
|
|
43
|
+
private readonly IUserRepository _userRepository;
|
|
44
|
+
public async Task<User?> GetUserByIdAsync(int id) { }
|
|
45
|
+
|
|
46
|
+
// BAD
|
|
47
|
+
private IUserRepository userRepository; // Missing underscore
|
|
48
|
+
public async Task<User?> GetUserById(int id) { } // Missing Async suffix
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Type Guidelines
|
|
52
|
+
|
|
53
|
+
### Use `var` When Type Is Obvious
|
|
54
|
+
|
|
55
|
+
```csharp
|
|
56
|
+
// GOOD - type is obvious
|
|
57
|
+
var user = new User();
|
|
58
|
+
var users = await _repository.GetAllAsync();
|
|
59
|
+
var count = items.Count;
|
|
60
|
+
|
|
61
|
+
// GOOD - type not obvious, be explicit
|
|
62
|
+
User user = GetUser();
|
|
63
|
+
IEnumerable<string> names = GetNames();
|
|
64
|
+
|
|
65
|
+
// BAD - redundant
|
|
66
|
+
User user = new User();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Prefer Collection Expressions (C# 12+)
|
|
70
|
+
|
|
71
|
+
```csharp
|
|
72
|
+
// GOOD - collection expressions
|
|
73
|
+
int[] numbers = [1, 2, 3];
|
|
74
|
+
List<string> names = ["Alice", "Bob"];
|
|
75
|
+
Dictionary<string, int> ages = new() { ["Alice"] = 30 };
|
|
76
|
+
|
|
77
|
+
// BAD - verbose
|
|
78
|
+
int[] numbers = new int[] { 1, 2, 3 };
|
|
79
|
+
List<string> names = new List<string> { "Alice", "Bob" };
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Use Pattern Matching
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
// GOOD - pattern matching
|
|
86
|
+
if (user is { IsActive: true, Role: "Admin" })
|
|
87
|
+
{
|
|
88
|
+
// ...
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return status switch
|
|
92
|
+
{
|
|
93
|
+
OrderStatus.Pending => "Awaiting payment",
|
|
94
|
+
OrderStatus.Shipped => "On the way",
|
|
95
|
+
OrderStatus.Delivered => "Completed",
|
|
96
|
+
_ => "Unknown"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// BAD - verbose checks
|
|
100
|
+
if (user != null && user.IsActive && user.Role == "Admin")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Async/Await
|
|
104
|
+
|
|
105
|
+
### Always Use Async All The Way
|
|
106
|
+
|
|
107
|
+
```csharp
|
|
108
|
+
// GOOD - async all the way
|
|
109
|
+
public async Task<User> GetUserAsync(int id)
|
|
110
|
+
{
|
|
111
|
+
return await _repository.GetByIdAsync(id);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// BAD - blocking on async
|
|
115
|
+
public User GetUser(int id)
|
|
116
|
+
{
|
|
117
|
+
return _repository.GetByIdAsync(id).Result; // Deadlock risk!
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// BAD - async void (except event handlers)
|
|
121
|
+
public async void ProcessData() // Can't await, exceptions lost
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### ConfigureAwait in Libraries
|
|
125
|
+
|
|
126
|
+
```csharp
|
|
127
|
+
// In library code (not ASP.NET Core)
|
|
128
|
+
await SomeOperationAsync().ConfigureAwait(false);
|
|
129
|
+
|
|
130
|
+
// In ASP.NET Core controllers - not needed
|
|
131
|
+
await SomeOperationAsync();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Cancellation Tokens
|
|
135
|
+
|
|
136
|
+
```csharp
|
|
137
|
+
// GOOD - propagate cancellation
|
|
138
|
+
public async Task<List<User>> GetUsersAsync(CancellationToken cancellationToken = default)
|
|
139
|
+
{
|
|
140
|
+
return await _context.Users
|
|
141
|
+
.Where(u => u.IsActive)
|
|
142
|
+
.ToListAsync(cancellationToken);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// BAD - ignoring cancellation
|
|
146
|
+
public async Task<List<User>> GetUsersAsync()
|
|
147
|
+
{
|
|
148
|
+
return await _context.Users.ToListAsync();
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## LINQ Best Practices
|
|
153
|
+
|
|
154
|
+
```csharp
|
|
155
|
+
// GOOD - method syntax for complex queries
|
|
156
|
+
var activeAdmins = users
|
|
157
|
+
.Where(u => u.IsActive)
|
|
158
|
+
.Where(u => u.Role == Role.Admin)
|
|
159
|
+
.OrderBy(u => u.Name)
|
|
160
|
+
.Select(u => new UserDto(u.Id, u.Name))
|
|
161
|
+
.ToList();
|
|
162
|
+
|
|
163
|
+
// GOOD - query syntax for joins
|
|
164
|
+
var result = from order in orders
|
|
165
|
+
join customer in customers on order.CustomerId equals customer.Id
|
|
166
|
+
select new { order.Id, customer.Name };
|
|
167
|
+
|
|
168
|
+
// BAD - multiple enumerations
|
|
169
|
+
var users = GetUsers();
|
|
170
|
+
var count = users.Count(); // First enumeration
|
|
171
|
+
var first = users.First(); // Second enumeration
|
|
172
|
+
|
|
173
|
+
// GOOD - materialize once
|
|
174
|
+
var users = GetUsers().ToList();
|
|
175
|
+
var count = users.Count;
|
|
176
|
+
var first = users[0];
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Records and Immutability
|
|
180
|
+
|
|
181
|
+
```csharp
|
|
182
|
+
// GOOD - immutable DTOs with records
|
|
183
|
+
public record UserDto(int Id, string Name, string Email);
|
|
184
|
+
|
|
185
|
+
public record CreateUserCommand(string Name, string Email)
|
|
186
|
+
{
|
|
187
|
+
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// GOOD - with-expressions for modifications
|
|
191
|
+
var updated = user with { Name = "New Name" };
|
|
192
|
+
|
|
193
|
+
// BAD - mutable DTOs
|
|
194
|
+
public class UserDto
|
|
195
|
+
{
|
|
196
|
+
public int Id { get; set; }
|
|
197
|
+
public string Name { get; set; }
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Error Handling
|
|
202
|
+
|
|
203
|
+
### Specific Exceptions
|
|
204
|
+
|
|
205
|
+
```csharp
|
|
206
|
+
// GOOD - specific exceptions
|
|
207
|
+
public User GetUser(int id)
|
|
208
|
+
{
|
|
209
|
+
return _repository.GetById(id)
|
|
210
|
+
?? throw new NotFoundException($"User {id} not found");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// BAD - generic exception
|
|
214
|
+
throw new Exception("User not found");
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Never Swallow Exceptions
|
|
218
|
+
|
|
219
|
+
```csharp
|
|
220
|
+
// BAD - silent failure
|
|
221
|
+
try
|
|
222
|
+
{
|
|
223
|
+
await _service.ProcessAsync();
|
|
224
|
+
}
|
|
225
|
+
catch (Exception)
|
|
226
|
+
{
|
|
227
|
+
// Swallowed
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// GOOD - log and handle
|
|
231
|
+
try
|
|
232
|
+
{
|
|
233
|
+
await _service.ProcessAsync();
|
|
234
|
+
}
|
|
235
|
+
catch (Exception ex)
|
|
236
|
+
{
|
|
237
|
+
_logger.LogError(ex, "Failed to process");
|
|
238
|
+
throw;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Disposables
|
|
243
|
+
|
|
244
|
+
```csharp
|
|
245
|
+
// GOOD - using declaration (C# 8+)
|
|
246
|
+
await using var connection = new SqlConnection(connectionString);
|
|
247
|
+
await connection.OpenAsync();
|
|
248
|
+
|
|
249
|
+
// GOOD - using statement for limited scope
|
|
250
|
+
using (var stream = File.OpenRead(path))
|
|
251
|
+
{
|
|
252
|
+
// ...
|
|
253
|
+
}
|
|
254
|
+
// Stream disposed here
|
|
255
|
+
|
|
256
|
+
// BAD - manual disposal
|
|
257
|
+
var connection = new SqlConnection(connectionString);
|
|
258
|
+
try
|
|
259
|
+
{
|
|
260
|
+
await connection.OpenAsync();
|
|
261
|
+
}
|
|
262
|
+
finally
|
|
263
|
+
{
|
|
264
|
+
connection.Dispose();
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## File-Scoped Namespaces
|
|
269
|
+
|
|
270
|
+
```csharp
|
|
271
|
+
// GOOD - file-scoped (C# 10+)
|
|
272
|
+
namespace MyApp.Services;
|
|
273
|
+
|
|
274
|
+
public class UserService
|
|
275
|
+
{
|
|
276
|
+
// ...
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// BAD - block-scoped (unnecessary nesting)
|
|
280
|
+
namespace MyApp.Services
|
|
281
|
+
{
|
|
282
|
+
public class UserService
|
|
283
|
+
{
|
|
284
|
+
// ...
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Primary Constructors (C# 12+)
|
|
290
|
+
|
|
291
|
+
```csharp
|
|
292
|
+
// GOOD - primary constructor
|
|
293
|
+
public class UserService(IUserRepository repository, ILogger<UserService> logger)
|
|
294
|
+
{
|
|
295
|
+
public async Task<User?> GetUserAsync(int id)
|
|
296
|
+
{
|
|
297
|
+
logger.LogInformation("Getting user {Id}", id);
|
|
298
|
+
return await repository.GetByIdAsync(id);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Also valid - traditional constructor with readonly fields
|
|
303
|
+
public class UserService
|
|
304
|
+
{
|
|
305
|
+
private readonly IUserRepository _repository;
|
|
306
|
+
private readonly ILogger<UserService> _logger;
|
|
307
|
+
|
|
308
|
+
public UserService(IUserRepository repository, ILogger<UserService> logger)
|
|
309
|
+
{
|
|
310
|
+
_repository = repository;
|
|
311
|
+
_logger = logger;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|