@malamute/ai-rules 1.0.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 (46) hide show
  1. package/README.md +174 -0
  2. package/bin/cli.js +5 -0
  3. package/configs/_shared/.claude/commands/fix-issue.md +38 -0
  4. package/configs/_shared/.claude/commands/generate-tests.md +49 -0
  5. package/configs/_shared/.claude/commands/review-pr.md +77 -0
  6. package/configs/_shared/.claude/rules/accessibility.md +270 -0
  7. package/configs/_shared/.claude/rules/performance.md +226 -0
  8. package/configs/_shared/.claude/rules/security.md +188 -0
  9. package/configs/_shared/.claude/skills/debug/SKILL.md +118 -0
  10. package/configs/_shared/.claude/skills/learning/SKILL.md +224 -0
  11. package/configs/_shared/.claude/skills/review/SKILL.md +86 -0
  12. package/configs/_shared/.claude/skills/spec/SKILL.md +112 -0
  13. package/configs/_shared/CLAUDE.md +174 -0
  14. package/configs/angular/.claude/rules/components.md +257 -0
  15. package/configs/angular/.claude/rules/state.md +250 -0
  16. package/configs/angular/.claude/rules/testing.md +422 -0
  17. package/configs/angular/.claude/settings.json +31 -0
  18. package/configs/angular/CLAUDE.md +251 -0
  19. package/configs/dotnet/.claude/rules/api.md +370 -0
  20. package/configs/dotnet/.claude/rules/architecture.md +199 -0
  21. package/configs/dotnet/.claude/rules/database/efcore.md +408 -0
  22. package/configs/dotnet/.claude/rules/testing.md +389 -0
  23. package/configs/dotnet/.claude/settings.json +9 -0
  24. package/configs/dotnet/CLAUDE.md +319 -0
  25. package/configs/nestjs/.claude/rules/auth.md +321 -0
  26. package/configs/nestjs/.claude/rules/database/prisma.md +305 -0
  27. package/configs/nestjs/.claude/rules/database/typeorm.md +379 -0
  28. package/configs/nestjs/.claude/rules/modules.md +215 -0
  29. package/configs/nestjs/.claude/rules/testing.md +315 -0
  30. package/configs/nestjs/.claude/rules/validation.md +279 -0
  31. package/configs/nestjs/.claude/settings.json +15 -0
  32. package/configs/nestjs/CLAUDE.md +263 -0
  33. package/configs/nextjs/.claude/rules/components.md +211 -0
  34. package/configs/nextjs/.claude/rules/state/redux-toolkit.md +429 -0
  35. package/configs/nextjs/.claude/rules/state/zustand.md +299 -0
  36. package/configs/nextjs/.claude/rules/testing.md +315 -0
  37. package/configs/nextjs/.claude/settings.json +29 -0
  38. package/configs/nextjs/CLAUDE.md +376 -0
  39. package/configs/python/.claude/rules/database/sqlalchemy.md +355 -0
  40. package/configs/python/.claude/rules/fastapi.md +272 -0
  41. package/configs/python/.claude/rules/flask.md +332 -0
  42. package/configs/python/.claude/rules/testing.md +374 -0
  43. package/configs/python/.claude/settings.json +18 -0
  44. package/configs/python/CLAUDE.md +273 -0
  45. package/package.json +41 -0
  46. package/src/install.js +315 -0
@@ -0,0 +1,389 @@
1
+ ---
2
+ paths:
3
+ - "tests/**/*.cs"
4
+ - "**/*.Tests/**/*.cs"
5
+ - "**/*Tests.cs"
6
+ ---
7
+
8
+ # .NET Testing Rules
9
+
10
+ ## Test Project Structure
11
+
12
+ ```
13
+ tests/
14
+ ├── Domain.UnitTests/
15
+ │ └── Entities/
16
+ │ └── UserTests.cs
17
+ ├── Application.UnitTests/
18
+ │ └── Users/
19
+ │ └── Commands/
20
+ │ └── CreateUserCommandHandlerTests.cs
21
+ ├── Infrastructure.IntegrationTests/
22
+ │ └── Repositories/
23
+ │ └── UserRepositoryTests.cs
24
+ └── WebApi.IntegrationTests/
25
+ └── Endpoints/
26
+ └── UsersEndpointsTests.cs
27
+ ```
28
+
29
+ ## Unit Tests (xUnit + NSubstitute + FluentAssertions)
30
+
31
+ ### Naming Convention
32
+
33
+ ```csharp
34
+ // Method_Scenario_ExpectedResult
35
+ public class UserTests
36
+ {
37
+ [Fact]
38
+ public void Create_WithValidEmail_ReturnsUser()
39
+ {
40
+ // Arrange
41
+ var email = "test@example.com";
42
+ var passwordHash = "hashedPassword";
43
+
44
+ // Act
45
+ var user = User.Create(email, passwordHash);
46
+
47
+ // Assert
48
+ user.Should().NotBeNull();
49
+ user.Email.Should().Be(email);
50
+ user.Id.Should().NotBeEmpty();
51
+ }
52
+
53
+ [Fact]
54
+ public void Create_WithEmptyEmail_ThrowsArgumentException()
55
+ {
56
+ // Arrange
57
+ var email = "";
58
+ var passwordHash = "hashedPassword";
59
+
60
+ // Act
61
+ var act = () => User.Create(email, passwordHash);
62
+
63
+ // Assert
64
+ act.Should().Throw<ArgumentException>()
65
+ .WithMessage("*email*");
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Testing Handlers with Mocks
71
+
72
+ ```csharp
73
+ public class CreateUserCommandHandlerTests
74
+ {
75
+ private readonly IUserRepository _userRepository;
76
+ private readonly IPasswordHasher _passwordHasher;
77
+ private readonly CreateUserCommandHandler _sut;
78
+
79
+ public CreateUserCommandHandlerTests()
80
+ {
81
+ _userRepository = Substitute.For<IUserRepository>();
82
+ _passwordHasher = Substitute.For<IPasswordHasher>();
83
+ _sut = new CreateUserCommandHandler(_userRepository, _passwordHasher);
84
+ }
85
+
86
+ [Fact]
87
+ public async Task Handle_ValidCommand_CreatesUserAndReturnsId()
88
+ {
89
+ // Arrange
90
+ var command = new CreateUserCommand("test@example.com", "Password123!");
91
+ _passwordHasher.Hash(command.Password).Returns("hashedPassword");
92
+
93
+ // Act
94
+ var result = await _sut.Handle(command, CancellationToken.None);
95
+
96
+ // Assert
97
+ result.Should().NotBeEmpty();
98
+ await _userRepository.Received(1).AddAsync(
99
+ Arg.Is<User>(u => u.Email == command.Email),
100
+ Arg.Any<CancellationToken>());
101
+ }
102
+
103
+ [Fact]
104
+ public async Task Handle_ExistingEmail_ThrowsValidationException()
105
+ {
106
+ // Arrange
107
+ var command = new CreateUserCommand("existing@example.com", "Password123!");
108
+ _userRepository.ExistsAsync(command.Email, Arg.Any<CancellationToken>())
109
+ .Returns(true);
110
+
111
+ // Act
112
+ var act = () => _sut.Handle(command, CancellationToken.None);
113
+
114
+ // Assert
115
+ await act.Should().ThrowAsync<ValidationException>();
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### Testing Validators
121
+
122
+ ```csharp
123
+ public class CreateUserCommandValidatorTests
124
+ {
125
+ private readonly CreateUserCommandValidator _sut;
126
+ private readonly IUserRepository _userRepository;
127
+
128
+ public CreateUserCommandValidatorTests()
129
+ {
130
+ _userRepository = Substitute.For<IUserRepository>();
131
+ _sut = new CreateUserCommandValidator(_userRepository);
132
+ }
133
+
134
+ [Theory]
135
+ [InlineData("")]
136
+ [InlineData("invalid")]
137
+ [InlineData("missing@")]
138
+ public async Task Validate_InvalidEmail_ReturnsError(string email)
139
+ {
140
+ // Arrange
141
+ var command = new CreateUserCommand(email, "Password123!");
142
+
143
+ // Act
144
+ var result = await _sut.ValidateAsync(command);
145
+
146
+ // Assert
147
+ result.IsValid.Should().BeFalse();
148
+ result.Errors.Should().Contain(e => e.PropertyName == "Email");
149
+ }
150
+
151
+ [Theory]
152
+ [InlineData("short")]
153
+ [InlineData("nouppercase1")]
154
+ [InlineData("NOLOWERCASE1")]
155
+ [InlineData("NoDigitsHere")]
156
+ public async Task Validate_WeakPassword_ReturnsError(string password)
157
+ {
158
+ // Arrange
159
+ var command = new CreateUserCommand("test@example.com", password);
160
+
161
+ // Act
162
+ var result = await _sut.ValidateAsync(command);
163
+
164
+ // Assert
165
+ result.IsValid.Should().BeFalse();
166
+ result.Errors.Should().Contain(e => e.PropertyName == "Password");
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Integration Tests
172
+
173
+ ### Database Tests with Test Containers
174
+
175
+ ```csharp
176
+ public class UserRepositoryTests : IAsyncLifetime
177
+ {
178
+ private readonly MsSqlContainer _sqlContainer = new MsSqlBuilder()
179
+ .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
180
+ .Build();
181
+
182
+ private ApplicationDbContext _context = null!;
183
+ private UserRepository _sut = null!;
184
+
185
+ public async Task InitializeAsync()
186
+ {
187
+ await _sqlContainer.StartAsync();
188
+
189
+ var options = new DbContextOptionsBuilder<ApplicationDbContext>()
190
+ .UseSqlServer(_sqlContainer.GetConnectionString())
191
+ .Options;
192
+
193
+ _context = new ApplicationDbContext(options);
194
+ await _context.Database.MigrateAsync();
195
+
196
+ _sut = new UserRepository(_context);
197
+ }
198
+
199
+ public async Task DisposeAsync()
200
+ {
201
+ await _context.DisposeAsync();
202
+ await _sqlContainer.DisposeAsync();
203
+ }
204
+
205
+ [Fact]
206
+ public async Task AddAsync_ValidUser_PersistsToDatabase()
207
+ {
208
+ // Arrange
209
+ var user = User.Create("test@example.com", "hashedPassword");
210
+
211
+ // Act
212
+ await _sut.AddAsync(user);
213
+
214
+ // Assert
215
+ var savedUser = await _context.Users.FindAsync(user.Id);
216
+ savedUser.Should().NotBeNull();
217
+ savedUser!.Email.Should().Be(user.Email);
218
+ }
219
+ }
220
+ ```
221
+
222
+ ### API Integration Tests with WebApplicationFactory
223
+
224
+ ```csharp
225
+ public class UsersEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
226
+ {
227
+ private readonly HttpClient _client;
228
+ private readonly WebApplicationFactory<Program> _factory;
229
+
230
+ public UsersEndpointsTests(WebApplicationFactory<Program> factory)
231
+ {
232
+ _factory = factory.WithWebHostBuilder(builder =>
233
+ {
234
+ builder.ConfigureServices(services =>
235
+ {
236
+ // Replace real DB with in-memory
237
+ var descriptor = services.SingleOrDefault(
238
+ d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
239
+ if (descriptor != null) services.Remove(descriptor);
240
+
241
+ services.AddDbContext<ApplicationDbContext>(options =>
242
+ options.UseInMemoryDatabase("TestDb"));
243
+ });
244
+ });
245
+
246
+ _client = _factory.CreateClient();
247
+ }
248
+
249
+ [Fact]
250
+ public async Task CreateUser_ValidRequest_Returns201()
251
+ {
252
+ // Arrange
253
+ var request = new { Email = "test@example.com", Password = "Password123!" };
254
+
255
+ // Act
256
+ var response = await _client.PostAsJsonAsync("/api/users", request);
257
+
258
+ // Assert
259
+ response.StatusCode.Should().Be(HttpStatusCode.Created);
260
+ response.Headers.Location.Should().NotBeNull();
261
+ }
262
+
263
+ [Fact]
264
+ public async Task CreateUser_InvalidEmail_Returns400WithProblemDetails()
265
+ {
266
+ // Arrange
267
+ var request = new { Email = "invalid", Password = "Password123!" };
268
+
269
+ // Act
270
+ var response = await _client.PostAsJsonAsync("/api/users", request);
271
+
272
+ // Assert
273
+ response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
274
+ var problemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
275
+ problemDetails!.Errors.Should().ContainKey("Email");
276
+ }
277
+
278
+ [Fact]
279
+ public async Task GetUser_NonExistent_Returns404()
280
+ {
281
+ // Act
282
+ var response = await _client.GetAsync($"/api/users/{Guid.NewGuid()}");
283
+
284
+ // Assert
285
+ response.StatusCode.Should().Be(HttpStatusCode.NotFound);
286
+ }
287
+ }
288
+ ```
289
+
290
+ ### Authenticated Tests
291
+
292
+ ```csharp
293
+ public class AuthenticatedEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
294
+ {
295
+ private readonly HttpClient _client;
296
+
297
+ public AuthenticatedEndpointsTests(WebApplicationFactory<Program> factory)
298
+ {
299
+ _client = factory.WithWebHostBuilder(builder =>
300
+ {
301
+ builder.ConfigureServices(services =>
302
+ {
303
+ // Add test authentication
304
+ services.AddAuthentication("Test")
305
+ .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", null);
306
+ });
307
+ }).CreateClient();
308
+
309
+ _client.DefaultRequestHeaders.Authorization =
310
+ new AuthenticationHeaderValue("Test");
311
+ }
312
+
313
+ [Fact]
314
+ public async Task GetProfile_Authenticated_Returns200()
315
+ {
316
+ var response = await _client.GetAsync("/api/users/me");
317
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
318
+ }
319
+ }
320
+
321
+ // Test auth handler
322
+ public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
323
+ {
324
+ public TestAuthHandler(
325
+ IOptionsMonitor<AuthenticationSchemeOptions> options,
326
+ ILoggerFactory logger,
327
+ UrlEncoder encoder)
328
+ : base(options, logger, encoder) { }
329
+
330
+ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
331
+ {
332
+ var claims = new[] { new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()) };
333
+ var identity = new ClaimsIdentity(claims, "Test");
334
+ var principal = new ClaimsPrincipal(identity);
335
+ var ticket = new AuthenticationTicket(principal, "Test");
336
+ return Task.FromResult(AuthenticateResult.Success(ticket));
337
+ }
338
+ }
339
+ ```
340
+
341
+ ## Test Data Builders
342
+
343
+ ```csharp
344
+ public class UserBuilder
345
+ {
346
+ private Guid _id = Guid.NewGuid();
347
+ private string _email = "default@example.com";
348
+ private string _passwordHash = "hashedPassword";
349
+ private string _role = "User";
350
+
351
+ public UserBuilder WithId(Guid id) { _id = id; return this; }
352
+ public UserBuilder WithEmail(string email) { _email = email; return this; }
353
+ public UserBuilder WithRole(string role) { _role = role; return this; }
354
+ public UserBuilder AsAdmin() => WithRole("Admin");
355
+
356
+ public User Build()
357
+ {
358
+ // Use reflection or internal setters for testing
359
+ return new User
360
+ {
361
+ Id = _id,
362
+ Email = _email,
363
+ PasswordHash = _passwordHash,
364
+ Role = _role
365
+ };
366
+ }
367
+ }
368
+
369
+ // Usage
370
+ var user = new UserBuilder()
371
+ .WithEmail("admin@example.com")
372
+ .AsAdmin()
373
+ .Build();
374
+ ```
375
+
376
+ ## Code Coverage
377
+
378
+ ```bash
379
+ # Run with coverage
380
+ dotnet test --collect:"XPlat Code Coverage"
381
+
382
+ # Generate HTML report (requires reportgenerator tool)
383
+ dotnet tool install -g dotnet-reportgenerator-globaltool
384
+ reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html
385
+ ```
386
+
387
+ ### Coverage Thresholds
388
+
389
+ Target: **80%+ coverage** on Application and Domain layers.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(dotnet *)",
5
+ "Bash(dotnet-ef *)"
6
+ ],
7
+ "deny": []
8
+ }
9
+ }
@@ -0,0 +1,319 @@
1
+ # .NET Project Guidelines
2
+
3
+ @../_shared/CLAUDE.md
4
+
5
+ ## Stack
6
+
7
+ - .NET 8+ (latest LTS)
8
+ - ASP.NET Core Web API
9
+ - Entity Framework Core
10
+ - C# 12+ features
11
+ - xUnit for testing
12
+
13
+ ## Architecture
14
+
15
+ ### Clean Architecture (Recommended)
16
+
17
+ ```
18
+ src/
19
+ ├── Domain/ # Core business logic (no dependencies)
20
+ │ ├── Entities/
21
+ │ │ └── User.cs
22
+ │ ├── ValueObjects/
23
+ │ │ └── Email.cs
24
+ │ ├── Enums/
25
+ │ ├── Exceptions/
26
+ │ │ └── DomainException.cs
27
+ │ └── Interfaces/
28
+ │ └── IUserRepository.cs
29
+
30
+ ├── Application/ # Use cases, CQRS, DTOs
31
+ │ ├── Common/
32
+ │ │ ├── Behaviors/
33
+ │ │ │ ├── ValidationBehavior.cs
34
+ │ │ │ └── LoggingBehavior.cs
35
+ │ │ ├── Interfaces/
36
+ │ │ │ └── IApplicationDbContext.cs
37
+ │ │ └── Mappings/
38
+ │ │ └── MappingProfile.cs
39
+ │ ├── Users/
40
+ │ │ ├── Commands/
41
+ │ │ │ ├── CreateUser/
42
+ │ │ │ │ ├── CreateUserCommand.cs
43
+ │ │ │ │ ├── CreateUserCommandHandler.cs
44
+ │ │ │ │ └── CreateUserCommandValidator.cs
45
+ │ │ │ └── UpdateUser/
46
+ │ │ └── Queries/
47
+ │ │ ├── GetUser/
48
+ │ │ │ ├── GetUserQuery.cs
49
+ │ │ │ ├── GetUserQueryHandler.cs
50
+ │ │ │ └── UserDto.cs
51
+ │ │ └── GetUsers/
52
+ │ └── DependencyInjection.cs
53
+
54
+ ├── Infrastructure/ # External concerns
55
+ │ ├── Data/
56
+ │ │ ├── ApplicationDbContext.cs
57
+ │ │ ├── Configurations/
58
+ │ │ │ └── UserConfiguration.cs
59
+ │ │ └── Migrations/
60
+ │ ├── Repositories/
61
+ │ │ └── UserRepository.cs
62
+ │ ├── Services/
63
+ │ │ └── DateTimeService.cs
64
+ │ └── DependencyInjection.cs
65
+
66
+ └── WebApi/ # Presentation layer
67
+ ├── Controllers/
68
+ │ └── UsersController.cs
69
+ ├── Filters/
70
+ │ └── ApiExceptionFilterAttribute.cs
71
+ ├── Middleware/
72
+ │ └── ExceptionHandlingMiddleware.cs
73
+ ├── Program.cs
74
+ └── appsettings.json
75
+ ```
76
+
77
+ ### Project References
78
+
79
+ ```
80
+ WebApi → Application → Domain
81
+ WebApi → Infrastructure → Application → Domain
82
+ ```
83
+
84
+ **Key rule**: Domain has ZERO external dependencies.
85
+
86
+ ## Code Style
87
+
88
+ ### Naming Conventions
89
+
90
+ | Element | Convention | Example |
91
+ |---------|------------|---------|
92
+ | Classes | PascalCase | `UserService` |
93
+ | Interfaces | IPascalCase | `IUserRepository` |
94
+ | Methods | PascalCase | `GetUserById` |
95
+ | Properties | PascalCase | `FirstName` |
96
+ | Private fields | _camelCase | `_userRepository` |
97
+ | Parameters | camelCase | `userId` |
98
+ | Constants | PascalCase | `MaxRetryCount` |
99
+ | Async methods | Suffix Async | `GetUserAsync` |
100
+
101
+ ### File-Scoped Namespaces
102
+
103
+ ```csharp
104
+ // Good - C# 10+
105
+ namespace MyApp.Domain.Entities;
106
+
107
+ public class User { }
108
+
109
+ // Avoid
110
+ namespace MyApp.Domain.Entities
111
+ {
112
+ public class User { }
113
+ }
114
+ ```
115
+
116
+ ### Primary Constructors (C# 12)
117
+
118
+ ```csharp
119
+ // Good - for simple DI
120
+ public class UserService(IUserRepository userRepository, ILogger<UserService> logger)
121
+ {
122
+ public async Task<User?> GetByIdAsync(Guid id)
123
+ {
124
+ logger.LogInformation("Getting user {UserId}", id);
125
+ return await userRepository.GetByIdAsync(id);
126
+ }
127
+ }
128
+
129
+ // Use traditional constructors when you need field assignment or validation
130
+ ```
131
+
132
+ ### Records for DTOs
133
+
134
+ ```csharp
135
+ // Immutable DTOs
136
+ public record UserDto(Guid Id, string Email, string Name);
137
+
138
+ public record CreateUserRequest(string Email, string Password, string Name);
139
+
140
+ // With validation attributes
141
+ public record CreateUserCommand(
142
+ [Required][EmailAddress] string Email,
143
+ [Required][MinLength(8)] string Password,
144
+ [Required] string Name
145
+ ) : IRequest<Guid>;
146
+ ```
147
+
148
+ ### Nullable Reference Types
149
+
150
+ ```csharp
151
+ // Enable in .csproj
152
+ <Nullable>enable</Nullable>
153
+
154
+ // Be explicit about nullability
155
+ public async Task<User?> GetByIdAsync(Guid id); // Can return null
156
+ public async Task<User> GetByIdOrThrowAsync(Guid id); // Never null
157
+ ```
158
+
159
+ ## Commands
160
+
161
+ ```bash
162
+ # Development
163
+ dotnet run --project src/WebApi
164
+
165
+ # Build
166
+ dotnet build
167
+ dotnet publish -c Release
168
+
169
+ # Tests
170
+ dotnet test
171
+ dotnet test --filter "Category=Unit"
172
+ dotnet test --collect:"XPlat Code Coverage"
173
+
174
+ # EF Core migrations
175
+ dotnet ef migrations add InitialCreate -p src/Infrastructure -s src/WebApi
176
+ dotnet ef database update -p src/Infrastructure -s src/WebApi
177
+
178
+ # Format
179
+ dotnet format
180
+ ```
181
+
182
+ ## Common Patterns
183
+
184
+ ### Minimal API Endpoints
185
+
186
+ ```csharp
187
+ // Program.cs or endpoint extension
188
+ app.MapGet("/users/{id:guid}", async (Guid id, ISender sender) =>
189
+ {
190
+ var user = await sender.Send(new GetUserQuery(id));
191
+ return user is not null ? Results.Ok(user) : Results.NotFound();
192
+ })
193
+ .WithName("GetUser")
194
+ .WithOpenApi()
195
+ .RequireAuthorization();
196
+
197
+ app.MapPost("/users", async (CreateUserCommand command, ISender sender) =>
198
+ {
199
+ var id = await sender.Send(command);
200
+ return Results.CreatedAtRoute("GetUser", new { id }, id);
201
+ })
202
+ .WithName("CreateUser")
203
+ .WithOpenApi();
204
+ ```
205
+
206
+ ### Controller-Based API
207
+
208
+ ```csharp
209
+ [ApiController]
210
+ [Route("api/[controller]")]
211
+ public class UsersController(ISender sender) : ControllerBase
212
+ {
213
+ [HttpGet("{id:guid}")]
214
+ [ProducesResponseType<UserDto>(StatusCodes.Status200OK)]
215
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
216
+ public async Task<IActionResult> Get(Guid id)
217
+ {
218
+ var user = await sender.Send(new GetUserQuery(id));
219
+ return user is not null ? Ok(user) : NotFound();
220
+ }
221
+
222
+ [HttpPost]
223
+ [ProducesResponseType<Guid>(StatusCodes.Status201Created)]
224
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
225
+ public async Task<IActionResult> Create(CreateUserCommand command)
226
+ {
227
+ var id = await sender.Send(command);
228
+ return CreatedAtAction(nameof(Get), new { id }, id);
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Global Exception Handling
234
+
235
+ ```csharp
236
+ public class ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
237
+ {
238
+ public async Task InvokeAsync(HttpContext context)
239
+ {
240
+ try
241
+ {
242
+ await next(context);
243
+ }
244
+ catch (ValidationException ex)
245
+ {
246
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
247
+ await context.Response.WriteAsJsonAsync(new ProblemDetails
248
+ {
249
+ Status = 400,
250
+ Title = "Validation Error",
251
+ Detail = string.Join(", ", ex.Errors.Select(e => e.ErrorMessage))
252
+ });
253
+ }
254
+ catch (NotFoundException ex)
255
+ {
256
+ context.Response.StatusCode = StatusCodes.Status404NotFound;
257
+ await context.Response.WriteAsJsonAsync(new ProblemDetails
258
+ {
259
+ Status = 404,
260
+ Title = "Not Found",
261
+ Detail = ex.Message
262
+ });
263
+ }
264
+ catch (Exception ex)
265
+ {
266
+ logger.LogError(ex, "Unhandled exception");
267
+ context.Response.StatusCode = StatusCodes.Status500InternalServerError;
268
+ await context.Response.WriteAsJsonAsync(new ProblemDetails
269
+ {
270
+ Status = 500,
271
+ Title = "Server Error"
272
+ });
273
+ }
274
+ }
275
+ }
276
+ ```
277
+
278
+ ### Dependency Injection Setup
279
+
280
+ ```csharp
281
+ // Application/DependencyInjection.cs
282
+ public static class DependencyInjection
283
+ {
284
+ public static IServiceCollection AddApplication(this IServiceCollection services)
285
+ {
286
+ services.AddMediatR(cfg => {
287
+ cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
288
+ cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
289
+ cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
290
+ });
291
+
292
+ services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
293
+ services.AddAutoMapper(Assembly.GetExecutingAssembly());
294
+
295
+ return services;
296
+ }
297
+ }
298
+
299
+ // Infrastructure/DependencyInjection.cs
300
+ public static class DependencyInjection
301
+ {
302
+ public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
303
+ {
304
+ services.AddDbContext<ApplicationDbContext>(options =>
305
+ options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
306
+
307
+ services.AddScoped<IApplicationDbContext>(sp =>
308
+ sp.GetRequiredService<ApplicationDbContext>());
309
+
310
+ services.AddScoped<IUserRepository, UserRepository>();
311
+
312
+ return services;
313
+ }
314
+ }
315
+
316
+ // Program.cs
317
+ builder.Services.AddApplication();
318
+ builder.Services.AddInfrastructure(builder.Configuration);
319
+ ```