.NET Framework to .NET 8 Migration | Upgrade Guide

.NET Framework Upgrade: Migration zu .NET 8 und Modernisierung von Legacy-Code
03/2024
Michael F. | Solution Engineer

TL;DR

Dieser .NET Framework to .NET 8 Migration Upgrade Guide zeigt, wie dynamische Service-Auswahl von ASP.NET MVC 4.8 sauber nach .NET 6/7 und .NET 8 migriert wird. Mit Keyed Services vereinfacht .NET 8 die Dependency Injection deutlich und ersetzt Workarounds wie Hilfs-Interfaces oder Factory-Pattern.

Dynamische Service-Auswahl und Keyed Services

In diesem .NET Framework to .NET 8 Migration Upgrade Guide betrachten wir ein bestehendes ASP.NET MVC-Projekt auf Basis von .NET Framework 4.8 und analysieren, wie eine spezielle Service-Implementierung im Zuge der Migration und des Upgrades auf moderne .NET-Versionen – insbesondere .NET 8 – umgesetzt werden kann.

Ausgehend von einem Projekt (hier ein ASP.NET MVC-Projekt im .NET Framework 4.8) wollen wir uns anschauen, wie eine spezielle Implementierung in nachfolgenden .NET-Versionen umgesetzt werden kann oder muss.

Aufgrund einer Anforderung ist es erforderlich, die Services im MVC-Controller dynamisch auszuwählen. In unserem Fall wird anhand eines ausgewählten Landes entschieden, welche Services verwendet werden müssen. In den Services sind jeweils unterschiedliche Import- und Export-Methodiken implementiert.

.NET Framework 4.8: Konstruktor mit Factory-Pattern

1public class ImportController : BaseController
2{
3    private ImportCSVService _ImportCSVService = null;
4    private ExportCSVService _ExportCSVService = null;
5
6    /// <summary>
7    /// Standard-Konstruktor mit Initialisierung der Services (abhängig vom aktiven Land)
8    /// </summary>
9    public ImportController()
10    {
11        if (land == LandEnum.NL)
12        {
13            _ImportCSVService = new ImportViService();
14            _ExportCSVService = new ExportViService();
15        }
16        else if (land == LandEnum.BE)
17        {
18            _ImportCSVService = new ImportV2Service();
19            _ExportCSVService = new ExportV2Service();
20        }
21    }
22
23    public ActionResult Index()
24    {
25        var model = _ImportCSVService.ImportCSV("test.csv");
26        return View(model);
27    }
28}

So viel zur Ausgangssituation. Schauen wir uns nun an, wie wir das Ganze auf neuere .NET-Versionen portieren können.

.NET 6 oder .NET 7: Konstrukt

services.AddScoped<IImportCSVService, ImportV1Service>();
services.AddScoped<IExportCSVService, ExportV1Service>();
services.AddScoped<IImportCSVService, ImportV2Service>(); // eine 2. Registrierung funktioniert nicht
services.AddScoped<IExportCSVService, ExportV2Service>();

Spätestens seit .NET 5 verwenden wir generell die Dependency Injection und instanziieren die Services nicht in der Klasse selbst, sondern registrieren die Services mittels ihrer Interfaces und geben sie dann im Konstruktor als Parameter mit.

1public ImportNNController(IImportCSVService importV1Service, IExportCSVService exportV1Service,
2                          IImportCSVService importV2Service, IExportCSVService exportV2Service)
3{
4    if (landEnum == LandEnum.NL)
5    {
6        _ImportCSVService = importV1Service;
7        _ExportCSVService = exportV1Service;
8    }
9    else if (landEnum == LandEnum.BE)
10    {
11        _ImportCSVService = importV2Service;
12        _ExportCSVService = exportV2Service;
13    }
14}

Doch wie registriert und übergibt man jetzt zwei unterschiedliche Services mit demselben Interface?

public interface IImportV1Service : IImportCSVService
{
}

Die einfachste Variante ist die Einführung von entsprechenden Hilfs-Interfaces, die eigentlich keine weitere Funktionalität haben, außer die Services auseinanderhalten zu können. Ein Beispiel für eines der Interfaces:

Nun können wir die Services entsprechend registrieren…

services.AddScoped<IImportV1Service, ImportV1Service>();
services.AddScoped<IExportV1Service, ExportV1Service>();
services.AddScoped<IImportV2Service, ImportV2Service>();
services.AddScoped<IExportV2Service, ExportV2Service>();

… und im Konstruktor verwenden:

1public ImportNNController(IImportV1Service importV1Service, IExportV1Service exportV1Service,
2                          IImportV2Service importV2Service, IExportV2Service exportV2Service)
3{
4    if (landEnum == LandEnum.NL)
5    {
6        _ImportCSVService = importV1Service;
7        _ExportCSVService = exportV1Service;
8    }
9    else if (landEnum == LandEnum.BE)
10    {
11        _ImportCSVService = importV2Service;
12        _ExportCSVService = exportV2Service;
13    }
14}

Dies stellt jedoch nur einen Workaround dar. Man könnte die Services natürlich auch per Reflektion dynamisch generieren – aber dann würden wir an der Dependency Injection vorbei implementieren.

.NET 8: Konstruktor mit Factory-Pattern

Mit .NET 8 geht dies nun etwas einfacher. Das entsprechende Schlüsselwort an dieser Stelle ist „KeyedServices“, welche mit dieser .NET-Version neu eingeführt wurden.

Die Services werden nun etwas anders registriert:

builder.Services.AddKeyedScoped<IImportCSVService, ImportV1Service>("V1");
builder.Services.AddKeyedScoped<IExportCSVService, ExportV1Service>("V1");
builder.Services.AddKeyedScoped<IImportCSVService, ImportV2Service>("V2");
builder.Services.AddKeyedScoped<IExportCSVService, ExportV2Service>("V2");

Hier können wir die eigentlichen Basis-Interfaces verwenden:

1public ImportController([FromKeyedServices("V1")] IImportCSVService importV1Service, 
2                        [FromKeyedServices("V1")] IExportCSVService exportV1Service, 
3                        [FromKeyedServices("V2")] IImportCSVService importV2Service, 
4                        [FromKeyedServices("V2")] IExportCSVService exportV2Service)
5{
6    if (land == LandEnum.NL)
7    {
8        _ImportCSVService = importV1Service;
9        _ExportCSVService = exportV1Service;
10    }
11    else if (land == LandEnum.BE)
12    {
13        _ImportCSVService = importV2Service;
14        _ExportCSVService = exportV2Service;
15    }
16}

Die Strings („V1“ und „V2“) sollten jedoch als Konstante definiert und verwendet werden. Hier könnten wir auch das Enum direkt verwenden, da als Key jedes Objekt verwendet werden kann:

Foto von Michael
Michael F. | Solution Engineer

Mehr zum Thema

Pfeil nach rechts (Verlinkung)
.NET-Ökosystem erklärt: Plattform, SDK, Core, C#, NuGet, Azure und DevOps kompakt und verständlich im Überblick für Entwicklerinnen und Entwickler.
07/2026

.NET: Framework, Plattform, Core? Was steckt eigentlich hinter dem Microsoft-Ökosystem?

Blauer Pfeil nach rechts (Verlinkung)
Erfahren Sie, wie SQL Indizes Datenbankabfragen beschleunigen, Fallstricke vermeiden und die Performance Ihrer Anwendung nachhaltig verbessern.
06/2026

SQL Indizes Datenbankabfragen effizient optimieren

Blauer Pfeil nach rechts (Verlinkung)
LoRaWAN-Geofencing mit .NET 10 und Azure: Architektur, Hysterese-Logik, CQRS, Blazor und Azure Communication Services im Praxis-PoC.
06/2026

LoRaWAN-Geofencing mit .NET 10 und Azure (PoC)

Blauer Pfeil nach rechts (Verlinkung)
Validierung in Blazor mit FluentValidation und Blazilla: Flexible Regeln, asynchrone Prüfungen und wartbare Formularvalidierung.
06/2026

Validierung in Blazor: FluentValidation und Blazilla

Blauer Pfeil nach rechts (Verlinkung)