This comprehensive guide provides developers with everything needed to quickly become productive with the SOLID Template, including best practices, tips, tricks, and essential resources.
Before diving into development, ensure your environment is properly configured:
# Install required tools
winget install Microsoft.DotNet.SDK.8
winget install Microsoft.VisualStudio.2022.Community
winget install Git.Git
# Verify installations
dotnet --version # Should show 8.0.x
git --version
# Recommended VS Code extensions (if using VS Code)
code --install-extension ms-dotnettools.csharp
code --install-extension ms-dotnettools.csdevkit
code --install-extension bradlc.vscode-tailwindcss- Clone the repository
- Build the solution (
dotnet build) - Run tests (
dotnet test) - Start the API (
dotnet run) - Verify Swagger UI works (http://localhost:5000/swagger)
- Review project structure
- Read this developer guide completely
The template follows Clean Architecture with clear separation of concerns:
βββββββββββββββββββββββββββββββββββββββ
β Presentation β β Controllers, API endpoints
βββββββββββββββββββββββββββββββββββββββ€
β Application β β Services, DTOs, Validators
βββββββββββββββββββββββββββββββββββββββ€
β Infrastructure β β Repositories, Database, External APIs
βββββββββββββββββββββββββββββββββββββββ€
β Domain β β Entities, Business Rules, Interfaces
βββββββββββββββββββββββββββββββββββββββ
Critical Rule: Dependencies point inward only!
- β Presentation β Application β Domain
- β Infrastructure β Domain
- β Domain should never depend on outer layers
| Pattern | Usage | Example |
|---|---|---|
| Repository | Data access abstraction | IProductRepository |
| Service Layer | Business logic orchestration | ProductService |
| DTO | Data transfer objects | CreateProductDto |
| Dependency Injection | Loose coupling | Constructor injection |
| CQRS (Light) | Read/Write separation | Different DTOs for operations |
| Factory | Object creation | Service registration |
Follow this proven workflow for new features:
# 1. Create feature branch
git checkout -b feature/product-management
# 2. Follow layer-by-layer approach
# Domain β Infrastructure β Application β Presentation β Tests
# 3. Test continuously
dotnet test
# 4. Document as you go
# Add XML comments, update README if needed
# 5. Review and refactor
# Check SOLID principles compliance// β
Good naming
public class ProductService : IProductService
public async Task<BaseResponseDto<ProductDto>> CreateAsync(CreateProductDto dto)
public const string CACHE_KEY_PRODUCTS = "products:all";
// β Poor naming
public class PS : IPS
public async Task<object> DoStuff(object thing)
public const string KEY = "key1";Domain/
βββ Entities/ # Business entities
βββ Interfaces/ # Repository and service contracts
βββ Enums/ # Domain enumerations
Application/
βββ DTOs/ # Data transfer objects
βββ Interfaces/ # Application service contracts
βββ Services/ # Business logic implementation
βββ Validators/ # Input validation rules
βββ Mappings/ # AutoMapper profiles
Infrastructure/
βββ Data/ # DbContext and configurations
βββ Repositories/ # Data access implementations
βββ Services/ # External service implementations
Presentation/
βββ Controllers/ # API endpoints
βββ Extensions/ # DI container configuration
E2E Tests (Few - High Confidence)
β β
Integration Tests (Some - Medium Confidence)
β β
Unit Tests (Many - Fast Feedback)
// β
Good test structure - AAA Pattern
[Fact]
public async Task CreateProduct_WithValidData_ReturnsSuccess()
{
// Arrange
var createDto = new CreateProductDto { Name = "Test Product", Price = 10.99m };
var expectedProduct = new Product { Id = 1, Name = "Test Product" };
_mockRepository.Setup(x => x.AddAsync(It.IsAny<Product>()))
.ReturnsAsync(expectedProduct);
// Act
var result = await _productService.CreateAsync(createDto);
// Assert
result.Should().NotBeNull();
result.Success.Should().BeTrue();
result.Data.Name.Should().Be("Test Product");
}{
"recommendations": [
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"ms-dotnettools.vscode-dotnet-runtime",
"formulahendry.auto-rename-tag",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-json"
]
}Create these in VS Code (File β Preferences β Configure User Snippets β csharp.json):
{
"Create Service": {
"prefix": "service",
"body": [
"public class ${1:Name}Service : I${1:Name}Service",
"{",
"\tprivate readonly I${1:Name}Repository _repository;",
"\tprivate readonly IMapper _mapper;",
"",
"\tpublic ${1:Name}Service(I${1:Name}Repository repository, IMapper mapper)",
"\t{",
"\t\t_repository = repository;",
"\t\t_mapper = mapper;",
"\t}",
"",
"\tpublic async Task<BaseResponseDto<${1:Name}Dto>> CreateAsync(Create${1:Name}Dto dto)",
"\t{",
"\t\ttry",
"\t\t{",
"\t\t\tvar entity = _mapper.Map<${1:Name}>(dto);",
"\t\t\tvar created = await _repository.AddAsync(entity);",
"\t\t\tvar result = _mapper.Map<${1:Name}Dto>(created);",
"\t\t\treturn BaseResponseDto<${1:Name}Dto>.Success(result);",
"\t\t}",
"\t\tcatch (Exception ex)",
"\t\t{",
"\t\t\treturn BaseResponseDto<${1:Name}Dto>.Failure($\"Error creating ${1:Name}: {ex.Message}\");",
"\t\t}",
"\t}",
"}"
],
"description": "Create a new service class"
},
"Create Controller": {
"prefix": "controller",
"body": [
"[ApiController]",
"[Route(\"api/[controller]\")]",
"public class ${1:Name}Controller : ControllerBase",
"{",
"\tprivate readonly I${1:Name}Service _service;",
"",
"\tpublic ${1:Name}Controller(I${1:Name}Service service)",
"\t{",
"\t\t_service = service;",
"\t}",
"",
"\t[HttpGet]",
"\tpublic async Task<ActionResult<BaseResponseDto<IEnumerable<${1:Name}Dto>>>> GetAll()",
"\t{",
"\t\tvar result = await _service.GetAllAsync();",
"\t\treturn result.Success ? Ok(result) : BadRequest(result);",
"\t}",
"",
"\t[HttpPost]",
"\tpublic async Task<ActionResult<BaseResponseDto<${1:Name}Dto>>> Create([FromBody] Create${1:Name}Dto dto)",
"\t{",
"\t\tif (!ModelState.IsValid)",
"\t\t\treturn BadRequest(ModelState);",
"",
"\t\tvar result = await _service.CreateAsync(dto);",
"\t\treturn result.Success ? CreatedAtAction(nameof(GetById), new { id = result.Data?.Id }, result) : BadRequest(result);",
"\t}",
"}"
],
"description": "Create a new API controller"
}
}Create these helpful scripts in your project root:
dev-setup.ps1
# Developer setup script
Write-Host "π Setting up SOLID Template development environment..." -ForegroundColor Green
# Restore packages
Write-Host "π¦ Restoring NuGet packages..." -ForegroundColor Yellow
dotnet restore
# Build solution
Write-Host "π¨ Building solution..." -ForegroundColor Yellow
dotnet build
# Run tests
Write-Host "π§ͺ Running tests..." -ForegroundColor Yellow
dotnet test --no-build
# Check for EF tools
if (!(Get-Command "dotnet-ef" -ErrorAction SilentlyContinue)) {
Write-Host "π Installing Entity Framework tools..." -ForegroundColor Yellow
dotnet tool install --global dotnet-ef
}
Write-Host "β
Setup complete! Ready to develop." -ForegroundColor Green
Write-Host "π‘ Tip: Run 'dotnet run' to start the API" -ForegroundColor Bluequick-test.ps1
# Quick test and build script
param(
[string]$Project = "",
[switch]$Watch
)
if ($Project) {
if ($Watch) {
dotnet test $Project --watch
} else {
dotnet test $Project --no-restore
}
} else {
dotnet test --no-restore
}| Technology | Documentation | Key Concepts |
|---|---|---|
| .NET 8 | docs.microsoft.com/dotnet | Minimal APIs, Performance improvements |
| ASP.NET Core | docs.microsoft.com/aspnet/core | Controllers, Middleware, DI |
| Entity Framework | docs.microsoft.com/ef/core | Code-First, Migrations, Querying |
| AutoMapper | automapper.org | Object-to-object mapping |
| FluentValidation | fluentvalidation.net | Input validation |
| Serilog | serilog.net | Structured logging |
-
Books:
- "Clean Architecture" by Robert C. Martin
- "Implementing Domain-Driven Design" by Vaughn Vernon
- "Clean Code" by Robert C. Martin
-
Articles:
- YouTube Channels:
- Nick Chapsas (.NET Performance & Best Practices)
- Milan JovanoviΔ (.NET Architecture)
- IAmTimCorey (C# Fundamentals)
- Stack Overflow: Use tags
c#,asp.net-core,entity-framework-core - Reddit: r/dotnet, r/csharp
- Discord: .NET Community Discord
- GitHub: Search for "clean architecture dotnet" examples
# Clear and restore packages
dotnet clean
dotnet restore
dotnet build
# Check for framework issues
dotnet --info# Reset database
dotnet ef database drop --force
dotnet ef database update
# Check migrations
dotnet ef migrations list// β
Correct registration order in Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(); // First
builder.Services.AddRepositories(); // Then repositories
builder.Services.AddApplicationServices(); // Then services
builder.Services.AddValidators(); // Finally validators// β
Verify mappings in tests
[Fact]
public void AutoMapper_Configuration_IsValid()
{
var configuration = new MapperConfiguration(cfg => cfg.AddProfile<MappingProfile>());
configuration.AssertConfigurationIsValid();
}// β
Good logging with context
_logger.LogInformation("Creating product with SKU {Sku} for category {Category}",
dto.Sku, dto.Category);
// β Poor logging
_logger.LogInformation("Creating product: " + dto.Sku);- Set breakpoints on business logic entry points
- Use conditional breakpoints for specific scenarios
- Leverage VS Code's call stack for tracing
# Test authentication
curl -X GET http://localhost:5000/api/products \
-H "Authorization: Bearer YOUR_TOKEN"
# Test POST with JSON
curl -X POST http://localhost:5000/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Test Product","price":19.99,"category":"Electronics"}'
# Test with verbose output
curl -v -X GET http://localhost:5000/healthpublic class SkuFormatAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value is not string sku) return false;
// Custom SKU validation logic
return Regex.IsMatch(sku, @"^[A-Z]{2,4}-\d{3,6}$");
}
}
// Usage in DTO
public class CreateProductDto
{
[SkuFormat(ErrorMessage = "SKU must be in format ABC-123")]
public string Sku { get; set; }
}public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var startTime = DateTimeOffset.UtcNow;
await _next(context);
var elapsed = DateTimeOffset.UtcNow - startTime;
_logger.LogInformation("Request {Method} {Path} completed in {ElapsedMs}ms with status {StatusCode}",
context.Request.Method,
context.Request.Path,
elapsed.TotalMilliseconds,
context.Response.StatusCode);
}
}public class StockCheckService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<StockCheckService> _logger;
public StockCheckService(IServiceProvider serviceProvider, ILogger<StockCheckService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var productService = scope.ServiceProvider.GetRequiredService<IProductService>();
// Check for low stock products
await CheckLowStockProducts(productService);
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}// β
Use async/await consistently
public async Task<IEnumerable<Product>> GetProductsAsync()
{
return await _context.Products
.AsNoTracking() // For read-only operations
.Where(p => !p.IsDeleted)
.ToListAsync();
}
// β
Use proper indexing in DbContext
modelBuilder.Entity<Product>()
.HasIndex(p => p.Category)
.HasDatabaseName("IX_Product_Category");public class CachedProductService : IProductService
{
private readonly IProductService _innerService;
private readonly IMemoryCache _cache;
private const string CACHE_KEY = "products:all";
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15);
public async Task<BaseResponseDto<IEnumerable<ProductDto>>> GetAllAsync()
{
if (_cache.TryGetValue(CACHE_KEY, out BaseResponseDto<IEnumerable<ProductDto>>? cached))
{
return cached!;
}
var result = await _innerService.GetAllAsync();
if (result.Success)
{
_cache.Set(CACHE_KEY, result, CacheDuration);
}
return result;
}
}// β
Use pagination for large datasets
public class PaginatedRequest
{
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string? SortBy { get; set; }
public string? SortDirection { get; set; } = "asc";
}
// β
Enable response compression in Program.cs
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
});// β
Always validate and sanitize input
public class CreateProductValidator : AbstractValidator<CreateProductDto>
{
public CreateProductValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(100)
.Matches(@"^[a-zA-Z0-9\s\-_.]+$") // Prevent script injection
.WithMessage("Name contains invalid characters");
RuleFor(x => x.Price)
.GreaterThan(0)
.LessThan(1000000); // Reasonable upper limit
}
}// β
Don't expose internal details in production
public async Task<BaseResponseDto<ProductDto>> CreateAsync(CreateProductDto dto)
{
try
{
// ... business logic
}
catch (ValidationException ex)
{
return BaseResponseDto<ProductDto>.Failure(ex.Message); // Safe to expose
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating product");
// Don't expose internal exception details
return BaseResponseDto<ProductDto>.Failure("An error occurred while creating the product");
}
}// In Program.cs
builder.Services.AddHealthChecks()
.AddDbContext<ApplicationDbContext>()
.AddCheck("custom-check", () => HealthCheckResult.Healthy("All systems operational"));
// Custom health check
public class DatabaseHealthCheck : IHealthCheck
{
private readonly ApplicationDbContext _context;
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
await _context.Database.CanConnectAsync(cancellationToken);
return HealthCheckResult.Healthy("Database is accessible");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database is not accessible", ex);
}
}
}// Add to Program.cs
builder.Services.AddApplicationInsightsTelemetry();
// Custom telemetry
public class ProductService : IProductService
{
private readonly TelemetryClient _telemetryClient;
public async Task<BaseResponseDto<ProductDto>> CreateAsync(CreateProductDto dto)
{
_telemetryClient.TrackEvent("ProductCreated", new Dictionary<string, string>
{
["Category"] = dto.Category,
["Price"] = dto.Price.ToString()
});
// ... rest of implementation
}
}- Master the basics: Understand each layer's responsibility
- Build your first feature: Follow the adding-features guide
- Write comprehensive tests: Aim for >80% code coverage
- Learn the debugging tools: Master VS Code/VS debugging features
- Performance optimization: Implement caching and pagination
- Security hardening: Add authentication and authorization
- CI/CD setup: Automate builds and deployments
- Monitoring: Implement comprehensive logging and health checks
- Microservices: Split into multiple services if needed
- Event-driven architecture: Implement domain events
- Advanced patterns: CQRS, Event Sourcing, Saga patterns
- Container deployment: Docker and Kubernetes
graph TD
A[Clean Architecture Basics] --> B[SOLID Principles]
B --> C[Domain-Driven Design]
C --> D[Testing Strategies]
D --> E[Performance Optimization]
E --> F[Security Implementation]
F --> G[Microservices Patterns]
G --> H[DevOps & Deployment]
- Check the documentation: Most answers are in the guides
- Review examples: Look at existing implementations
- Use debugging: Step through the code to understand flow
- Search Stack Overflow: Use specific technology tags
- Ask the community: Join .NET Discord or Reddit
- Follow coding standards: Use the existing patterns
- Write tests: All new code should have tests
- Update documentation: Keep docs in sync with code changes
- Performance considerations: Consider impact of changes
- Backward compatibility: Don't break existing APIs
- Follows Clean Architecture principles
- Implements SOLID principles correctly
- Has comprehensive unit tests
- Includes proper error handling
- Uses async/await correctly
- Follows naming conventions
- Includes XML documentation
- No performance bottlenecks
- Security considerations addressed
Remember: This template is designed to help you build maintainable, scalable applications. Take time to understand the patterns and principles - they'll make you a better developer and your applications more robust.
Happy coding! π