Senior .NET Developer Skill
Workflow Instructions
IMPORTANT: When this skill is invoked for complex multi-file implementations, code generation, or architectural tasks:
Use a subagent to perform the actual implementation work to keep the main session context clean
The subagent should:
- Read this entire SKILL.md file as the first action
- Apply all principles, patterns, and best practices defined below
- Implement the requested .NET features following the guidelines
- Return a concise summary of what was implemented
For simple tasks (single file edits, quick answers, code explanations), work directly without a subagent
When to use subagent:
- Creating multiple new files (controllers, services, repositories)
- Refactoring across multiple files
- Implementing new features with tests
- Setting up project architecture
- Complex database migrations
When to work directly:
- Answering .NET questions
- Explaining code patterns
- Single file edits
- Quick bug fixes
- Code reviews
Core Principles
1. Write Idiomatic C# Code
- Use modern C# features (records, pattern matching, nullable reference types, top-level statements where appropriate)
- Prefer expression-bodied members for simple properties and methods
- Use var when type is obvious from right side, explicit types when clarity is needed
- Follow PascalCase for public members, camelCase for private fields with
_ prefix
- Use async/await for all I/O operations; avoid
.Result or .Wait()
- Leverage LINQ for collection operations instead of manual loops when readable
2. Dependency Injection & Configuration
- Constructor injection only for required dependencies
- Register services with appropriate lifetime:
AddSingleton: Stateless services, shared across all requests
AddScoped: Per-request services (DbContext, HTTP context-dependent)
AddTransient: Lightweight, stateless, created each time
- Use IOptions pattern for configuration binding
- Never use Service Locator anti-pattern
- Keep constructors clean; avoid logic in constructors
3. API Development Best Practices
RESTful Design
- Use conventional HTTP verbs: GET (read), POST (create), PUT (update), DELETE (remove), PATCH (partial update)
- Return appropriate status codes:
200 OK: Successful GET/PUT/PATCH
201 Created: Successful POST with Location header
204 No Content: Successful DELETE
400 Bad Request: Validation errors
401 Unauthorized: Missing/invalid authentication
403 Forbidden: Authenticated but insufficient permissions
404 Not Found: Resource doesn't exist
409 Conflict: Business rule violation
500 Internal Server Error: Unhandled exceptions
- Use plural nouns for resource names:
/api/expenses, /api/categories
- Version APIs:
/api/v1/expenses or via headers
Request Validation
- Use Data Annotations for simple validation:
[Required], [MaxLength], [Range]
- Use FluentValidation for complex validation rules
- Implement model validation in action filters or middleware
- Return ProblemDetails (RFC 7807) for error responses
- Validate at API boundary; don't trust input
Response Patterns
// Success with data
return Ok(new { data = result, timestamp = DateTime.UtcNow });
// Created resource
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
// Validation errors
return BadRequest(new ValidationProblemDetails(ModelState));
// Not found
return NotFound(new { message = $"Expense {id} not found" });
4. Async/Await Best Practices
- Always use async all the way: Don't mix sync and async code
- Use
ConfigureAwait(false) in library code (not in ASP.NET Core)
- Return
Task<T> not Task<Task<T>> - don't double-wrap
- Avoid
async void except for event handlers
- Use
ValueTask<T> for hot paths when result often available synchronously
- Use
IAsyncEnumerable<T> for streaming large datasets
5. Error Handling & Logging
Exception Handling
// Use exception filters for logging
[HttpPost]
public async Task<IActionResult> CreateExpense(ExpenseDto dto)
{
try
{
var result = await _service.CreateAsync(dto);
return CreatedAtAction(nameof(GetExpense), new { id = result.Id }, result);
}
catch (ValidationException ex)
{
_logger.LogWarning(ex, "Validation failed for expense creation");
return BadRequest(new { errors = ex.Errors });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error creating expense");
return StatusCode(500, new { message = "An error occurred" });
}
}
Structured Logging
// Use structured logging with named parameters
_logger.LogInformation("Expense {ExpenseId} created by user {UserId}", expense.Id, userId);
// Log levels:
// Trace: Very detailed, development only
// Debug: Internal flow, development/staging
// Information: General flow, track requests
// Warning: Unusual but handled situations
// Error: Errors and exceptions
// Critical: Application/system failures
6. Performance Optimization
Database Access
- Use projection to select only needed columns
- Implement pagination for list endpoints (avoid returning all records)
- Use AsNoTracking() for read-only queries
- Batch operations when possible
- Use compiled queries for frequently executed queries
- Index foreign keys and frequently queried columns
Caching
// In-memory cache for frequently accessed data
public async Task<Category> GetCategoryAsync(string id)
{
var cacheKey = $"category_{id}";
if (!_cache.TryGetValue(cacheKey, out Category category))
{
category = await _repository.GetByIdAsync(id);
_cache.Set(cacheKey, category, TimeSpan.FromMinutes(10));
}
return category;
}
Response Compression
- Enable response compression middleware
- Configure for JSON, XML, text responses
- Use GZIP or Brotli
7. Security Best Practices
Authentication & Authorization
// Use JWT bearer authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
// Apply authorization policies
[Authorize(Policy = "RequireAdminRole")]
public async Task<IActionResult> DeleteExpense(string id)
Input Sanitization
- Never trust user input
- Use parameterized queries (prevents SQL injection)
- Validate and sanitize all inputs
- Use HTML encoding for output
- Implement rate limiting for public APIs
- Use CORS policies appropriately
Secrets Management
- Never hardcode secrets in source code
- Use User Secrets for development
- Use Azure Key Vault, AWS Secrets Manager, or similar for production
- Use environment variables for configuration, not appsettings.json
8. Testing Strategy
Unit Tests
[Fact]
public async Task CreateExpense_ValidInput_ReturnsCreatedExpense()
{
// Arrange
var mockRepo = new Mock<IExpenseRepository>();
var service = new ExpenseService(mockRepo.Object);
var dto = new ExpenseDto { Amount = 100, Description = "Test" };
// Act
var result = await service.CreateAsync(dto);
// Assert
Assert.NotNull(result);
Assert.Equal(100, result.Amount);
mockRepo.Verify(r => r.AddAsync(It.IsAny<Expense>()), Times.Once);
}
Integration Tests
- Use WebApplicationFactory for API testing
- Test against test database (not production)
- Use TestContainers for database integration tests
- Test authentication/authorization flows
- Verify HTTP status codes and response structure
Test Organization
- Arrange-Act-Assert pattern
- One assertion concept per test
- Use descriptive test names:
MethodName_Scenario_ExpectedBehavior
- Mock external dependencies
- Use xUnit, NUnit, or MSTest consistently
9. Code Organization
Project Structure
Solution/
├── src/
│ ├── Api/ # ASP.NET Core API
│ │ ├── Controllers/
│ │ ├── Middleware/
│ │ ├── Filters/
│ │ └── Program.cs
│ ├── Application/ # Business logic, services
│ │ ├── Services/
│ │ ├── DTOs/
│ │ └── Interfaces/
│ ├── Domain/ # Domain models, entities
│ │ ├── Entities/
│ │ └── ValueObjects/
│ └── Infrastructure/ # Data access, external services
│ ├── Repositories/
│ └── Data/
└── tests/
├── UnitTests/
└── IntegrationTests/
Clean Architecture Principles
- Separation of concerns: Each layer has single responsibility
- Dependency inversion: Depend on abstractions, not concretions
- Domain-driven design: Rich domain models with behavior
- Repository pattern: Abstract data access
- Service layer: Orchestrate business operations
10. Modern .NET Patterns
Minimal APIs (Alternative to Controllers)
app.MapGet("/api/expenses/{id}", async (string id, IExpenseService service) =>
{
var expense = await service.GetByIdAsync(id);
return expense is null ? Results.NotFound() : Results.Ok(expense);
});
app.MapPost("/api/expenses", async (ExpenseDto dto, IExpenseService service) =>
{
var expense = await service.CreateAsync(dto);
return Results.Created($"/api/expenses/{expense.Id}", expense);
});
Records for DTOs
public record ExpenseDto(
decimal Amount,
string Description,
string CategoryId,
DateTime Date);
public record ExpenseResponse(
string Id,
decimal Amount,
string Description,
CategoryDto Category,
DateTime CreatedAt);
Result Pattern (Instead of Exceptions for Business Logic)
public record Result<T>(bool IsSuccess, T? Value, string? Error)
{
public static Result<T> Success(T value) => new(true, value, null);
public static Result<T> Failure(string error) => new(false, default, error);
}
Decision Making Framework
When to use which pattern?
| Scenario |
Recommended Pattern |
| Simple CRUD API |
Controller with repository pattern |
| Complex business logic |
CQRS with MediatR |
| High-performance APIs |
Minimal APIs with Dapper |
| Real-time updates |
SignalR with event-driven architecture |
| Long-running processes |
Background services with Hosted Services |
| Microservices |
Domain-driven design with message bus |
| File uploads |
Stream processing with IFormFile |
| Bulk operations |
Background jobs with Hangfire/Quartz |
Database Access Patterns
| Use Case |
Technology |
When |
| Relational data |
Entity Framework Core |
Complex relationships, rapid development |
| High-performance reads |
Dapper |
Read-heavy workloads, complex queries |
| Document storage |
MongoDB with official driver |
Flexible schema, nested documents |
| Caching |
Redis/IMemoryCache |
Frequently accessed data |
| Time-series data |
InfluxDB/TimescaleDB |
Metrics, logs, sensor data |
Common Anti-Patterns to Avoid
❌ Using async void (except event handlers)
❌ Blocking async code with .Result or .Wait()
❌ N+1 queries (use eager loading or projection)
❌ Fat controllers (move logic to services)
❌ Returning IQueryable from services (expose implementation details)
❌ Using DateTime.Now (use DateTime.UtcNow)
❌ Swallowing exceptions without logging
❌ Magic strings (use constants or enums)
❌ God objects (violates Single Responsibility Principle)
❌ Primitive obsession (use value objects for domain concepts)
Quick Checklist
Before completing any .NET task, verify:
References
1---2name: net-developer3description: Expert .NET development guidance. Use when working with C#, ASP.NET Core, Entity Framework, .NET APIs, dependency injection, middleware, authentication, RESTful services, microservices, performance optimization, testing, LINQ, async/await, or any .NET framework task. Provides architectural patterns, best practices, and production-ready code.4---56# Senior .NET Developer Skill78## Workflow Instructions910**IMPORTANT**: When this skill is invoked for complex multi-file implementations, code generation, or architectural tasks:11121. **Use a subagent** to perform the actual implementation work to keep the main session context clean132. **The subagent should**:14 - Read this entire SKILL.md file as the first action15 - Apply all principles, patterns, and best practices defined below16 - Implement the requested .NET features following the guidelines17 - Return a concise summary of what was implemented18193. **For simple tasks** (single file edits, quick answers, code explanations), work directly without a subagent2021**When to use subagent**:22- Creating multiple new files (controllers, services, repositories)23- Refactoring across multiple files24- Implementing new features with tests25- Setting up project architecture26- Complex database migrations2728**When to work directly**:29- Answering .NET questions30- Explaining code patterns31- Single file edits32- Quick bug fixes33- Code reviews3435## Core Principles3637### 1. Write Idiomatic C# Code38- Use **modern C# features** (records, pattern matching, nullable reference types, top-level statements where appropriate)39- Prefer **expression-bodied members** for simple properties and methods40- Use **var** when type is obvious from right side, explicit types when clarity is needed41- Follow **PascalCase** for public members, **camelCase** for private fields with `_` prefix42- Use **async/await** for all I/O operations; avoid `.Result` or `.Wait()`43- Leverage **LINQ** for collection operations instead of manual loops when readable4445### 2. Dependency Injection & Configuration46- **Constructor injection only** for required dependencies47- Register services with appropriate lifetime:48 - `AddSingleton`: Stateless services, shared across all requests49 - `AddScoped`: Per-request services (DbContext, HTTP context-dependent)50 - `AddTransient`: Lightweight, stateless, created each time51- Use **IOptions<T>** pattern for configuration binding52- Never use Service Locator anti-pattern53- Keep constructors clean; avoid logic in constructors5455### 3. API Development Best Practices5657#### RESTful Design58- Use **conventional HTTP verbs**: GET (read), POST (create), PUT (update), DELETE (remove), PATCH (partial update)59- Return appropriate status codes:60 - `200 OK`: Successful GET/PUT/PATCH61 - `201 Created`: Successful POST with `Location` header62 - `204 No Content`: Successful DELETE63 - `400 Bad Request`: Validation errors64 - `401 Unauthorized`: Missing/invalid authentication65 - `403 Forbidden`: Authenticated but insufficient permissions66 - `404 Not Found`: Resource doesn't exist67 - `409 Conflict`: Business rule violation68 - `500 Internal Server Error`: Unhandled exceptions69- Use **plural nouns** for resource names: `/api/expenses`, `/api/categories`70- Version APIs: `/api/v1/expenses` or via headers7172#### Request Validation73- Use **Data Annotations** for simple validation: `[Required]`, `[MaxLength]`, `[Range]`74- Use **FluentValidation** for complex validation rules75- Implement model validation in action filters or middleware76- Return **ProblemDetails** (RFC 7807) for error responses77- Validate at API boundary; don't trust input7879#### Response Patterns80```csharp81// Success with data82return Ok(new { data = result, timestamp = DateTime.UtcNow });8384// Created resource85return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);8687// Validation errors88return BadRequest(new ValidationProblemDetails(ModelState));8990// Not found91return NotFound(new { message = $"Expense {id} not found" });92```9394### 4. Async/Await Best Practices95- **Always use async all the way**: Don't mix sync and async code96- Use `ConfigureAwait(false)` in library code (not in ASP.NET Core)97- Return `Task<T>` not `Task<Task<T>>` - don't double-wrap98- Avoid `async void` except for event handlers99- Use `ValueTask<T>` for hot paths when result often available synchronously100- Use `IAsyncEnumerable<T>` for streaming large datasets101102### 5. Error Handling & Logging103104#### Exception Handling105```csharp106// Use exception filters for logging107[HttpPost]108public async Task<IActionResult> CreateExpense(ExpenseDto dto)109{110 try111 {112 var result = await _service.CreateAsync(dto);113 return CreatedAtAction(nameof(GetExpense), new { id = result.Id }, result);114 }115 catch (ValidationException ex)116 {117 _logger.LogWarning(ex, "Validation failed for expense creation");118 return BadRequest(new { errors = ex.Errors });119 }120 catch (Exception ex)121 {122 _logger.LogError(ex, "Unexpected error creating expense");123 return StatusCode(500, new { message = "An error occurred" });124 }125}126```127128#### Structured Logging129```csharp130// Use structured logging with named parameters131_logger.LogInformation("Expense {ExpenseId} created by user {UserId}", expense.Id, userId);132133// Log levels:134// Trace: Very detailed, development only135// Debug: Internal flow, development/staging136// Information: General flow, track requests137// Warning: Unusual but handled situations138// Error: Errors and exceptions139// Critical: Application/system failures140```141142### 6. Performance Optimization143144#### Database Access145- Use **projection** to select only needed columns146- Implement **pagination** for list endpoints (avoid returning all records)147- Use **AsNoTracking()** for read-only queries148- Batch operations when possible149- Use **compiled queries** for frequently executed queries150- Index foreign keys and frequently queried columns151152#### Caching153```csharp154// In-memory cache for frequently accessed data155public async Task<Category> GetCategoryAsync(string id)156{157 var cacheKey = $"category_{id}";158 if (!_cache.TryGetValue(cacheKey, out Category category))159 {160 category = await _repository.GetByIdAsync(id);161 _cache.Set(cacheKey, category, TimeSpan.FromMinutes(10));162 }163 return category;164}165```166167#### Response Compression168- Enable response compression middleware169- Configure for JSON, XML, text responses170- Use GZIP or Brotli171172### 7. Security Best Practices173174#### Authentication & Authorization175```csharp176// Use JWT bearer authentication177services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)178 .AddJwtBearer(options => {179 options.TokenValidationParameters = new TokenValidationParameters180 {181 ValidateIssuer = true,182 ValidateAudience = true,183 ValidateLifetime = true,184 ValidateIssuerSigningKey = true185 };186 });187188// Apply authorization policies189[Authorize(Policy = "RequireAdminRole")]190public async Task<IActionResult> DeleteExpense(string id)191```192193#### Input Sanitization194- **Never trust user input**195- Use parameterized queries (prevents SQL injection)196- Validate and sanitize all inputs197- Use HTML encoding for output198- Implement rate limiting for public APIs199- Use CORS policies appropriately200201#### Secrets Management202- **Never hardcode secrets** in source code203- Use User Secrets for development204- Use Azure Key Vault, AWS Secrets Manager, or similar for production205- Use environment variables for configuration, not appsettings.json206207### 8. Testing Strategy208209#### Unit Tests210```csharp211[Fact]212public async Task CreateExpense_ValidInput_ReturnsCreatedExpense()213{214 // Arrange215 var mockRepo = new Mock<IExpenseRepository>();216 var service = new ExpenseService(mockRepo.Object);217 var dto = new ExpenseDto { Amount = 100, Description = "Test" };218 219 // Act220 var result = await service.CreateAsync(dto);221 222 // Assert223 Assert.NotNull(result);224 Assert.Equal(100, result.Amount);225 mockRepo.Verify(r => r.AddAsync(It.IsAny<Expense>()), Times.Once);226}227```228229#### Integration Tests230- Use **WebApplicationFactory** for API testing231- Test against test database (not production)232- Use **TestContainers** for database integration tests233- Test authentication/authorization flows234- Verify HTTP status codes and response structure235236#### Test Organization237- Arrange-Act-Assert pattern238- One assertion concept per test239- Use descriptive test names: `MethodName_Scenario_ExpectedBehavior`240- Mock external dependencies241- Use xUnit, NUnit, or MSTest consistently242243### 9. Code Organization244245#### Project Structure246```247Solution/248├── src/249│ ├── Api/ # ASP.NET Core API250│ │ ├── Controllers/251│ │ ├── Middleware/252│ │ ├── Filters/253│ │ └── Program.cs254│ ├── Application/ # Business logic, services255│ │ ├── Services/256│ │ ├── DTOs/257│ │ └── Interfaces/258│ ├── Domain/ # Domain models, entities259│ │ ├── Entities/260│ │ └── ValueObjects/261│ └── Infrastructure/ # Data access, external services262│ ├── Repositories/263│ └── Data/264└── tests/265 ├── UnitTests/266 └── IntegrationTests/267```268269#### Clean Architecture Principles270- **Separation of concerns**: Each layer has single responsibility271- **Dependency inversion**: Depend on abstractions, not concretions272- **Domain-driven design**: Rich domain models with behavior273- **Repository pattern**: Abstract data access274- **Service layer**: Orchestrate business operations275276### 10. Modern .NET Patterns277278#### Minimal APIs (Alternative to Controllers)279```csharp280app.MapGet("/api/expenses/{id}", async (string id, IExpenseService service) =>281{282 var expense = await service.GetByIdAsync(id);283 return expense is null ? Results.NotFound() : Results.Ok(expense);284});285286app.MapPost("/api/expenses", async (ExpenseDto dto, IExpenseService service) =>287{288 var expense = await service.CreateAsync(dto);289 return Results.Created($"/api/expenses/{expense.Id}", expense);290});291```292293#### Records for DTOs294```csharp295public record ExpenseDto(296 decimal Amount,297 string Description,298 string CategoryId,299 DateTime Date);300301public record ExpenseResponse(302 string Id,303 decimal Amount,304 string Description,305 CategoryDto Category,306 DateTime CreatedAt);307```308309#### Result Pattern (Instead of Exceptions for Business Logic)310```csharp311public record Result<T>(bool IsSuccess, T? Value, string? Error)312{313 public static Result<T> Success(T value) => new(true, value, null);314 public static Result<T> Failure(string error) => new(false, default, error);315}316```317318## Decision Making Framework319320### When to use which pattern?321322| Scenario | Recommended Pattern |323|----------|-------------------|324| Simple CRUD API | Controller with repository pattern |325| Complex business logic | CQRS with MediatR |326| High-performance APIs | Minimal APIs with Dapper |327| Real-time updates | SignalR with event-driven architecture |328| Long-running processes | Background services with Hosted Services |329| Microservices | Domain-driven design with message bus |330| File uploads | Stream processing with IFormFile |331| Bulk operations | Background jobs with Hangfire/Quartz |332333### Database Access Patterns334335| Use Case | Technology | When |336|----------|-----------|------|337| Relational data | Entity Framework Core | Complex relationships, rapid development |338| High-performance reads | Dapper | Read-heavy workloads, complex queries |339| Document storage | MongoDB with official driver | Flexible schema, nested documents |340| Caching | Redis/IMemoryCache | Frequently accessed data |341| Time-series data | InfluxDB/TimescaleDB | Metrics, logs, sensor data |342343## Common Anti-Patterns to Avoid344345❌ **Using `async void`** (except event handlers)346❌ **Blocking async code** with `.Result` or `.Wait()`347❌ **N+1 queries** (use eager loading or projection)348❌ **Fat controllers** (move logic to services)349❌ **Returning IQueryable from services** (expose implementation details)350❌ **Using `DateTime.Now`** (use `DateTime.UtcNow`)351❌ **Swallowing exceptions** without logging352❌ **Magic strings** (use constants or enums)353❌ **God objects** (violates Single Responsibility Principle)354❌ **Primitive obsession** (use value objects for domain concepts)355356## Quick Checklist357358Before completing any .NET task, verify:359360- [ ] All I/O operations are async361- [ ] Appropriate HTTP status codes returned362- [ ] Input validation implemented363- [ ] Proper error handling and logging364- [ ] Dependencies injected via constructor365- [ ] No hardcoded configuration values366- [ ] Nullable reference types considered367- [ ] XML documentation on public APIs368- [ ] Unit tests written for business logic369- [ ] Follows SOLID principles370- [ ] Uses appropriate data structures371- [ ] No memory leaks (dispose IDisposable)372- [ ] Thread-safe when needed373- [ ] Performance considered (async, caching, pagination)374- [ ] Security vulnerabilities checked375376## References377378- [Microsoft .NET Documentation](https://docs.microsoft.com/dotnet/)379- [C# Coding Conventions](https://docs.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions)380- [ASP.NET Core Best Practices](https://docs.microsoft.com/aspnet/core/fundamentals/best-practices)381- [Clean Architecture in .NET](https://github.com/jasontaylordev/CleanArchitecture)