Miljoenen regels .NET Framework-code vormen de ruggengraat van bedrijfskritische systemen in organisaties wereldwijd. Met een gerichte aanpak moderniseer je een legacy .NET-landschap stapsgewijs, zonder de risico's en kosten van een complete herschrijving.
Waarom Nu Moderniseren?
.NET Framework 4.8 is de laatste versie van het klassieke .NET Framework. Het ontvangt nog wel beveiligingsupdates via Windows, maar krijgt geen nieuwe runtime-functies meer. Ondertussen biedt .NET 10, de huidige Long-Term Support (LTS)-release, duidelijke voordelen:
- Prestaties: .NET 10 is 2 tot 5 keer sneller dan .NET Framework 4.8 op gangbare workloads dankzij JIT-verbeteringen,
Span<T>en hardware-intrinsics. - Cloud-native mogelijkheden: Native containerondersteuning, minimale API's, ingebouwde OpenTelemetry en directe integratie met Kubernetes en Azure.
- Langdurige ondersteuning tot november 2028: Een stabiel platform waarop je jaren vooruit kunt.
- Moderne tooling: C# 13, NativeAOT, broncodegeneratoren, verbeterde analyzers en een vlottere ontwikkelervaring.
Op .NET Framework blijven brengt extra onderhoudskosten met zich mee: tragere builds, moeilijker te vinden ontwikkelaars en het ontbreken van Linux-containers. Grote herschrijvingen brengen echter een hoog risico op vertraging en budgetoverschrijding met zich mee. Incrementele modernisering biedt hier een werkbaar alternatief.
Het Strangler Fig-patroon
Het Strangler Fig-patroon, vernoemd naar een vijgenboom die om een gastboom groeit en deze geleidelijk vervangt, is een bewezen methode om legacysystemen zonder downtime te vernieuwen.
De aanpak bestaat uit vijf stappen:
- Identificeer een afgebakend onderdeel in het legacysysteem.
- Bouw de moderne vervanger in .NET 10 naast het bestaande systeem.
- Stuur het verkeer voor die route door naar de nieuwe implementatie.
- Verwijder de oude code zodra de nieuwe service stabiel draait.
- Herhaal dit tot het legacysysteem volledig is uitgefaseerd.
In de praktijk plaats je een reverse proxy voor de bestaande applicatie. Deze proxy stuurt specifieke paden naar nieuwe .NET 10-services, terwijl het oude systeem de overige verzoeken blijft afhandelen.
Routering met YARP
Microsofts YARP (Yet Another Reverse Proxy) past goed bij dit patroon. Het draait als een ASP.NET Core-applicatie en routeert verkeer op basis van URL-paden:
// appsettings.json - YARP Strangler Fig-configuratie
{
"ReverseProxy": {
"Routes": {
"new-orders-route": {
"ClusterId": "new-orders-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}"
}
},
"legacy-fallback": {
"ClusterId": "legacy-cluster",
"Match": {
"Path": "{**catch-all}"
},
"Order": 100
}
},
"Clusters": {
"new-orders-cluster": {
"Destinations": {
"destination1": { "Address": "https://new-orders-service:8080" }
}
},
"legacy-cluster": {
"Destinations": {
"destination1": { "Address": "https://legacy-app:80" }
}
}
}
}
}
Met deze configuratie handelt de nieuwe .NET 10-service alle verzoeken op /api/orders/* af. Alle overige routes gaan direct naar de legacy-omgeving, zonder dat gebruikers of externe clients de wijziging merken.
API's Extraheren: Van Monoliet naar Service
Een praktische eerste stap is het verplaatsen van endpoints uit een WebForms- of WCF-applicatie naar een losse ASP.NET Core-service.
De Legacy-code
Hier is een traditionele WCF-service die klantgegevens ophaalt via ADO.NET:
// Legacy WCF Service - CustomerService.svc.cs (.NET Framework)
[ServiceContract]
public class CustomerService
{
public CustomerDto GetCustomer(int id)
{
using var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Default"].ConnectionString);
conn.Open();
var cmd = new SqlCommand("EXEC sp_GetCustomer @Id", conn);
cmd.Parameters.AddWithValue("@Id", id);
using var reader = cmd.ExecuteReader();
if (!reader.Read())
return null;
return new CustomerDto
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Email = reader.GetString(2)
};
}
}
De Moderne Vervanger
Deze functionaliteit kan worden overgezet naar een minimale ASP.NET Core 10 API, waarbij de bestaande stored procedure ongewijzigd blijft:
// Modern ASP.NET Core 10 - Program.cs
using Microsoft.Data.SqlClient;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedScoped<SqlConnection>("db", (sp, key) =>
new SqlConnection(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
app.MapGet("/customers/{id:int}", async (int id, [FromKeyedServices("db")] SqlConnection conn) =>
{
await conn.OpenAsync();
await using var cmd = new SqlCommand("EXEC sp_GetCustomer @Id", conn);
cmd.Parameters.AddWithValue("@Id", id);
await using var reader = await cmd.ExecuteReaderAsync();
if (!await reader.ReadAsync())
return Results.NotFound();
return Results.Ok(new CustomerDto(
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2)
));
});
app.Run();
Zo vernieuw je de servicelaag zonder direct het datamodel te hoeven aanpassen.
Migratiepaden voor Veelvoorkomende Componenten
Onderstaande tabel toont geschikte vervangers voor bekende .NET Framework-onderdelen:
| Legacy .NET Framework | .NET 10 Alternatief | Aandachtspunten |
|---|---|---|
| WCF (SOAP) | gRPC, ASP.NET Core Web API of CoreWCF | Gebruik CoreWCF als clients SOAP vereisen; kies anders voor gRPC of REST |
| ASP.NET MVC 5 / Web API 2 | ASP.NET Core Controllers of Minimal API's | Controllers zijn eenvoudig over te zetten; Minimal API's bieden minder boilerplate |
| Entity Framework 6 | Entity Framework Core 10 of Dapper | EF Core heeft gewijzigde functionaliteit; query's zorgvuldig valideren |
| MSMQ | Azure Service Bus, RabbitMQ of MassTransit | Biedt direct de mogelijkheid om messaging te ontkoppelen |
| ASP.NET WebForms | Blazor, Razor Pages of een modern frontend | Geen directe migratie mogelijk; de presentatielaag moet opnieuw worden opgebouwd |
| Global.asax en HttpModules | ASP.NET Core Middleware | Sluit conceptueel goed aan en is eenvoudig te herschrijven |
Databaseoverwegingen
Bij modernisering is de database vaak een gevoelig punt. Het databaseschema kan in veel gevallen intact blijven tijdens het omzetten van de applicatielaag.
Voorlopig behouden
- Bestaande stored procedures die geteste bedrijfslogica bevatten.
- Het databaseschema, zodat de applicatielaag eerst stabiel kan worden getrokken.
- Bestaande indexen en queryplannen.
Wat je wel moderniseert
- Verbindingsbeheer: Vervang verbindingsstrings in
web.configdoor configuratie via omgevingsvariabelen of Azure Key Vault. - Gegevenstoegang: Vervang handmatige ADO.NET-code door Dapper of EF Core met asynchrone methoden.
- Langlopende queries: Geef een
CancellationTokendoor door de hele aanroepketen, zodat applicatieservers en Kubernetes-pods netjes kunnen afsluiten.
// Moderne gegevenstoegang met Dapper - behoudt bestaande stored procedures
public class CustomerRepository(IConfiguration configuration)
{
private readonly string _connectionString = configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Default connection string not found.");
public async Task<CustomerDto?> GetByIdAsync(int id, CancellationToken ct = default)
{
await using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync(ct);
return await connection.QuerySingleOrDefaultAsync<CustomerDto>(
new CommandDefinition(
"EXEC sp_GetCustomer @Id",
parameters: new { Id = id },
cancellationToken: ct
)
);
}
}
CI/CD-modernisering
Het vervangen van legacy MSBuild- of TFS-builds door de cross-platform dotnet CLI maakt snelle Linux-builds mogelijk in GitHub Actions of Azure DevOps Pipelines.
# .github/workflows/build.yml - Moderne .NET 10 CI-pipeline
name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Herstel afhankelijkheden
run: dotnet restore
- name: Bouw
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --configuration Release
- name: Publiceer
run: dotnet publish --no-build --configuration Release -o ./publish
- name: Bouw Docker-image
run: docker build -t myapp:${{ github.sha }} .
Observeerbaarheid met OpenTelemetry
Oudere applicaties schrijven logs vaak naar lokale bestanden op schijf. In containers is gecentraliseerde telemetrie noodzakelijk. .NET 10 biedt ingebouwde OpenTelemetry-ondersteuning voor tracing, metrics en logging:
// Program.cs - OpenTelemetry toevoegen aan een .NET 10-applicatie
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddOtlpExporter(otlp => otlp.Endpoint = new Uri("http://otel-collector:4317")))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());
builder.Logging.AddOpenTelemetry(logging => logging
.AddOtlpExporter());
var app = builder.Build();
Bepalen Wat Je Niet Herschrijft
Niet ieder onderdeel van het systeem hoeft direct overgezet te worden. Enkele vuistregels:
- Stabiele modules met weinig wijzigingen: Onderdelen die al geruime tijd betrouwbaar functioneren en geen nieuwe features vereisen, kunnen geïsoleerd blijven draaien.
- Sterk gekoppelde onderdelen: Splits eerst de verantwoordelijkheden voordat je de code migreert.
- Niet-geteste code: Schrijf eerst integratietests om het huidige gedrag vast te leggen.
- Laag-prioritaire beheerschermen: Focus eerst op kernprocessen en drukbezochte API's.
Gefaseerde Aanpak
| Fase | Typische doorlooptijd | Focus | Risico |
|---|---|---|---|
| 1. Beoordeling | 2 tot 4 weken | Upgrade Assistant-analyse, afhankelijkheden en testdekking in kaart brengen | Laag |
| 2. Fundament | 3 tot 5 weken | YARP-proxy inrichten, CI/CD opzetten, eerste .NET 10-service opleveren | Laag |
| 3. API's Extraheren | Doorlopende sprints | Stapsgewijs bounded contexts extraheren met het Strangler Fig-patroon | Gemiddeld (beheerst via feature flags) |
| 4. Gegevenstoegang en Telemetrie | Parallel aan fase 3 | Data-toegang moderniseren en OpenTelemetry activeren per geëxtraheerde service | Gemiddeld |
| 5. Oude Componenten Uitfaseren | Doorlopend | Legacy-onderdelen uitschakelen zodra de nieuwe services stabiel draaien | Laag |
Ondersteuning door Neneos
Bij Neneos is .NET-ontwikkeling een van onze kernactiviteiten. We helpen teams bij de overstap van .NET Framework-monolieten naar moderne, cloud-native .NET 10-architecturen op Kubernetes.
- Architectuurbeoordeling: We analyseren de codebase en stellen een concreet moderniseringsplan op met prioriteiten en risicoschattingen.
- Hands-on implementatie: Onze engineers werken mee in het team om het Strangler Fig-patroon stapsgewijs in te richten.
- CI/CD en Cloud Platform: We combineren .NET-modernisering met onze Kubernetes- en cloudkennis voor een solide delivery-pipeline.
- Kennisoverdracht: We borgen de keuzes en werkwijzen binnen het eigen team.
Wil je sparren over jouw applicatie? Neem contact op voor een oriënterend gesprek over een passende moderniseringsstrategie.