.NET Patterns: Builder, Exceptions & CLI Setup
Fluent Builder Pattern for Unit Tests
Section titled “Fluent Builder Pattern for Unit Tests”The classic constructor approach becomes unreadable with many parameters. A User object with 5+ properties forces you to count arguments, guess order, and maintain brittle tests.
The Fluent Builder reads like English:
var user = new UserBuilder() .WithName("John Doe") .WithEmail("john.doe@example.com") .WithAge(30) .Build();Each method returns this, enabling method chaining. Your test intent becomes crystal clear—no squinting at constructor arguments.
Classic vs. Fluent Comparison
Section titled “Classic vs. Fluent Comparison”| Aspect | Classic Constructor | Fluent Builder |
|---|---|---|
| Readability | new User("John Doe", "john@...", 30, "Admin") | Chained readable calls |
| Parameter Order | Easy to mess up | Self-documenting |
| Test Maintenance | Breaks when signature changes | Flexible, optional params |
| Complex Objects | Constructor telescoping chaos | Graceful, modular |
Builder Implementation
Section titled “Builder Implementation”public class UserBuilder{ private string _name = "Guest"; private string _email = "guest@example.com"; private int _age = 18;
public UserBuilder WithName(string name) { _name = name; return this; }
public UserBuilder WithEmail(string email) { _email = email; return this; }
public UserBuilder WithAge(int age) { _age = age; return this; }
public User Build() => new User(_name, _email, _age);}Custom Exceptions
Section titled “Custom Exceptions”Native Exception is too generic. Your API handler can’t distinguish a 404 from a 500, and logs become noise. Custom exceptions carry HTTP status codes and error codes.
Three Exception Patterns
Section titled “Three Exception Patterns”1. Abstract Base Class — Clear inheritance, centralized logic:
public abstract class CustomExceptionBase : Exception{ public string CustomMessage { get; } public HttpStatusCode StatusCode { get; } public string ErrorCode { get; }
protected CustomExceptionBase(string message, string errorCode, HttpStatusCode statusCode) { CustomMessage = message; ErrorCode = errorCode; StatusCode = statusCode; }}2. Const Strings for Error Codes — Centralized, reusable:
public static class ErrorCodes{ public const string NotFound = "NOT_FOUND"; public const string Unauthorized = "UNAUTHORIZED"; public const string Conflict = "CONFLICT";}3. Specific Exception Types — Type-safe handling:
public class NotFoundException : CustomExceptionBase{ public NotFoundException(string message) : base(message, ErrorCodes.NotFound, HttpStatusCode.NotFound) { }}
public class UnauthorizedException : CustomExceptionBase{ public UnauthorizedException(string message) : base(message, ErrorCodes.Unauthorized, HttpStatusCode.Unauthorized) { }}Exception Patterns Comparison
Section titled “Exception Patterns Comparison”| Pattern | Pros | Cons |
|---|---|---|
| Abstract Base | DRY, enforces structure | Requires all exceptions inherit |
| Generics (T) | Type-safe, flexible | More complex syntax |
| Const Strings | Reusable error codes | Need manual mapping |
.NET Project Structure with CLI
Section titled “.NET Project Structure with CLI”Professional projects separate source and tests for clarity. dotnet CLI automates this setup.
Starting Fresh
Section titled “Starting Fresh”# Create Aspire starter template with Redisdotnet new aspire-starter --use-redis-cache --output AspireSample
cd AspireSample
# Create directory structuremkdir src tests
# Move projects into organized foldersmv ExpenseTracker.ApiService src/mv ExpenseTracker.Tests tests/
# Recreate solution (new structure)rm AspireSample.slndotnet new sln -n AspireSample
# Register projects to solutiondotnet sln AspireSample.sln add src/ExpenseTracker.ApiService/ExpenseTracker.ApiService.csprojdotnet sln AspireSample.sln add tests/ExpenseTracker.Tests/ExpenseTracker.Tests.csproj
# Build and verifydotnet restore && dotnet buildDirectory Layout
Section titled “Directory Layout”AspireSample/├── src/│ ├── ExpenseTracker.ApiService/│ │ └── ExpenseTracker.ApiService.csproj│ └── ExpenseTracker.Shared/├── tests/│ └── ExpenseTracker.Tests/│ └── ExpenseTracker.Tests.csproj└── AspireSample.slnNext steps: Implement Fluent Builders in your test utilities, define custom exceptions in a shared layer, then scaffold your project with dotnet new to enforce structure from day one.