Publishing Every 3 Weeks · 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 10 · PUBLISHED
Live Data

LINQ Pitfalls — The Query That Ran Twice

Eight lines, reads like English, passed review — and for 400 claims it called the eligibility service 801 times. The variable wasn't holding claims. It was holding instructions for going to find them.

EligibilityEndpoint.cs
// ❌ Any() + Count() + foreach = three passes, 801 calls.
var eligible = claims.Where(c => _eligibility.IsCovered(c));

// ✓ One pass, right here. 400.
var eligible = claims
    .Where(c => _eligibility.IsCovered(c))
    .ToList();
POST 09 · PUBLISHED
Live Performance

Entity Framework Core Performance — Stop Your ORM From Killing the Database

It returned in 40ms on your machine. In production it takes nine seconds and fires 2,001 queries. Nobody wrote 2,001 queries — EF Core did, because your local database has five rows in it and has been lying to you since day one.

ClaimsController.cs
// ❌ One round trip per claim. Six was all you saw in dev.
foreach (var id in claimIds)
    claims.Add(await _context.Claims.FindAsync(id, ct));

// ✓ One query. Same count at five rows and two million.
var claims = await _context.Claims
    .Where(c => claimIds.Contains(c.Id))
    .ToListAsync(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
11 📋 The Rules File — Governing AI Before It Writes a Line AI Oct 20 ~11 min 13 ✂️ What .NET 11 Deletes From Your Codebase .NET Nov 10 ~13 min 12 💳 Retry Safely — Idempotency Keys and the Double Charge Resilience Dec 1 ~11 min 14 🎄🎁 Naughty and Nice — What Your Dependency Tree Brought You Production Dec 22 ~10 min 15 💸 Proper Prompting Prevents Paying for Padding AI Jan 12 ~10 min 16 👻 Background Services That Die Quietly Production Feb 2 ~11 min 17 🎛️ The Options Pattern — The Three Interfaces Nobody Explains Configuration Feb 23 ~10 min 18 🐃 Caching Without the Stampede Performance Mar 16 ~11 min 19 🌀 The Doom Loop — When Your Assistant Goes in Circles AI Apr 6 ~10 min 20 🧪 Integration Tests That Touch the Real Thing Testing Apr 27 ~12 min