Installs into .claude/skills of the current project.
Are you the author of Asp Rules?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/david-li0406-asp-rules)
---
name: asp-rules
description: ASP.NET Core Best Practices and Coding Standards for Beyond8 project
---
# ASP.NET Core Best Practices Rules
## Overview
Best practices and coding standards for ASP.NET Core development in the Beyond8 project. Follow these rules to maintain consistency, security, and code quality across all services.
---
## 1. API Design (Minimal APIs)
### Endpoint Organization
- Always organize endpoints using MapGroup with versioning (e.g., /api/v1/...)
- Use static extension methods for API mapping (e.g., MapAuthApi())
- Add descriptive tags, names, and descriptions for OpenAPI documentation
- Always specify return types using .Produces<>() for proper OpenAPI generation
- Explicitly use AllowAnonymous() or RequireAuthorization() on each endpoint
- Apply rate limiting where appropriate using RequireRateLimiting()
### Request/Response Handling
- Always use ApiResponse<T> wrapper for consistent API responses
- Return appropriate HTTP status codes: 200 for success, 400 for bad requests, 401 for unauthorized, 404 for not found, 500 for server errors
- Always handle both success and failure cases in endpoint handlers
- Never return raw exceptions or unhandled errors to clients
---
## 2. Response Consistency
### ApiResponse Pattern
- Always wrap service responses in ApiResponse<T> type
- Use factory methods: SuccessResponse(), FailureResponse(), SuccessPagedResponse()
- Include meaningful messages in all responses for user understanding
- Use SuccessPagedResponse() for paginated data with metadata
### Response Examples
- Success: ApiResponse<T>.SuccessResponse(data, message)
- Failure: ApiResponse<T>.FailureResponse(message)
- Paginated: ApiResponse<List<T>>.SuccessPagedResponse(items, totalItems, pageNumber, pageSize, message)
---
## 3. Error Handling
### Global Exception Middleware
- Always use GlobalExceptionsMiddleware for centralized error handling
- Log all exceptions with appropriate log levels before handling
- Map exceptions to appropriate HTTP status codes using switch expressions
- Never expose sensitive error details to clients in production environment
### Service Layer Error Handling
- Catch and handle specific exceptions in service methods
- Return ApiResponse<T>.FailureResponse() instead of throwing exceptions for business logic errors
- Always log errors with structured logging using ILogger
- Never use generic catch blocks without logging the exception
- Use try-catch blocks for error handling, not for flow control
### Exception Mapping
- UnauthorizedAccessException maps to HTTP 401 Unauthorized
- ArgumentException maps to HTTP 400 Bad Request
- KeyNotFoundException maps to HTTP 404 Not Found
- All other exceptions map to HTTP 500 Internal Server Error
---
## 4. Validation
### FluentValidation (Recommended)
- Always use FluentValidation for request validation in Minimal APIs
- FluentValidation provides better type safety, testability, and separation of concerns
- Create validators in Application/Validators folder, organized by domain (e.g., Auth, Users)
- Register validators using `AddValidatorsFromAssemblyContaining<T>()` in DI container
- Inject validators into endpoint handlers using `IValidator<TRequest>`
- Use ValidationExtensions.ValidateRequest() method for consistent validation handling
### Validator Rules
- Use NotEmpty() instead of Required for non-nullable types
- Provide meaningful error messages in Vietnamese for user-facing validation
- Common rules: NotEmpty(), EmailAddress(), MinimumLength(), MaximumLength(), Matches(), Equal(), NotEqual()
- Use Length(exact) for fixed-length strings like OTP codes
- Chain multiple rules using method chaining for readability
- Use Matches() with regex for complex patterns (passwords, phone numbers)
### Validation in Endpoints
- Always validate request DTOs at the beginning of endpoint handlers
- Pattern: `if (!request.ValidateRequest(validator, out var validationResult)) return validationResult!;`
- Validation errors are automatically formatted as ApiResponse<object>.FailureResponse()
- Never process requests without validation
### Validator Examples
```csharp
public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
{
public RegisterRequestValidator()
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email không được để trống")
.EmailAddress().WithMessage("Email không hợp lệ")
.MaximumLength(256).WithMessage("Email không được vượt quá 256 ký tự");
RuleFor(x => x.Password)
.NotEmpty().WithMessage("Password không được để trống")
.MinimumLength(8).WithMessage("Password tối thiểu 8 ký tự")
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)")
.WithMessage("Password phải có ít nhất 1 chữ thường, 1 chữ hoa và 1 số");
}
}
```
---
## 5. Security Best Practices
### Password Handling
- Always hash passwords using PasswordHasher<User> before storing
- Never store plain text passwords in database
- Use strong password requirements: minimum length 8 characters, complexity rules
- Validate password strength in service layer before hashing
### JWT Authentication
- Store JWT configuration in appsettings.json, never hardcode
- Use secure token expiration times (typically 15-60 minutes for access tokens)
- Always implement refresh token mechanism for long-lived sessions
- Validate tokens on all protected endpoints using RequireAuthorization()
### Authorization
- Use RequireAuthorization() on all protected endpoints
- Use role-based authorization when user roles are defined
- Always validate user permissions in service layer, not just in API layer
- Never trust client-side authentication state, always verify server-side
---
## 6. Dependency Injection
### Service Registration
- Use extension methods for service registration (e.g., AddApplicationServices())
- Register services with appropriate lifetimes based on usage:
- Use Scoped for services with database context (IUnitOfWork, services)
- Use Transient for stateless services without dependencies on context
- Use Singleton only for thread-safe services (caching, configuration)
- Always register repositories through Unit of Work pattern
- Group related service registrations in extension methods
### Constructor Injection
- Use primary constructors (C# 12) when available for cleaner code
- Always inject interfaces, never concrete classes
- Never use service locator pattern or static service resolution
- Keep constructor parameter lists manageable (consider refactoring if too many)
---
## 7. Repository Pattern & Unit of Work
### Repository Interface
- Define repository interfaces in Domain layer
- Implement repositories in Infrastructure layer
- Use generic repository pattern (IGenericRepository<T>) for common CRUD operations
- Add specific methods only when needed, avoid duplicating generic methods
- Use Unit of Work pattern for transaction management across repositories
### Database Operations
- Always use async methods: FindOneAsync, FindAllAsync, AddAsync, UpdateAsync, SaveChangesAsync
- Use transactions for operations that must succeed together
- Handle database exceptions appropriately with specific error messages
- Never use synchronous database methods (.Result, .Wait())
### Unit of Work Pattern
- Expose repositories as properties through IUnitOfWork interface
- Implement SaveChangesAsync() in UnitOfWork for transaction control
- Use IUnitOfWork for all database operations in service layer
---
## 8. Logging
### Structured Logging
- Always use ILogger<T> for logging in all services
- Use structured logging with parameters, never string interpolation
- Include relevant context in log messages using named parameters
- Use appropriate log levels:
- LogError for exceptions and critical errors
- LogWarning for business logic issues and recoverable errors
- LogInformation for important business events and milestones
- LogDebug for detailed debugging information (development only)
### Logging Examples
- Good: logger.LogInformation("User registered successfully: {Email}", request.Email)
- Good: logger.LogError(ex, "Error registering user with email {Email}", request.Email)
- Bad: logger.LogInformation($"User registered successfully: {request.Email}")
---
## 9. Async/Await Patterns
### Asynchronous Methods
- Always use async/await for all I/O operations: database, HTTP, file system, external APIs
- Return Task<T> or Task from async methods, never void
- Use ConfigureAwait(false) only when necessary (rare in ASP.NET Core)
- Never use .Result or .Wait() on async methods as it causes deadlocks
- Use async all the way: if a method calls async, it should be async
### Async Best Practices
- Good: public async Task<ApiResponse<T>> MethodAsync() { await ... }
- Bad: public Task<ApiResponse<T>> MethodAsync() { return task.Result; }
- Always await async calls, don't return tasks directly unless intentionally
---
## 10. Clean Architecture Layers
### Layer Responsibilities
- Domain Layer: Contains entities, enums, repository interfaces, domain logic only
- Application Layer: Contains DTOs, service interfaces and implementations, mappings
- Infrastructure Layer: Contains database context, repository implementations, external services
- API Layer: Contains endpoints, request/response handling, middleware configuration
### Dependency Direction
- Maintain strict dependency direction: API → Application → Domain ← Infrastructure
- Keep Domain layer completely independent of all other layers
- Never reference Infrastructure layer from Application layer
- API layer can reference Application and Infrastructure, but not Domain directly
---
## 11. Entity Framework & Database
### Entities
- Always inherit from BaseEntity for common properties: Id, CreatedAt, UpdatedAt
- Use [Required] attribute for required properties with meaningful error messages
- Configure relationships explicitly in DbContext OnModelCreating method
- Use [MaxLength] for string properties to define database column constraints
### Migrations
- Always create migrations for schema changes, never modify database manually
- Apply migrations on application startup in Development environment
- Use CI/CD pipeline for migration application in Production
- Always review migration files before applying to ensure correctness
### Database Context
- Use DbContextFactory for creating contexts in migrations
- Apply migrations using MigrateDbContextAsync<T>() extension method in Program.cs
- Never commit sensitive connection strings to source control
---
## 12. Caching
### Cache Service
- Use ICacheService for all caching operations
- Always set appropriate expiration times for cached data
- Use consistent key naming conventions (e.g., "otp_register:{email}")
- Consider cache invalidation strategy when updating data
### Cache Examples
- Key format: "prefix:identifier" (e.g., "otp_register:{email}")
- Set: await cacheService.SetAsync(key, value, TimeSpan.FromMinutes(5))
- Get: await cacheService.GetAsync<T>(key)
- Always handle cache misses gracefully
---
## 13. Configuration
### appsettings.json
- Store all configuration in appsettings.json and appsettings.Development.json
- Use strongly-typed configuration classes when possible
- Never commit secrets or sensitive data to source control
- Use User Secrets (Development) or environment variables (Production) for sensitive data
### Connection Strings
- Reference connection strings by name using constants (e.g., Const.IdentityServiceDatabase)
- Store connection strings in appsettings.json, never hardcode
- Use extension methods for database configuration (e.g., AddPostgresDatabase<T>())
---
## 14. Code Organization & Naming
### File Structure
- Group related files in folders: Dtos, Services, Entities, Repositories, Mappings
- Use descriptive namespaces matching folder structure exactly
- Keep files focused on single responsibility principle
- Separate concerns: put interfaces in separate files from implementations
### Naming Conventions
- Use PascalCase for classes, methods, properties, interfaces
- Use camelCase for local variables and method parameters
- Prefix interfaces with I (e.g., IAuthService, IUserRepository)
- Use descriptive names that clearly indicate purpose and responsibility
- Use Async suffix for async methods (e.g., RegisterUserAsync)
### Mapping
- Create mapping extension methods in Application/Mappings folder
- Keep mapping logic separate from business logic
- Use consistent naming: ToEntity(), ToDto(), ToUserSimpleResponse()
- Group mappings by domain entity (e.g., RegisterMappings, UserMappings)
---
## 15. Performance & Testing
### Performance Best Practices
- Use FindOneAsync instead of fetching all records and filtering in memory
- Include only necessary data in queries, avoid SELECT * patterns
- Use pagination for large datasets using SuccessPagedResponse()
- Cache frequently accessed data that doesn't change often
- Optimize database queries and ensure proper indexes exist
### Testing Considerations
- Design services to be testable using dependency injection and interfaces
- Mock dependencies in unit tests using test doubles or mocks
- Test business logic separately from infrastructure concerns
- Use integration tests for database operations, unit tests for business logic
---
## Quick Reference
### Common Patterns
- Service Response: ApiResponse<T>.SuccessResponse(data, message)
- Error Response: ApiResponse<T>.FailureResponse(message)
- Paginated Response: ApiResponse<List<T>>.SuccessPagedResponse(items, total, page, size, message)
- Structured Logging: logger.LogInformation("Message with {Parameter}", value)
- Async Query: await repository.FindOneAsync(x => x.Id == id)
- Service Registration: builder.Services.AddScoped<IService, Service>()
- Protected Endpoint: .RequireAuthorization()
- Validation: [Required(ErrorMessage = "Error message")]
### Service Lifetimes
- Scoped: Services with database context (IUnitOfWork, application services)
- Transient: Stateless services without context dependencies
- Singleton: Thread-safe services (cache, configuration readers)
---
## Checklist
Before submitting code, ensure:
- [ ] All API endpoints use ApiResponse<T> wrapper
- [ ] Services return ApiResponse<T> instead of throwing exceptions for business errors
- [ ] All async methods use async/await properly, no .Result or .Wait()
- [ ] DTOs have validation attributes with meaningful error messages in Vietnamese
- [ ] Logging uses structured logging with parameters, not string interpolation
- [ ] Dependencies are injected through constructors, not service locator
- [ ] Repository pattern and Unit of Work are used for all data access
- [ ] Clean architecture layers are respected (no circular dependencies)
- [ ] Security best practices followed (password hashing, JWT, authorization)
- [ ] Error handling is centralized in middleware
- [ ] Configuration stored in appsettings.json, not hardcoded
- [ ] Code follows consistent naming conventions (PascalCase, camelCase)
- [ ] Database operations use async methods only