Publishing Bi-Weekly · ASP.NET Core · Design Patterns · Architecture · 20 yrs C#/.NET · cleancsharp.com
Professional Development Patterns

Write .NET code that scales.

20 years of C#/.NET in production — world-class healthcare companies, national-scale real estate platforms, AI-assisted engineering workflows. The patterns here are the ones that actually held up. No toy examples. Just what works at scale.

Michael O'Hara
Michael O'Hara · writes here
20 YRS · C# / .NET / ASP.NET
OrderController.cs WebApi Controllers main · 18 lines · UTF-8
1// ❌ The wrong way — you wrote your own dependency.
2public class OrderController : ControllerBase
3{
4    private readonly OrderService _service = new OrderService();
5}
6
7// ✓ The clean way — ask for what you need.
8public class OrderController(IOrderService service) : ControllerBase
9{
10    private readonly IOrderService _service = service;
11
12    [HttpPost]
13    public IActionResult Create(OrderDto dto) =>
14        Ok(_service.Create(dto));
15}
16
17// Program.cs — register once, inject everywhere.
18builder.Services.AddScoped<IOrderService, OrderService>();
● main C# .NET 9 Ln 8, Col 24 UTF-8 LF
$
// LATEST The two most recent posts
POST 01 · PUBLISHED
Live Resilience

When the CRUD Hits the Fan — Building Resilient WebAPI Controllers

Your controller passed every code review. Then Black Friday hit, the payment API went down, and it took the entire site with it. Resilience isn't a feature — it's the difference between degraded and dead.

Program.cs
// ❌ No timeout — a dead dependency drains the pool.
builder.Services
    .AddHttpClient<IPaymentClient, PaymentClient>();

// ✓ Timeout, retry with backoff, circuit breaker —
//   they only work as a set.
builder.Services
    .AddHttpClient<IPaymentClient, PaymentClient>()
    .AddStandardResilienceHandler();
POST 02 · PUBLISHED
Live WebAPI

Building Professional WebAPI Controllers

Your controller is the first code that touches every request and the last code before every response. Endpoints that work fine locally fail in production — here's what separates the two.

OrdersController.cs
// ❌ Entity in, entity out, every answer is 200.
[HttpPost]
public IActionResult Create(Order order) =>
    Ok(_repo.Add(order));

// ✓ DTO at the boundary, the status code the client earned.
[HttpPost]
public async Task<ActionResult<OrderResponse>> Create(
    CreateOrderRequest request, CancellationToken ct) =>
    CreatedAtAction(nameof(Get),
        await _orders.CreateAsync(request, ct));
// MOST READ Top performers all time
POST 03 · PUBLISHED
Live Error Handling

Exception Handling That Survives Production

At 1,500 fake "errors" an hour, your error log is 97% noise — and the one SqlException that matters is buried under exceptions that aren't exceptional. Expected failures are results, not throws.

ClaimService.cs
// ❌ Exceptions as control flow — your log becomes noise.
throw new ClaimNotFoundException(claimId);

// ✓ Expected outcomes are results, not exceptions.
public async Task<Result<Claim>> GetClaimAsync(string id)
{
    var claim = await _repo.FindAsync(id);
    return claim is null
        ? Result<Claim>.Failure($"Claim {id} not found")
        : Result<Claim>.Success(claim);
}
POST 04 · PUBLISHED
Live AI & Workflow

Be the Architect, Not the Typist

Letting AI write your code one method at a time is a step sideways. Direct it at the system, hand it the constraints, and review the design — that's how twenty years of experience compounds with the tools.

PaymentReconciler.cs
// Don't type code. Direct the architect.
//
// Instead of:
//   "Write a method that adds two numbers"
//
// Try:
//   "Add an idempotent reconcile step to the
//    payment pipeline that handles partial refunds
//    without double-crediting the customer."

public sealed class PaymentReconciler
{
    public async Task<ReconcileResult> ReconcileAsync(
        PaymentBatch batch,
        CancellationToken ct)
    {
        // implementation guided by your specification
    }
}
// 02 Everything ALL POSTS · NEWEST FIRST
// UPCOMING What's on the publishing schedule
08 Unit Testing with NSubstitute — Tests That Survive Refactoring Testing Aug 18 14 min 09 Entity Framework Core Performance — Stop Your ORM From Killing the Database Performance Sep 1 ~14 min 10 LINQ Pitfalls — The Query That Ran Twice Data Sep 15 ~10 min 11 The Rules File — Governing AI Before It Writes a Line AI Sep 29 ~11 min 12 Retry Safely — Idempotency Keys and the Double Charge Resilience Oct 13 ~11 min 13 What .NET 11 Deletes From Your Codebase .NET Nov 2026 ~13 min