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, high-volume microservices. 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 08 · PUBLISHED
Live Async

Async/Await — Don't Block Your Threads

Gilligan called .Result on the Professor's async radio and blocked the entire island. Your API does the same thing — and the thread-pool exhaustion only shows up when production traffic hits.

OrderController.cs
// ❌ Blocks the thread — exhausts the pool under load.
public IActionResult GetOrder(int id) =>
    Ok(_repo.GetAsync(id).Result);

// ✓ Awaits properly — frees the thread for other work.
public async Task<IActionResult> GetOrder(int id) =>
    Ok(await _repo.GetAsync(id));

// And the call site:
await using var db = await _factory.CreateAsync();
var customer = await db.Customers
    .Where(c => c.Id == id)
    .SingleAsync(ct);
POST 07 · PUBLISHED
Live Testing

Unit Testing with NSubstitute — Tests That Survive Refactoring

The Professor could build a radio from a coconut, but his tests checked which coconut was used instead of whether the radio worked. Your test suite makes the same mistake — and 47 tests break every time you refactor.

OrderServiceTests.cs
// Test behaviour — not which functions got called.
[Fact]
public async Task Returns_Discount_For_VIP_Customer()
{
    var pricing = Substitute.For<IPricingService>();
    pricing.GetDiscount(Arg.Any<Customer>())
           .Returns(0.15m);

    var service = new OrderService(pricing);
    var total = await service.GetTotal(vipCustomer, 100m);

    Assert.Equal(85m, total);
}
// MOST READ Top performers all time
POST 01 · PUBLISHED
Live Dependency Injection

Dependency Injection in ASP.NET Core — Stop Using new

If your controller creates its own dependencies, your tests must too — including real database connections. A test that should take 50 ms now takes 2 seconds and fails if the database is offline.

OrderController.cs
// Constructor injection — clean & testable.
public class OrderController(IOrderService service) : ControllerBase
{
    [HttpPost]
    public IActionResult Create(OrderDto dto) =>
        Ok(service.Create(dto));
}

// Program.cs — register once, inject everywhere.
builder.Services.AddScoped<IOrderService, OrderService>();
POST 03 · 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
09 LINQ Pitfalls — Multiple Enumeration and Deferred Execution Data Apr 2026 ~10 min 10 EF Core Best Practices — Don't Let Your ORM Bite You Data Apr 2026 ~12 min 11 API Versioning Without Breaking Clients Backend May 2026 ~9 min