.NET Core 3.0 (einschließlich Entity Framework Core 3.0 und ASP.NET Core 3.0) sind am 23. September 2019 erschienen. Wer diese Versionen intensiv in der Praxis einsetzt, wird einige Unzulänglichkeiten bemerkt haben. Man könnte spotten: Dass diese Version schon im September erschienen ist, liegt nicht daran, dass sie fertig war, sondern daran, dass Microsoft im Mai 2019 das Erscheinen für die .NET Conf 2019 am 23. September verkündet hatte.
.NET Core 3.1 mit Entity Framework Core 3.1 und ASP.NET Core 3.1 sollten laut der Ankündigung vom Mai schon im November 2019 erscheinen. Das hat Microsoft dann aber auf 3. Dezember 2019 vertagt, was auch gut war.
Bugfixes
Die 3.0er Versionen enthielten eine Reihe von kleinen und größeren Problemen, zum Beispiel bei der Benutzerverwaltung und Authentifizierung in ASP.NET Core Blazor Server. Während man mit Blazor Server eine Single Page Application (SPA) erstellen kann [1], basieren die von Microsoft in ASP.NET Core Identity gelieferten Webseiten zur Benutzerverwaltung und Authentifizierung noch auf ASP.NET Core Razor Pages, also einer klassische Multi Page Application (MPA). Solange man in ASP.NET Core 3.0 diese Webseiten im Standardlayout von Microsoft verwendete, funktionierte die Integration zwischen Razor Pages und Blazor Components gut. Sobald man aber zur Anpassung des Layouts die Razor Pages mit Add/New Scaffolded Item/Identity explizit als Templates in das Projekt hineingenerieren lies, sah man die Menüzeile plötzlich doppelt. Microsoft hatte die Layoutseiten falsch verschachtelt; der Entwickler musste es selbst lösen. In ASP.NET Core 3.1 ist das gelöst.
Einen echten Showstopper gab es in .NET Core 3.0, wenn man eine klassische .NET Framework-basierte Anwendung (z. B. Windows Forms oder Windows Presentation Foundation), die noch mit typisierten DataSets (.xsd-Dateien) arbeitet, auf .NET Core migrieren wollte. Das war in der Zeit vor Entity Framework (gerade in Windows Forms- und WPF-Anwendungen) üblich und kommt daher auch heute noch in einigen Projekten vor. Typisierte DataSets sollten zwar in .NET Core 3.0 möglich sein, dem Autor dieses Beitrags fiel in einem entsprechenden Projekt aber auf, dass der von den .xsd-Dateien generierte Programmcode zur Laufzeit eine Null Reference Exception auslöst und zwar in der Methode SqlParameterCollection.Add(). Obwohl der Autor das am 2. September 2019 via GitHub gemeldet hatte und ein Microsoft-Entwickler am 14. September 2019 den Fehler behoben hatte, wanderte die Fehlerbehebung nicht mehr in die Version der System.Data.SqlClient.dll, die mit .NET Core 3.0 ausgeliefert wurde. Dabei fielen drei Dinge auf:
- Der Microsoft-Entwickler musste zugeben, dass es nicht ausreichend Unit-Tests für die System.Data.SqlClient gibt, also nicht gut getestet wurde.
- Es hat wohl auch niemand bei Microsoft ein typisiertes DataSet einmal manuell auf .NET Core 3.0 getestet, denn der Fehler trat in jedem typisiertes DataSet auf.
- Microsoft ist immer noch nicht agil genug, um einen Bugfix, der neun Tage vor dem Release fertig ist, in das Produkt einzubauen, selbst wenn es so ein kritischer Bug ist, der das Funktionieren gänzlich verhindert.
Als Workaround kam übrigens der Vorschlag, doch eine andere Überladung von SqlParameterCollection.Add() zu verwenden. Da aber ja der Aufruf von SqlParameterCollection.Add() im generierten Programmcode lag und dreimal pro Tabelle vorkommt, hätte das einen enormen Aufwand bedeutet, der nach jeder Änderung am DataSet immer wieder neu vollzogen hätte werden müssen. Auch die Anpassung des Codegenerators war keine Option, denn bei typisierten DataSets gab es noch keine Templatesprache wie T4 oder Handlebars, sondern der Codegenerator war noch als reine C#-Klasse mit Namen MSDataSetGenerator implementiert. Die Klasse kann man zwar austauschen, das ist jedoch viel Arbeit.
In diesem Stil gab es noch eine Reihe weiterer Probleme in .NET Core 3.0, Entity Framework Core 3.0 und ASP.NET Core 3.0. Allein das Entity-Framework-Core-Entwicklungsteam rühmt sich, in Version 3.1 noch über 150 Fehler aus Version 3.0 beseitigt zu haben (siehe https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-3-1-and-entity-framework-6-4/ und https://github.com/aspnet/EntityFrameworkCore/milestone/76?closed=1.
Nur wenige neue Features
Das Entity-Framework-Core-Entwicklungsteam schreibt über die Version 3.1: „To this end we have fixed over 150 issues for the 3.1 release, but there are no major new features to announce“ [4]. Dabei ist das gar nicht ganz richtig, denn es gibt schon eine wesentliche Neuerung in Entity Framework Core 3.1 gegenüber Version 3.0: Entity Framework Core 3.1 basiert wieder auf .NET Standard 2.0 und läuft damit nicht nur auf .NET Core, Mono und Xamarin, sondern auch wieder im klassischen .NET Framework und in Universal-Windows-Platform-(UWP-)Apps. Bei Entity Framework Core 3.0 hatte sich das Entwicklungsteam entschlossen, .NET Standard 2.1 zu verwenden. Diesen wird es gemäß Ankündigung von Microsoft nicht mehr für das klassische .NET Framework geben und für die UWP gibt es .NET Standard 2.1 noch nicht. So waren alle Softwareentwickler, die Entity Framework Core 1.x/2.x in .NET Framework oder UWP einsetzen, ausgeschlossen. Zum Glück hat Microsoft diese Fehlentscheidung bei Entity Framework Core 3.1 revidiert. Nicht revidiert hat Microsoft diese Entscheidung aber für ASP.NET Core: Auch die Version 3.1 läuft nicht mehr auf dem klassischen .NET Framework (Abb. 1).
ASP.NET Core 3.1 bietet zumindest einige kleine neue Features gegenüber Version 3.0, vor allem für Blazor. Den Startpunkt einer Blazor-Anwendung kann der Entwickler nun alternativ auch per Tag-Helper <component>
<app> <component type="typeof(App)" render-mode="ServerPrerendered" /> </app>
und nicht nur per Code
<app> @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered)) </app>
festlegen. Code-behind-Dateien in Razor Components können nun auch durch partielle Klassen anstelle von Vererbung realisiert werden. So kann man nun die Code-behind-Klasse statt public class XYModel : ComponentBase, IDisposable { … } auch verkürzt deklarieren: public partial class XY { … }. Dann entfällt in der Razor-Datei das @inherits XYModel.
Wichtig ist, dass der Namensraum in der Code-behind-Datei dann dem automatisch gebildeten Namensraum der Razor-Datei entspricht. Der Namensraum wird aus dem auf Projektebene definierten Wurzelnamensraum und den Ordnernamen gebildet, z. B. ist für eine Razor Component im Ordner /Kunde/Edit mit Wurzelnamensraum ITVisions.Intranet der Namensraum dann ITVisions.Intranet.Kunde.Edit.
Die Listings 1 und 2 zeigen eine Blazor Component mit Code-behind als partielle Klasse. Interessant ist auch, dass nun Dependency Injections nicht mehr doppelt erfolgen müssen wie bei einer Lösung auf Basis von Vererbung [1].
@page "/Vorlage" @inject BlazorUtil Util @inject IJSRuntime JSRuntime @inject NavigationManager NavigationManager @using ITVisions.Blazor <h2>Code-Behind-Vorlage</h2> X: <input type="number" @bind="X" /> Y: <input type="number" @bind="Y" /> <button @onclick="AddAsync">Add x + y</button> Sum: @Sum @code { // Hier wäre auch noch Code möglich, wenn man unbedingt will }
using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using ITVisions.Blazor; using System.Threading.Tasks; namespace Web.Demos.Vorlagen { public partial class CodeBehindVorlage { [Parameter] public decimal X { get; set; } = 1.23m; [Parameter] public decimal Y { get; set; } = 2.34m; public decimal Sum = 0; #region Standard-Lebenszyklus-Ereignisse protected override void OnInitialized() { Util.Log(nameof(CodeBehindVorlage) + ".OnInitialized()"); } protected async override Task OnInitializedAsync() { Util.Log(nameof(CodeBehindVorlage) + ".OnInitializedAsync()"); } protected override void OnParametersSet() { Util.Log(nameof(CodeBehindVorlage) + ".OnParametersSet()"); } protected async override Task OnParametersSetAsync() { Util.Log(nameof(CodeBehindVorlage) + ".OnParametersSetAsync()"); } protected override void OnAfterRender(bool firstRender) { Util.Log(nameof(CodeBehindVorlage) + ".OnAfterRender(firstRender=" + firstRender + ")"); // this.StateHasChanged(); // --> Endlosschleife !!! } protected async override Task OnAfterRenderAsync(bool firstRender) { Util.Log(nameof(CodeBehindVorlage) + ".OnAfterRenderAsync(firstRender=" + firstRender + ")"); } public void Dispose() { Util.Log(nameof(CodeBehindVorlage) + ".Dispose()"); } #endregion #region Reaktionen auf Benutzerinteraktionen public async Task AddAsync() { Sum = X + Y; X = Sum; Util.Log($"{nameof(CodeBehindVorlage)}.Add(). x={X} y={Y} sum={Sum}"); await Util.SetTitle(Sum.ToString()); } #endregion } }
Weitere Neuerungen in ASP.NET Core 3.1 sind die nun mögliche Verhinderung der Standardereignisbehandlung in Razor Components in Blazor
<input value="@eingabe" @onkeypress="KeyHandler" @onkeypress:preventDefault />
und der Ereignisweitergabe in Razor Components in Blazor (Listing 3).
<input @bind="stopPropagation" type="checkbox" /> <div @onclick="OnSelectParentDiv"> <div @onclick="OnSelectChildDiv" @onclick:stopPropagation="stopPropagation"> ... </div> </div> @code { private bool stopPropagation = false; }
Zudem zeigen Blazor-Anwendungen bei Laufzeitfehler nun eine Fehlerleiste für den Benutzer (Abb. 2). Die zu der Fehlerleiste zugehörigen HTML-Tags findet man in einem
in einer Blazor Server App in der Datei Pages/_Host.cshtml und in Blazor Webassembly in der Datei wwwroot/index.html. Den Verweis auf die Developer Tools des Browsers gibt es nur, wenn die Anwendung im Development-Modus läuft.
Neu zusammen mit .NET Core 3.1 erschienen ist auch erstmals die Unterstützung für die Programmiersprache C++/CLI für .NET Core 3.0 und 3.1. Das erfordert Visual Studio 2019 v16.4. In C++/CLI geschriebene .NET Core Assemblies laufen aber (vorerst) nur auf Windows. Auch ein Austausch mit C++/CLI kompilierter Assemblies zwischen .NET Core und .NET Framework ist nicht möglich.
Breaking Changes
Breaking Changes, die verhindern, dass eine 3.0-basierte Anwendung einwandfrei auf Version 3.1 läuft, sollte es gemäß dem Semantic Versioning, dem Microsoft folgt, in .NET Core 3.1, Entity Framework Core 3.1 und ASP.NET Core 3.1 eigentlich gar nicht geben. Aber es gibt sie doch.
Microsoft hat sich entschlossen, einige ältere Windows-Forms-Steuerelemente, die es in .NET Core 3.0 in der .NET Core Windows Desktop Runtime gab, in Version 3.1 auszubauen. Tabelle 1 zeigt die entfallenen Steuerelemente und die zugehörigen ebenfalls entfallenen Klassen sowie den Ersatz, den Microsoft vorschlägt.
Entfallenes Windows Forms-SteuerelementEntfallene zugehörige APIsVon Microsoft empfohlener Ersatz
DataGrid | DataGridCell, DataGridRow, DataGridTableCollection, DataGridColumnCollection, DataGridTableStyle, DataGridColumnStyle, DataGridLineStyle, DataGridParentRowsLabel, DataGridParentRowsLabelStyle, DataGridBoolColumn, DataGridTextBox, GridColumnStylesCollection, GridTableStylesCollection, HitTestType | DataGridView |
ToolBar | ToolBarAppearance | ToolStrip |
ToolBarButton | ToolBarButtonClickEventArgs, ToolBarButtonClickEventHandler, ToolBarButtonStyle, ToolBarTextAlign | ToolStripButton |
ContextMenu | ContextMenuStrip | |
Menu | MenuItemCollection | ToolStripDropDown, ToolstripDropDownMenu |
MainMenu | MenuStrip | |
MenuItem | ToolstripMenuItem |
Auch wenn das ältere, bereits in .NET Framework 1.0 eingeführte Steuerelemente sind, für die es seit dem Jahr 2005 (.NET Framework 2.0) Alternativen gibt, ist es dennoch ärgerlich für einige ältere Projekte, die diese Steuerelemente noch verwenden. Sie liefen ja immer gut im klassischen .NET Framework.
Warum Microsoft die Steuerelemente in Windows Forms für .NET Core noch geändert hat, verrät der Blogeintrag zu .NET Core 3.1: Man will diese Steuerelemente nicht im neuen Windows Forms-Designer für .NET Core einbauen („As we got further into the Windows Forms designer project, we realized that these controls were not aligned with creating modern applications and should never have been part of the .NET Core port of Windows Forms. We also saw that they would require more time from us to support than made sense.“).
Immerhin ist sich Microsoft seiner Schuld, gegen das Semanic Versioning zu verstoßen, bewusst und entschuldigt sich bei den Kunden: „Yes, this is an unfortunate breaking change. You will see build breaks if you are using the controls we removed in your applications. … We should have made these changes before we released .NET Core 3.0, and we appologize for that. We try to avoid late changes, and even more for breaking changes, and it pains us to make this one.“
Der besagte Windows Forms-Designer ist weiterhin nicht fertig. Visual Studio 2019 v16.5 Preview 1 enthält zwar eine Preview-Version des Designers (zu aktivieren in den Optionen, siehe Abb. 3), diese ist aber nicht praxisreif (Abb. 4 und 5).
Einen weiteren Breaking Change findet man in ASP.NET Core 3.1: Microsoft hat das Verhalten von Same-site-Cookies geändert.
Long Term Support
.NET Core 3.1 bekommt von Microsoft Long Term Support (LTS), was aber auch nur eine Unterstützung für drei Jahre bedeutet – nicht die von den Kunden geschätzten 10 Jahre nach dem letzten Erscheinen in Windows wie beim klassischen .NET Framework.
Der Support für .NET Core 3.0 (inkl. ASP.NET Core und Entity Framework Core) endet schon am 3. März 2020, denn das war nur ein Current-Release (auch Maintainance Release genannt), für das es nur drei Monate nach dem Erscheinen der folgenden LTS-Version noch Updates gibt. Zu beachten ist auch, dass der Support für .NET Core 2.2 schon am 23. Dezember 2019 endete. Für Entwickler, die Version 2.2 einsetzen, heißt das dann: Umstellen auf Version 3.1 mit allen Breaking Changes, die es in Version 3.0 [2] bzw. in 3.1 gab. Alternativ geht natürlich auch ein Downgrade auf Version 2.1 inklusive Featureverzicht.
Das trifft besonders schwer Entwickler, die ASP.NET Core 2.2 auf klassischem .NET Framework einsetzen, denn die können nicht auf Version 3.1 hochgehen, sondern müssen auf 2.1 herabstufen – oder ohne Support leben.
Fazit
Das Gute an .NET Core 3.1, ASP.NET Core 3.1 und Entity Framework 3.1 ist, dass Microsoft nun Fehler behoben hat. Das Schlechte ist, dass die Version 3.0 mit diesen Fehlern überhaupt erschienen ist und nicht vertagt wurde, um eine bessere Qualität zu liefern.
Literatur
[1] Schwichtenberg, Holger: „Der erste Blazor-Streich“; in: Windows Developer 2.20, S. 18
[2] Schwichtenberg, Holger: „Sie sind wieder da!“; in: Windows Developer 1.20, S. 18