If I walked into a company with a tangled C# or .NET service architecture, I wouldn’t assume the first problem is the code itself.

I’d look at the boundaries.

In most legacy systems, risk doesn’t come from the age of the language. It comes from years of small decisions that gradually mixed communication, serialization, business logic, security, retries, and infrastructure concerns into the same execution paths.

Over time, service calls start carrying hidden assumptions:

That’s when changes become expensive. Not because engineers can’t modify the code, but because nobody can confidently predict the blast radius.

That’s the real modernization problem.

What these systems usually look like

The patterns are surprisingly consistent across large legacy .NET systems.

A service directly calls another service:

varpayload=JsonConvert.SerializeObject(order);

awaithttpClient.PostAsync(
"<http://service-b/process>",
newStringContent(payload,Encoding.UTF8,"application/json")
);

And the receiving service:

varbody=awaitnewStreamReader(Request.Body).ReadToEndAsync();

varorder=JsonConvert.DeserializeObject<OrderDto>(body);

if (order.CustomerType=="VIP")
{
ApplyDiscount(order);
}

Save(order);

Individually, none of this looks terrible.