Skip to main content

dotnet-expert

.NET development principles and decision-making. .NET 8, ASP.NET Core, C# 12, dependency injection, best practices.

Source facts

Repository
skeletorflet/opencode-kit
Last source activity
March 31, 2026 at 19:12
Detected SKILL.md language
English
Stars
9
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
dotnet-expert
description
.NET development principles and decision-making. .NET 8, ASP.NET Core, C# 12, dependency injection, best practices.
# .NET Expert - Modern .NET 8 > .NET development principles and decision-making. > **Learn to THINK, not copy-paste patterns.** --- ## ⚠️ How to Use This Skill This skill teaches **decision-making principles** for .NET development. - ASK user for project type (web API, worker, blazor) - Choose minimal APIs vs controller-based - Don't use outdated .NET Framework patterns --- ## 1. Framework Selection ### Decision Tree ``` What are you building? │ ├── REST API │ ├── Minimal APIs (NEW .NET 6+) - Recommended for simple APIs │ └── Controller-based - Complex operations, heavy validation │ ├── Full-stack Web │ ├── Blazor Server - Server-rendered, WebSocket │ ├── Blazor WebAssembly - Client-side, PWA-ish │ └── ASP.NET Core MVC - Traditional Razor │ ├── Background Worker │ └── .NET Worker Services + Quartz/Hangfire │ ├── gRPC Services │ └── .NET gRPC (protobuf) │ ├── Desktop │ ├── WPF (.NET 8) │ ├── WinForms (legacy) │ └── MAUI (cross-platform) │ └── Microservices └── .NET 8 + Dapr (cloud-native patterns) ``` ### Comparison: Minimal APIs vs Controllers | Factor | Minimal APIs | Controllers | |--------|--------------|-------------| | **Boilerplate** | Very Low | Medium | | **Routing** | Inline/Fluent | Attribute-based | | **Validation** | Manual/FluentValidation | DataAnnotations | | **Best For** | Microservices, simple APIs | Enterprise, complex validation | | **OpenAPI/Swagger** | Built-in | Route-based | --- ## 2. Modern C# 12 Features ### Primary Constructors ```csharp // C# 12: Primary constructors (NEW!) public class UserService( IUserRepository userRepository, IEmailService emailService) { public User? GetById(int id) { return userRepository.FindById(id); } } // Or record with primary constructor (immutable) public record User(int Id, string Name, string Email); ``` ### Collection Expressions ```csharp // OLD: Array initializer var list = new List<int> { 1, 2, 3 }; int[] array = new int[] { 1, 2, 3 }; // C# 12: Collection expressions var list = [1, 2, 3]; int[] array = [1, 2, 3]; var dict = new Dictionary<string, int> { {"a", 1} }; var dict2 = ["a", 1 ]; ``` ### LINQ Improvements ```csharp // NEW in .NET: More efficient LINQ var result = items .Where(x => x.IsActive) .OrderBy(x => x.Name) .Select(x => x.ToDto()) .ToList(); // Materializes once // Use CountBy, ChunkBy for better performance var countByCategory = products.CountBy(p => p.Category); // Returns: IEnumerable<(Category, int)> ``` ### Pattern Matching ```csharp // Pattern matching with C# 12 public string Describe(object obj) => obj switch { int i => $"Integer: {i}", string s => $"String: {s.ToUpper()}", User { Name: "Admin" } => "Admin user!", User u => $"User: {u.Name}", null => "Null!", _ => $"Type: {obj.GetType().Name}" }; ``` --- ## 3. Dependency Injection ### .NET 8 DI (Built-in) ```csharp // Configure services (Program.cs) var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddTransient<IEmailService, EmailService>(); builder.Services.AddSingleton<IConfigService, ConfigService>(); // Use options pattern builder.Services.Configure<DatabaseOptions>( builder.Configuration.GetSection("Database")); // Build var app = builder.Build(); // Minimal API with DI app.MapGet("/users/{id}", (IUserRepository repo, int id) => repo.FindById(id)); ``` ### Constructor Injection ```csharp // ✅ RECOMMENDED in .NET public class UserService(IUserRepository repository) { private readonly IUserRepository _repo = repository; public User? GetById(int id) => _repo.FindById(id); } // Record-based service (C# 12) public record EmailService(SmtpConfig Config) : IEmailService { public async Task SendAsync(string to, string subject, string body) { // Implementation } } ``` --- ## 4. Minimal APIs (Recommended) ### Basic Pattern ```csharp // Program.cs - Minimal API var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddDbContext<AppDbContext>(); builder.Services.AddScoped<IUserRepository, UserRepository>(); var app = builder.Build(); // Endpoints app.MapGet("/api/users", async (AppDbContext db) => await db.Users.ToListAsync()); app.MapGet("/api/users/{id}", async (int id, AppDbContext db) => await db.Users.FindAsync(id)); app.MapPost("/api/users", async (User user, AppDbContext db) => { db.Users.Add(user); await db.SaveChangesAsync(); return Results.Created($"/api/users/{user.Id}", user); }); app.Run(); ``` ### With Validation ```csharp // Using FluentValidation public record CreateUserRequest(string Name, string Email); app.MapPost("/api/users", async (CreateUserRequest request, IValidator<CreateUserRequest> validator, AppDbContext db) => { var validation = await validator.ValidateAsync(request); if (!validation.IsValid) return Results.BadRequest(validation.Errors); var user = new User { Name = request.Name, Email = request.Email }; db.Users.Add(user); await db.SaveChangesAsync(); return Results.Created($"/api/users/{user.Id}", user); }); ``` --- ## 5. Database Access ### Entity Framework Core ```csharp // DbContext public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {} public DbSet<User> Users => Set<User>(); public DbSet<Order> Orders => Set<Order>(); } // Entity (use primary constructor for EF Core 8) public class User { public int Id { get; set; } public string Name { get; set; } = ""; public string Email { get; set; } = ""; public DateTime CreatedAt { get; set; } } // Queries with LINQ var users = await context.Users .Where(u => u.IsActive) .OrderBy(u => u.Name) .Select(u => new UserDto(u.Id, u.Name)) .ToListAsync(); ``` ### Dapper for Performance ```csharp // Dapper - lightweight ORM public class UserRepository : IUserRepository { private readonly DbConnection _connection; public async Task<User?> FindById(int id) { return await _connection.QueryFirstOrDefaultAsync<User>( "SELECT * FROM Users WHERE Id = @Id", new { Id = id }); } } ``` ### Comparison | Approach | Best For | Performance | |----------|----------|-------------| | **EF Core** | Most CRUD, migrations | Good | | **Dapper** | Raw SQL, high performance | Excellent | | **ADO.NET** | Minimal overhead | Best | --- ## 6. Testing Patterns ### xUnit with Moq ```csharp // Unit test public class UserServiceTest { [Fact] public void GetById_WhenExists_ReturnsUser() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(r => r.FindById(1)) .Returns(new User(1, "test@test.com")); var service = new UserService(mockRepo.Object); // Act var result = service.GetById(1); // Assert Assert.NotNull(result); Assert.Equal("test@test.com", result.Email); } } // Integration test public class UserApiTest : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _client; public UserApiTest(WebApplicationFactory<Program> factory) { _client = factory.CreateClient(); } [Fact] public async Task GetUsers_ReturnsOk() { var response = await _client.GetAsync("/api/users"); Assert.True(response.IsSuccessStatusCode); } } ``` ### Testing Tools | Tool | Purpose | |------|---------| | **xUnit** | Unit/Integration testing | | **Moq** | Mocking framework | | **FluentAssertions** | Fluent assertions | | **Testcontainers.Sqlite** | In-memory DB testing | | **WireMock** | HTTP mocking | --- ## 7. Configuration ### Options Pattern ```csharp // Configuration class public class DatabaseOptions { public string ConnectionString { get; set; } = ""; public int MaxConnections { get; set; } = 10; } // appsettings.json { "Database": { "ConnectionString": "Server=.;Database=mydb;", "MaxConnections": 20 } } // Registration builder.Services.Configure<DatabaseOptions>( builder.Configuration.GetSection("Database")); // Usage public class UserService( IOptions<DatabaseOptions> options) { public string ConnString => options.Value.ConnectionString; } ``` --- ## 8. Error Handling ### Global Exception Handler ```csharp // Program.cs app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; await context.Response.WriteAsync( "{\"error\": \"An unexpected error occurred\"}"); }); }); // Problem Details (RFC 7807) app.MapPost("/api/users", async ([FromBody] User user) => { try { // Process } catch (Exception ex) { return Results.Problem(detail: ex.Message, statusCode: 400); } }); ``` --- ## 9. Decision Checklist Before implementing: - [ ] **Minimal API or Controller? (match complexity)** - [ ] **EF Core or Dapper? (match performance needs)** - [ ] **Async/await for I/O operations?** - [ ] **Dependency injection configured?** - [ ] **Validation strategy chosen?** - [ ] **Testing approach defined?** - [ ] **Configuration via options pattern?** --- ## ❌ Anti-Patterns to Avoid ### ❌ DON'T: - Use synchronous I/O in API endpoints - Configure EF Core without tracking changes awareness - Use ViewBag/ViewData in minimal APIs - Skip health checks in microservices - Use .NET Framework patterns in .NET 8 - Skip connection string encryption for production ### ✅ DO: - Use Primary Constructors (C# 12) - Use records for DTOs and responses - Use Minimal APIs for simple services - Use IAsyncEnumerable for streaming - Configure health checks (/healthz) - Use FluentValidation for complex validation --- > **Remember:** .NET 8 is modern and productive. > Use Minimal APIs, records, and primary constructors. > **Think in modern .NET, not .NET Framework.**
View on GitHub