Umstieg auf .NET Core: Desktop-Anwendungen mit WPF und Windows Forms umstellen
Seite 2: Migration bestehender WPF-Anwendungen auf .NET Core
Das klassische .NET Framework wird zwar nicht mehr weiterentwickelt, aber von Microsoft weiterhin dauerhaft mit Fehlerbereinigungen sowie Sicherheitsupdates versorgt und als Bestandteil von Windows ausgeliefert. Daher ist niemand gezwungen, bestehende WPF-Anwendungen vom klassischen .NET Framework auf .NET Core umzustellen. Es gibt aber durchaus Vorteile im Bereich der Ausführungsgeschwindigkeit, den Verbreitungsoptionen und der Funktionalität, die eine Migration auf .NET Core rechtfertigen können.
Leider steht bisher weder innerhalb noch außerhalb von Visual Studio ein adäquates Migrationswerkzeug samt aussagekräftiger Dokumentation parat. In dem Dokument "Migration WPF apps to .NET Core" beschreibt Microsoft die Migration lediglich an einem Beispiel; eine vollständige Dokumentation der API- und Paketzuschnitt-Änderungen fehlt jedoch.
Auf GitHub findet sich das Microsoft-Projekt Try-Convert. Wie der Name andeutet, handelt es sich um einen Versuch, wie Microsoft auch klarstellt: "Sample tool showing how to build a global tool and also help you convert projects to .NET Core." Die Leistungen der zum Erscheinen dieses Beitrags aktuellen Version v0.2.51601 vom 22.10.2019 des Werkzeugs sind enttäuschend: Es stellt zwar einige XML-Tags der Projektdatei (.csproj) auf die neue, in .NET Core verwendete Struktur um, belässt aber viele andere Tags in der Datei, die in .NET Core nicht mehr zwingend notwendig sind (s. Abb. 1).
Auch fällt in dem Screenshot auf, dass immer noch "net48" (also das klassische .NET Framework in der aktuellen Version 4.8) referenziert wird. Im weiteren Verlauf der Projektdatei finden sich dann auch viele NuGet-Paketreferenzen, die in der Core-Welt entweder nicht mehr notwendig oder sogar möglich sind wie die Einstellungen für Click-Once-Deployment. Andere in .NET Core notwendige Referenzen, beispielsweise auf das Paket Microsoft.Windows.Compatibility, fehlen hingegen. Auch in der .NET-Core-Welt notwendige Tags wie <UseWPF>true</UseWPF> sucht man vergebens.
Tatsächlich liefert das Tool im konkreten Testfall eine Projektdatei mit 195 Zeilen, von denen effektiv aber 29 Zeilen reichen würden. Da Try-Convert weder Kommentare noch Leerzeilen erstellt, muss man diese zum quantitativen Vergleich aus Listing 1 herausrechnen – wo sie auch lediglich der Übersichtlichkeit dienen.
Einige wenige Tests mit Try-Convert genĂĽgen, um zu dem Urteil zu gelangen, dass das Werkzeug auf dem derzeitigen Stand nicht praxistauglich ist.
Eine manuelle Migration einer WPF-Anwendung vom klassischen .NET Framework zu .NET Core besteht aus den folgenden acht Schritten:
1. Zunächst sollten alle Paketreferenzen in dem klassischen .NET-Framework-Projekt per NuGet Package Manager auf den aktuellen Stand gebracht werden.
2. Dann gilt es, das NuGet-Paketverwaltungsverfahren umzustellen. Klassische .NET-Projekte besitzen eine Datei packages.config, in der nicht nur die referenzierten Pakete, sondern auch die transitiven Referenzen aufgefĂĽhrt sind. Das Umstellen auf PackageReference-Tags innerhalb der .csproj-Datei (s. Listing 1) wird durch neuere Visual-Studio-Versionen unterstĂĽtzt per KontextmenĂĽbefehl Migrate packages.config to PackageReference auf einer packages.config-Datei (s. Abb. 2).
3. Dann sind für jedes Projekt alle Code- und Ressourcendateien in ein neues Verzeichnis zu kopieren. Dabei sollte man nur die Dateien kopieren, die tatsächlich benötigt werden. In Kundenprojekten begegnen einem immer wieder Programmcodedateien in Projektverzeichnissen im Dateisystem, die einst aus dem Projekt ausgeschlossen wurden, aber im Dateisystem noch weiter existieren. Mit dem Umkopieren dieser Dateien in den neuen Projektordner erwachen diese Dateien ungewollt wieder zum Leben.
4. Nun legt man fĂĽr jedes Projekt eine .NET-Core-Projektdatei neu an.
5. Hier sind zunächst die Projekt- und direkten Assembly-Referenzen wieder einzutragen.
6. Dann erweitert man die NuGet-Referenzen: Meist ist eine Referenz auf das NuGet-Paket Microsoft.Windows.Compatibility zu ergänzen. Dies sollte die Anzahl der Compiler-Fehlermeldungen bezüglich nicht gefundener Typen schon deutlich reduzieren. Wenn weitere Typen nicht gefunden werden, ist eine Prüfung des .NET API Browser angeraten. Gegebenenfalls ist durch eine allgemeine Internet-Suche zu prüfen, in welchem NuGet-Paket die Typen tatsächlich stecken.
7. Nun gilt es, fĂĽr Dateien, die keine Codedateien sind (Grafiken, Sound, EDMX-Dateien fĂĽr Entity Framework, XSD-Dateien fĂĽr typisierte Dataset etc.), die Build-Action wieder korrekt zu setzen.
8. Alle Fehlermeldungen, die jetzt noch auftreten, erfordern Programmcodeanpassungen für entfallene APIs. Im Idealfall gibt es einen einfachen Ersatz, im schlimmsten Fall sind es gänzlich entfallene oder geänderte APIs (vgl. "Umstieg auf .NET Core – Teil 1"). Der .NET Portability Analyzer hilft, entfallene APIs zu erkennen. Er verrät aber leider in vielen Fällen nicht, was unter .NET Core die Lösung für entfallene APIs ist. Da es auch in der Microsoft-Dokumentation keine Liste gibt, bleibt nur, das Internet zu durchforsten, in der Hoffnung, dass andere Benutzer bereits eine geeignete Lösung gefunden haben.
Die Schritte 3 bis 6 der beschriebenen Vorgehensweise kommen einer Klickorgie gleich. Diese lässt sich jedoch mit dem vom Autor vorgeschlagenen PowerShell-Skript (s. Listing im Anhang) automatisiert abarbeiten. Das Skript ist allerdings keine allgemeingültige Lösung für alle Projekte, sondern bezieht sich jeweils auf eine spezielle Projektstruktur. Der Großteil des Programmcodes ist aber wiederverwendbar (Unterroutine Migrate-Project mit diversen Hilfsfunktionen und XML-Vorlagen) und kann so als Grundlage für ein eigenes projektspezifisches Migrationsskript hilfreich sein. Das Skript demonstriert am Ende auch die Ausführung von UI-Tests mit dem Kommandozeilenbefehl dotnet test, die den Erfolg der Migration prüfen.
API- und Verhaltensänderungen
Entfallene und geänderte APIs aus Punkt 8 der obigen Auflistung sind der große Unsicherheitsfaktor bei der Migration. Es gibt keine vollständige Liste dieser APIs, die eine Programmcode-Änderung erfordern. Manchmal stehen Hinweise in dem Ausgabebericht des Portability-Analyzers, gelegentlich auch in der Klassendokumentation. Meist findet man aber die Änderungen nur in Diskussionen auf GitHub oder in Foreneinträgen bei Stack Overflow und vergleichbaren Portalen.
Beispiele für entfallene oder geänderte APIs:
- .NET Core-Projekte verwenden standardmäßig keine AssemblyInfo.cs für Metadaten wie die Versionsnummern von Assembly, Datei und Produkt. Sie speichern diese in der .csproj-Datei. Findet sich dort kein Eintrag, ist die Versionsnummer im Standard 1.0. Dies kann zu gravierenden Verhaltensänderungen führen. Mit dem manuellen Zusatz
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>in der .csproj-Datei lässt sich die alte AssemblyInfo.cs wieder aktivieren. - Das Sternchen als Platzhalter für das automatische Hochzählen in Versionsnummern wie bei [assembly: AssemblyVersion("2.8.6.*")] ist im Standard nicht mehr erlaubt. Das alte Verhalten lässt sich mit dem Eintrag
<Deterministic>false</Deterministic>in der .csproj-Datei reaktivieren. - Während klassische .NET-Framework-Anwendungen beim Programmstart die Windows-Registrierungsdatenbank nach installierten Datenbankprovidern durchsuchen, ist dieses Verhalten in .NET Core – auch unter Windows – nicht mehr vorgesehen. Datenbankprovider, die man via ADO.NET verwenden will, sind daher bei Programmstart im Programmcode zu registrieren:
System.Data.Common.DbProviderFactories.RegisterFactory("System.Data.SqlClient", System.Data.SqlClient.SqlClientFactory.Instance);
- Unit-Tests können unter .NET Core nicht mehr per
System.Configuration.ConfigurationManager.AppSettings["abc"]Daten aus der Datei app.config verwenden. Stattdessen ist folgender sperriger Befehl erforderlich:
((AppSettingsSection)ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).GetSection("appSettings")).Settings["abc"].Value.