C# Architecture Mastery — Refactoring Legacy ASP.NET Core Apps Toward Clean Architecture (Part 10)

Published: (December 23, 2025 at 04:05 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Introduction

Most ASP.NET Core systems don’t start broken—they become broken.
Legacy systems are rarely the result of bad developers; they are the result of good developers under pressure.

In this Part 10 we’ll cover how senior teams refactor legacy ASP.NET Core applications toward Clean Architecture incrementally, without rewrites, big‑bang refactors, or feature freezes. This is about controlled evolution, not demolition.

Never rewrite what you can reshape.
Rewrites fail because business logic is poorly understood, edge cases are undocumented, and delivery stops.

Clean Architecture refactoring is about creating seams, not perfection.

Identifying Problem Areas

Before touching code, look for:

  • Fat controllers
  • God services
  • DbContext leaks
  • Untestable logic
  • Fear of change

These indicate where to start, not what to redesign wholesale.

Protect Behavior with Characterization Tests

// Characterization test
[Fact]
public async Task CreateOrder_Current_Behavior_Is_Preserved()
{
    var response = await client.PostAsJsonAsync("/orders", request);
    response.StatusCode.Should().Be(HttpStatusCode.OK);
}

These tests:

  • Capture current behavior
  • Prevent accidental regressions
  • Provide safety for refactoring

They don’t need to be pretty.

Refactoring Steps

Extract Use Cases

Legacy controllers often contain business rules, data access, and mapping logic.
Refactor by extracting use cases instead of redesigning everything.

Before

public IActionResult Create(OrderDto dto) { /* everything */ }

After

public IActionResult Create(OrderDto dto)
{
    _useCase.Execute(dto);
    return Ok();
}

Create an Application Layer

Application/
 └─ Orders/
     └─ CreateOrderUseCase.cs

Controllers become adapters that delegate to the application layer.

Introduce Repository Interfaces

Do not remove EF Core immediately. Instead:

  1. Introduce repository interfaces.
  2. Wrap DbContext access.
  3. Move EF usage outward, creating a migration seam.
public interface IOrderRepository
{
    Task SaveAsync(Order order);
}

Improve the Domain Model

Legacy domains are often anemic. Start small:

  • Value objects
  • Invariants
  • Behavior‑rich entities

You don’t need a “perfect” domain—just better boundaries than yesterday.

Refactoring Rules

  • Dependencies must always point inward.
  • Move interfaces inward, implementations outward.
  • Use DI to wire the system at the edge.

What to Avoid

  • Big‑bang rewrites
  • Framework purges
  • Premature abstractions
  • Over‑modeling the domain
  • Freezing feature work

Refactoring must coexist with delivery.

Winning Indicators

You’re winning when:

  • Tests become easier to write
  • Controllers shrink
  • Logic moves inward
  • EF Core becomes less visible
  • Changes feel safer
  • Architecture improves gradually

Conclusion

Clean Architecture is not a destination; it is a direction. Legacy systems don’t need perfection—they need momentum in the right direction. Refactor for safety and keep the system moving forward.

Written by Cristian Sifuentes — helping teams modernize legacy .NET systems without rewrites, fear, or lost velocity.

Back to Blog

Related posts

Read more »