BASTA! https://basta.net/ Konferenz für .NET, Windows & JavaScript Fri, 27 Feb 2026 16:14:05 +0000 de-DE hourly 1 https://wordpress.org/?v=6.7.2 https://basta.net/wp-content/uploads/2021/02/BASTA21_Website_global_Favicon_64x64_v1-1.png BASTA! https://basta.net/ 32 32 Implizit inkrementelle LINQ-Queries in C# für XAML Data Binding https://basta.net/blog/inkrementelle-linq-queries-data-binding-csharp-nmf-expressions/ Fri, 27 Feb 2026 15:32:10 +0000 https://basta.net/?p=209789 Die Querysyntax und LINQ sind fantastische Sprachelemente in C#, um Algorithmen gut les- und wartbar als Querys zu formulieren. Allerdings eignen sie sich nur dazu, Daten einmalig abzurufen. In diesem Artikel wird das Konzept der impliziten Inkrementalisierung sowie dessen Implementierung in NMF Expressions vorgestellt und dessen Anwendung für Data Binding in WPF, MAUI und Avalonia gezeigt.

The post Implizit inkrementelle LINQ-Queries in C# für XAML Data Binding appeared first on BASTA!.

]]>
Schon seit .NET Framework 3.5 steht in C# und weiteren .NET-Programmiersprachen die Querysyntax zur Verfügung. Sie bietet Entwickler:innen eine sehr deklarative Möglichkeit, viele Informationsbedürfnisse durch gut les- und wartbare Querys zu formulieren, ähnlich prägnant wie Datenbankabfragen, aber geprüft vom Compiler.

Um beispielsweise eine priorisierte Liste von Aufgaben zu ermitteln, bei der die Aufgaben einer Reihe von Projekten zugeordnet sind, lässt sich die in Listing 1 gezeigte Abfrage verwenden: Eine Query nach den unerledigten To-dos aller aktiven Projekte (nicht „on hold“), sortiert nach Priorität und Deadline (falls gesetzt), lässt sich wohl kaum prägnanter ausdrücken.

from p in Projects
where !p.IsOnHold
from t in p.Todos
where !t.IsDone
orderby t.Priority descending, t.Deadline ?? EndOfTime
select t

Von Anfang an war es ein wichtiges Ziel, diese Abfragen analysieren zu können, um daraus beispielsweise SQL-Statements zu generieren – ein Ansatz, der mit dem Entity Framework Core im Grunde seither unverändert eine sehr hohe Verbreitung gefunden hat. Doch während die Querysyntax aus Datenbankabfragen kaum mehr wegzudenken ist, hat sie bei der Implementierung von Nutzerschnittstellen kaum Einzug gehalten.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Das ist schade, denn solche Abfragen wären auch bei der Entwicklung grafischer Benutzeroberflächen nützlich. In einer Anwendung zur Aufgabenverwaltung wäre es beispielsweise dienlich, eine priorisierte Liste von Aufgaben angezeigt zu bekommen, die sich sofort aktualisiert, wenn der Nutzer Änderungen vornimmt, die sich auf die Reihenfolge der Aufgaben auswirken.

Auch abseits von Nutzerschnittstellen möchte man manchmal wissen, wann und wie sich das Ergebnis einer Query ändert, um automatisiert Maßnahmen anstoßen zu können. Beispielsweise könnte man durch eine Query ermittelte Defekte automatisch korrigieren oder eine KPI in einem definierten Zielkorridor halten wollen.

Ein Beispiel für eine Bibliothek, die genau diese Funktionalität ermöglicht und sowohl .NET 8, 9 und 10 als auch .NET Standard 2.0 und somit auch das .NET-Framework unterstützt, ist die unter New-BSD lizenzierte Open-Source-Bibliothek NMF Expressions[1], [2]. In diesem Artikel wird beschrieben, wie diese Bibliothek funktioniert und wie sie dabei helfen kann, Data Binding gegen Querys zu ermöglichen.

INotifyPropertyChanged und INotifyCollectionChanged

Der Grund, warum Queries trotz ihrer guten Les- und Wartbarkeit nicht bei Nutzeroberflächen eingesetzt werden, ist meist, dass die XAML-basierten UI-Frameworks in .NET (WPF, MAUI, Avalonia, Uno) auf die Schnittstellen INotifyPropertyChanged und INotifyCollectionChanged angewiesen sind, um zu erkennen, welche Teile der UI wann aktualisiert werden müssen. Die Schnittstelle INotifyPropertyChanged erlaubt es hierbei, sich per Event benachrichtigen zu lassen, wenn sich das Ergebnis einer Eigenschaft ändert, wohingegen INotifyCollectionChanged über Änderungen an Auflistungen informiert. Beide Schnittstellen sind bereits seit dem .NET-Framework 3.0 Teil der Basisklassenbibliothek; es gibt sie sogar schon länger als die Querysyntax. Um die Implementierung von INotifyPropertyChanged für eigene Klassen zu unterstützen, gibt es seit einiger Zeit auch das MVVM Community Toolkit, das den notwendigen Boilerplate-Code zur Unterstützung von INotifyPropertyChanged generieren kann. Doch was ist mit INotifyCollectionChanged?

Für diese Schnittstelle gibt es im Framework nur genau eine einzige Implementierung: die ObservableCollection. Das Problem aber ist, dass LINQ-to-Objects-Querys diese nicht verwenden und Querys daher nicht für Data Binding genutzt werden können.

Data Binding an eine Query erfordert manuelle Synchronisation

Um das Ergebnis einer Query also in einer Nutzerschnittstelle anzeigen zu können, müssen die Ergebnisse manuell in einer ObservableCollection gepuffert werden. Bei jeder Änderung von Daten müssen Entwickler:innen also dafür sorgen, dass dieser Puffer aktualisiert wird. Dabei zählt Cache Invalidation nach Phil Karlton zu den schwierigsten Dingen der Informatik, da es kaum möglich ist, sicherzustellen, dass dies richtig erfolgt. Wenn der Puffer zu häufig aktualisiert wird, ist das eine Verschwendung von Ressourcen. Wenn er zu selten aktualisiert wird, können die Daten darin unter Umständen falsch sein. Hinzu kommt, dass sich in der Regel eine Vielzahl von Änderungen auf die Ergebnisse einer Query auswirken können. Wann immer ein Projekt oder To-do hinzugefügt oder gelöscht wird, ein Projekt auf „on hold“ gesetzt oder reaktiviert wird oder sich die Prioritäten oder Deadlines der einzelnen To-dos ändern, muss der Puffer entsprechend aktualisiert werden. All diese Szenarien müssen gut getestet werden. Die größte Gefahr besteht jedoch darin, irgendeine Art von Änderungen, die sich auf das Ergebnis der Query auswirken, zu vergessen.

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

Inkrementalisierung

Wie lässt sich nun dieses Problem lösen, wie lassen sich Querys auch für Data Binding einsetzen, ohne dass Änderungen an der Query selbst vorgenommen werden müssen? Wenn sich eine Änderung im Modell ergibt, was genau soll dann neu berechnet werden? Neben der Frage nach dem Wann stellt sich auch die, wie die Puffer aktualisiert werden müssen – auch im Hinblick auf die algorithmische Komplexität. In manchen Fällen kann es zeitkritisch sein, nach der Änderung der eigentlichen Daten auch die aktualisierten Queryergebnisse zur Verfügung zu haben. Es kann schlicht zu lange dauern, dann immer die gesamte Query neu zu berechnen, gerade wenn sich nur ein sehr kleiner Teil der Daten geändert hat und die Teilergebnisse der Query eigentlich noch aktuell wären.

Wenn aber immer nur Teilergebnisse neu berechnet werden, bezeichnet man das als inkrementelle Ausführung, weil sich die Datenstruktur an inkrementell auftretende Änderungen anpassen muss. In der Algorithmik spricht man in diesem Zusammenhang auch von dynamischen Algorithmen, weil die Änderungen auch bedeuten können, dass Elemente gelöscht werden. Wenn sich ein solcher inkrementeller bzw. dynamischer Algorithmus ohne Zutun des Entwicklers von einer Spezifikation, beispielsweise einer Query, ableiten lässt, handelt es sich um eine implizite Inkrementalisierung; das Ergebnis ist eine implizit inkrementelle Query.

Damit das Ergebnis einer inkrementellen Query wohldefiniert ist, dürfen die eingesetzten Prädikate keine Seiteneffekte haben, die die Berechnung anderer Teilergebnisse beeinflussen, denn andernfalls wäre unklar, welche Seiteneffekte nach einer Änderung der zugrundeliegenden Daten ausgeführt wurden. Prädikate wie Filterbedingungen dürfen daher keine von außen sichtbaren Variablen oder andere Zustände verändern. Das ist bei Querys ohnehin eine übliche Annahme. Auch wenn man mittels PLINQ eine Query parallel ausführen möchte, sind seiteneffektbehaftete Prädikate keine gute Idee, da man sich sonst mit Thread-Synchronisation auseinandersetzen muss.

Als Nächstes müssen wir überlegen, welche API wir haben wollen. Das Ergebnis einer Query kann entweder ein einzelner Wert oder eine Auflistung sein. Während Auflistungen gewöhnlich ohnehin schon von Schnittstellen repräsentiert werden, kann ein einzelner Wert kaum für sich selbst die Schnittstelle INotifyPropertyChanged implementieren und muss daher in einer separaten Schnittstelle gekapselt werden.

Eine solche Schnittstelle ist in etwa in Listing 2 skizziert. Die einfachste denkbare Implementierung hierfür ist natürlich eine Konstante, bei der also Value immer genau den Wert der Konstante zurückliefert und das Event ValueChanged nie ausgelöst wird.

public interface INotifyValue<out T>
{
  T Value { get; }
  event EventHandler ValueChanged;
}

Generische Schnittstellen sind dabei ein Weg, mathematische Funktoren zu implementieren. Collections sind ein Beispiel für Funktoren, die in vielen Programmiersprachen auftreten. Da Schnittstellen andere Schnittstellen erben können, lässt sich der Funktor IEnumerable beispielsweise mit der Schnittstelle INotifyCollectionChanged kombinieren. In NMF heißt das Ergebnis INotifyEnumerable.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Ein Funktor besteht jedoch nicht nur aus einer Abbildung von Typen (wie string auf INotifyValue<string>), sondern auch aus einer Abbildung von Funktionen/Methoden. So muss es beispielsweise aus einer Funktion Func<string, int> eine Abbildung nach Func<INotifyValue<string>,INotifyValue<int>> geben. Für den Funktor IEnumerable übernimmt diese Abbildung der Queryoperator Select. Den ganzen Funktor für INotifyValue und INotifyEnumerable, also die Typabbildung und die Abbildung von Methoden, sowie die Abbildung Value zurück auf den derzeitigen Wert (in der Mathematik ist das eine natürliche Transformation), nennt man Inkrementalisierungssystem.

Um von einer Collection, die InotifyCollectionChanged, aber nicht INotifyEnumerable implementiert, auf letztere zu kommen, braucht man einen Adapter. In NMF Expressions nennt sich die Methode, die einen solchen Adapter erzeugen kann, WithUpdates.

Doch wie erreicht man eine solche Methodenabbildung für INotifyValue und INotifyEnumerable?

Dynamische Abhängigkeitsgraphen

Um immer nur die von einer Änderung tatsächlich betroffenen Teilergebnisse aktualisieren zu müssen, liegt es nahe, einen Abhängigkeitsgraphen zu halten, um entscheiden zu können, wann welche Teilergebnisse neu berechnet werden müssen. Um eine einheitliche Schnittstelle zu schaffen, verwenden die Teilergebnisse dieselben Schnittstellen wie die Endergebnisse, also INotifyValue und INotifyEnumerable. Da sich die Teilergebnisse normalerweise auf Teile der Daten beziehen (z. B., ob ein To-do noch unerledigt ist), variiert ihre Menge mit den Daten, ist also dynamisch; daher der Name „dynamischer Abhängigkeitsgraph“ [3].

Die einfachste Methode, um einen solchen dynamischen Abhängigkeitsgraphen zu erzeugen, besteht darin, eine Teilfunktion in ihre einzelnen Bestandteile, also die einzelnen syntaktischen Elemente, zu zerlegen. Auf dieser Ebene lässt sich leicht entscheiden, wann sich das Ergebnis ändert. Ändert sich beispielsweise das Bezugsobjekt für den Zugriff auf eine Property oder löst das aktuelle Bezugsobjekt ein Event über die INotifyPropertyChanged-Schnittstelle aus, dann könnte sich auch der Wert für den Zugriff auf die Property geändert haben. Doch wie kann man zur Laufzeit eine Funktion in ihre Bestandteile aufteilen?

Expression Trees als Grundlage für implizite Inkrementalisierung

In .NET lässt sich eine solche Dekomposition mit denselben Technologien bewerkstelligen, die ursprünglich für LINQ entwickelt wurden: Expression Trees erlauben es, zur Laufzeit auf den abstrakten Syntaxbaum einer Funktion zuzugreifen anstatt auf deren Kompilat. Dazu können Lambda-Ausdrücke vom Compiler nicht nur in Delegattypen wie Func<string> gecastet werden, sondern auch in Expression-Typen davon, wie Expression<Func<string>>. Im Entity Framework (Core) wird dieses Syntaxfeature, das nicht nur in C#, sondern auch in vielen anderen .NET-Sprachen existiert, dazu verwendet, aus einer Query SQL-Statements zu generieren. Mit Expression Trees lassen sich jedoch auch dynamische Abhängigkeitsgraphen erstellen.

NMF Expressions macht genau das: Es verwendet den Aufbau von Lambdaausdrücken, um daraus einen dynamischen Abhängigkeitsgraphen zu erzeugen und so die Schnittstellen INotifyValue und INotifyCollectionChanged zu implementieren.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Explizite Inkrementalisierung von Queryoperatoren

Dynamische Abhängigkeitsgraphen auf Basis einzelner Instruktionen haben zwei Nachteile: Zum einen werden sie sehr schnell sehr groß, zum anderen ist es nicht immer sinnvoll, auf Ebene der einzelnen Instruktionen zu überlegen, welche Instruktionen von einer Änderung genau betroffen sind. Stattdessen gibt es für einige Probleme dedizierte dynamische Algorithmen, die häufig deutlich effizienter sind, als es ein instruktionsbasierter Ansatz sein kann. Um beispielsweise die Summe oder den Mittelwert einer Menge von Zahlen dynamisch zu berechnen, ist es ausreichend, sich die aktuelle Summe (und im Fall des Mittelwerts zusätzlich die aktuelle Anzahl) zu merken und sie beim Hinzufügen oder Löschen einer Zahl entsprechend zu aktualisieren. Auf diese Weise lassen sich für viele Query-Operatoren sehr effiziente dynamische Algorithmen finden, insbesondere wenn die Reihenfolge der Ergebnisse keine Rolle spielt.

Die Bibliothek NMF Expressions bietet für viele der Standardqueryoperatoren (SQO) eine explizite Inkrementierung. Die SQO sind diejenigen Queryoperatoren, die von einigen Programmiersprachen mit einer eigenen Syntax versehen sind. Ob eine Programmiersprache für einen SQO eine spezielle Syntax anbietet, ist je nach Sprache verschieden. So bildet VB.NET den Operator Aggregate in einer eigenen Syntax ab, C# aber nicht.

Die Faustregel besagt, dass es für alle Queryoperatoren, die nicht indexbasiert sind, eine inkrementelle Variante gibt. Der Grund dafür ist, dass sich Indizes zu häufig ändern. Wenn ein neues Element eingefügt wird, ändert sich nämlich der Index für alle nachfolgenden Elemente. Eine so inkrementalisierte Query hätte daher wahrscheinlich eine schlechte Performance. Um das zu vermeiden, wird diese Funktionalität erst gar nicht angeboten.

Doch wie implementiert man eine explizite Inkrementalisierung einer Methode in NMF Expressions?

Eigene Funktionen explizit inkrementalisieren

Im Unterschied zu Lambdaausdrücken kann die Implementierung einer Methode nicht ohne Weiteres eingesehen werden, da sie per Reflection nur als Bytecode abrufbar ist. Daher benötigt ein Inkrementalisierungssystem eine Heuristik, wann sich das Ergebnis eines Methodenaufrufs potenziell ändert. Eine gute erste Heuristik ist, dass sich die Rückgabe einer Methode dann ändern könnte, wenn sich die Parameter ändern.

Wenn nun Teile einer Query in separate Methoden ausgelagert werden, ist das für Technologien, die auf Expression Trees beruhen, daher grundsätzlich problematisch. So könnte das Entity Framework (Core) ein so ausgelagertes Prädikat beispielsweise nicht mehr nach SQL übersetzen, sondern wäre gezwungen, das Prädikat und alles, was danach kommt, auf dem Client auszuwerten. Im Fall der Inkrementalisierung kann das aber nützlich sein, um den Abhängigkeitsgraphen zu verkleinern. Denn für die Ausführung einer Methode gibt es im Abhängigkeitsgraphen zunächst nur genau einen Knoten. Wenn die Heuristik zutrifft, beispielsweise weil die Argumente der Methode alle unveränderlich sind, führt das zu einer effizienteren und speicherschonenderen Abarbeitung. Das ist der Fall, wenn die Methode ausschließlich mit Zahlen, Zeichenketten oder Record Structs arbeitet. Was aber, wenn nicht alle Parameter unveränderlich sind?

Die Idee der expliziten Inkrementalisierung besteht nun darin, in diese Heuristik einzugreifen und sie zu überschreiben. NMF Expressions verwendet hierfür Attribute, genauso wie das Entity Framework, das seit jeher Attribute nutzt, um Methodenaufrufe in den Aufruf von Stored Procedures in der Datenbank umzuwandeln. Das verwendete Attribut in NMF Expressions nennt sich ObservableProxy. Da man in ein Attribut keine Methode eintragen kann, muss man in das Attribut den Namen einer öffentlichen Proxymethode eintragen, sowie (falls abweichend) die Klasse, in der die Methode deklariert ist. Wenn die Proxymethode nicht öffentlich sichtbar sein soll, empfiehlt es sich, eine interne oder private Hilfsklasse dafür anzulegen.

Eine solche Proxymethode wird von NMF Expressions anstelle der eigentlichen Methode aufgerufen. Dazu muss sie dieselben Typparameter haben; außerdem muss es für jeden Parameter einen Parameter geben, bei dem der Typ in INotifyValue geschachtelt ist, ebenso für den Rückgabetyp. Da die Datenstrukturen häufig auch komplett neu aufgebaut werden müssen, wenn sich Parameter ändern, dürfen die Parametertypen der Proxymethode auch die originalen Parametertypen sein. In diesem Fall kümmert sich NMF Expressions darum, die Methode erneut aufzurufen, wenn sich die übergebenen Argumente ändern.

Ein Beispiel, das zeigt, wie das Prädikat, ob ein To-do weiterhin relevant ist, in eine Methode ausgelagert werden kann, ist in Listing 3 dargestellt. Zu Demonstrationszwecken wird hier als Implementierung der Schnittstelle INotifyValue wiederum NMF Expressions verwendet, aber natürlich sind auch eigene Implementierungen möglich, wodurch sich beliebige dynamische Algorithmen umsetzen lassen.

[ObservableProxy(nameof(IsRelevantInc1))] // alternativ nameof(IsRelevantInc2)
public bool IsRelevant(Todo todo) => !todo.IsDone;

public INotifyValue<bool> IsRelevantInc1(INotifyValue<Todo> todo)
   => Observable.Expression(() => !todo.Value.IsDone);

public INotifyValue<bool> IsRelevantInc2(Todo todo)
   => Observable.Expression(() => !todo.IsDone);

Integration in UI-Technologien

Obwohl es laut den Architekturrichtlinien von .NET für Schnittstellen immer mindestens zwei denkbare Implementierungen geben muss, existiert für INotifyCollectionChanged in der Basisklassenbibliothek, wie schon erwähnt, nur eine Implementierung, nämlich ObservableCollection. Das hat anscheinend zur Folge, dass die meisten XAML-basierten UI-Bibliotheken davon ausgehen, dass es sich bei einer Implementierung von INotifyCollection um die ObservableCollection handeln muss, obwohl die Schnittstelle eigentlich allgemeiner entworfen wurde. So kann man bei einem CollectionChanged-Ereignis den Index eines geänderten, hinzugefügten oder gelöschten Elements angeben oder eine -1 eintragen, falls der Typ der Collection keine Indizes unterstützt, wie beispielsweise bei Mengentypen wie HashSet, die keine Ordnung der Elemente garantieren.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Leider wird bei UI-Bibliotheken weder die Möglichkeit berücksichtigt, dass eine Implementierung von INotifyCollectionChanged von einem Index -1 Gebrauch machen könnte, noch die Möglichkeit, dass in einer Collection mehrere Elemente auf einmal verändert werden oder nach einem Reset Elemente übrigbleiben könnten. Auch wenn sich die Fehlermeldungen je nach UI-Technologie durchaus unterscheiden, implementieren alle großen UI-Bibliotheken immer nur genau das, was für die ObservableCollection benötigt wird.

Wie macht man also eine inkrementelle Query kompatibel für Data Binding in UI-Bibliotheken? In NMF Expressions existiert hierfür die spezielle Methode RestoreIndices, die die inkrementelle Query puffert, Änderungen vereinzelt und Indizes wiederherstellt. Damit lässt sich die Query aus Listing 1 wie in Listing 4 in die Oberfläche einbinden.

TodosInOrder = (from p in Projects.WithUpdates()
                where !p.IsOnHold
                from t in p.Todos
                where !t.IsDone
                orderby t.Priority descending, t.Deadline ?? EndOfTime
                select t).RestoreIndices();

In [4] habe ich ein Repository erstellt, das die Verwendung von NMF Expressions in Verbindung mit dem MVVM Community Toolkit für das Eingangsbeispiel der To-do-Liste für WPF, MAUI und Avalonia demonstriert.

GraphQL

Die meisten XAML-basierten Technologien wie WPF, MAUI oder Avalonia arbeiten innerhalb desselben Prozesses, weshalb sich das UI bequem an die über INotifyPropertyChanged und INotifyCollectionChanged zur Verfügung gestellten Ereignisse auf Änderungen des Modells registrieren kann, um Data Binding zu ermöglichen. Das funktioniert natürlich nicht mehr, wenn das UI in einem anderen Prozess liegt, wie es beispielsweise bei Webanwendungen der Fall ist. Um sich hier auch über Änderungen benachrichtigen zu lassen, müssen Events explizit als Subscriptions hinterlegt werden. In [4] habe ich das exemplarisch auch für eine GraphQL-Version derselben To-do-Liste verwendet.

Performancegewinne hängen von der Anwendung ab

Eine Frage, die sich förmlich aufdrängt, ist die nach der Performance. Natürlich verbraucht der dynamische Abhängigkeitsgraph Speicher. Mehr Speicher ist allerdings häufig einfacher zu bekommen als mehr CPU-Zeit. Hinzu kommt, dass manche Queryoperatoren wie Orderby bei jeder Ausführung Speicher benötigen, während eine inkrementelle Query einen balancierten Suchbaum im Speicher hält und diesen permanent beibehält. Daher wird bei einer Abfrage der Ergebnisse kein neuer Speicher allokiert. Die Performance der Änderungsausbreitung kann jedoch auch von der Beschaffenheit konkreter Änderungen abhängen. So ist es im Beispiel natürlich aufwendiger zu propagieren, wenn in einem Projekt viele To-dos von „on hold“ wieder auf aktiv gesetzt werden, als wenn diesem Projekt gar keine To-dos zugeordnet gewesen wären. Diese Beispiele illustrieren, dass eine allgemeine Aussage kaum möglich ist.

In guten Fällen kann es allerdings auch zu sehr hohen Beschleunigungen kommen, solange der dynamische Abhängigkeitsgraph in den Speicher passt. So konnten in einigen Benchmarks auch schon Beschleunigungen von mehreren Größenordnungen gemessen werden [5]. Insbesondere wenn die Änderungen nur kleine Teile der Query betreffen, ist der Aufwand für die Propagation in einer viel günstigeren Komplexitätsklasse, als wenn komplett alles neu berechnet und anschließend noch verglichen werden müsste, was genau sich denn geändert hat.

Zusammenfassung

Mit Hilfe von dynamischen Abhängigkeitsgraphen lassen sich Querys in .NET nicht nur automatisch in Datenbankabfragen konvertieren. Die angebotenen Abstraktionen eignen sich auch dazu, automatisiert zu erfassen, wann sich das Ergebnis einer Query ändert. Auf diese Weise können deklarative Querys mit NMF Expressions beispielsweise für das Data Binding von XAML-basierten UI-Technologien eingesetzt werden.


Links & Literatur

[1] https://nmfcode.github.io/expressions/index.html

[2] Hinkel, G.; Heinrich, R.; Reussner, R.: „An extensible approach to implicit incremental model analyses“; in: Software & Systems Modeling 18, 2019

[3] Acar, U.: „Self-adjusting computation:(an overview)“: in: „Proceedings of the 2009 ACM SIGPLAN workshop on Partial evaluation and program manipulation“, 2009

[4] https://github.com/georghinkel/expressions-demo

[5] Szárnyas, G.; Semeráth, O.; Ráth, I.: „The TTC 2015 Train Benchmark Case for Incremental Model Validation“; in: „Proceedings of the 8th Transformation Tool Contest“, 2015

 

The post Implizit inkrementelle LINQ-Queries in C# für XAML Data Binding appeared first on BASTA!.

]]>
.NET 10.0 RTM: Alle Neuerungen seit dem Release Candidate im Überblick https://basta.net/blog/dotnet-10-rtm-neuerungen-efcore-vectors/ Mon, 12 Jan 2026 14:40:18 +0000 https://basta.net/?p=108090 Der DOTNET-DOKTOR hat in früheren Artikeln bereits zentrale Neuerungen aus den .NET-10.0-Previews beleuchtet. In diesem Beitrag geht es um alles, was seit Release Candidate 1 bis zur finalen RTM-Version hinzugekommen ist – mit Fokus auf Entity Framework Core und verwandte Technologien.

The post .NET 10.0 RTM: Alle Neuerungen seit dem Release Candidate im Überblick appeared first on BASTA!.

]]>
Die Release-to-Manufacturing-Version (RTM) für .NET 10.0 erschien am 11.11.2025 im Rahmen der .NET Conf 2025 (Abb. 1). Zeitgleich hat Visual Studio 2026 (alias Version 18.0) das Stadium „Stable Version“ erreicht. Mit Visual Studio 2022 kann man .NET-10.0-Anwendungen nicht kompilieren. Zukünftig wird es jedes Jahr zusammen mit dem .NET-Release auch eine neue Hauptversionsnummer von Visual Studio geben.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

.NET 10.0 ist eine Long-Term-Support-Version, die ab November 2025 36 Monate unterstützt wird. .NET 8.0 und .NET 9.0 haben noch Support bis November 2026, nachdem Microsoft den Support für die Standard-Term-Support-Versionen auf 24 Monate erhöht hat. Die nächste Version, .NET 11.0, wird im November 2026 erscheinen – dann zusammen mit Visual Studio 2027.

Scott Hanselman bedankt sich in der Keynote der .NET Conf 2025 bei der Community für ihre Beiträge zu .NET 10.0.

Abb. 1: Scott Hanselman dankt in der Keynote der .NET Conf 2025 allen Community-Mitgliedern, die Beiträge zu .NET 10.0 geleistet haben – darunter auch einigen BASTA!-Sprechern

Entity Framework Core: JSON-Spalten

Seit Entity Framework Core 10.0 Release Candidate 1 wird der Spaltentyp JSON unterstützt. Dieser steht in Microsoft SQL Server 2025 (erschienen am 18.11.2025 [1]) sowie in Azure SQL zur Verfügung.

JSON-Spalten kamen in EF Core zwar schon früher zum Einsatz, etwa um einfache Wertlisten wie List<int> in einer einzelnen Spalte abzulegen (eine Ausnahme bildet PostgreSQL, das solche Datentypen von Haus aus unterstützt) oder um sogenannte Owned Types zu speichern. Bislang wurde dafür jedoch eine nvarchar(max)-Spalte genutzt, kombiniert mit der SQL-Funktion OPENJSON().

Mit den neuen SQL-Server-Versionen greift EF Core nun auf den nativen JSON-Datentyp zurück – vorausgesetzt, die verwendete SQL-Server-Instanz beherrscht diesen auch. Zusätzlich muss EF Core darüber informiert werden, dass JSON-Spalten verfügbar sind. Bei Azure SQL geschieht das, indem statt UseSqlServer() nun UseSqlAzure() verwendet wird.

 

Beim lokalen Microsoft SQL Server muss man den Kompatibilitätslevel auf 170 (Standard ist 150) setzen (Abb. 2 und 3):

builder.UseSqlServer(connstring, x => x.UseCompatibilityLevel (170));
Datentyp-Mapping bei EF Core 10.0 mit SQL Server 2025 im Kompatibilitätslevel 150 – JSON-Typ noch nicht nativ unterstützt.

Abb. 2: Datentypmapping mit SQL Server 2025 bei EF Core im Kompatibilitätslevel 150

Datentyp-Mapping bei EF Core 10.0 mit SQL Server 2025 im Kompatibilitätslevel 170 – jetzt mit nativer JSON-Spaltenunterstützung.

Abb. 3: Datentypmapping mit SQL Server 2025 bei EF Core im Kompatibilitätslevel 170

Entity Framework Core: Erweiterungen bei komplexen Typen

Mit Entity Framework Core 8.0 hat Microsoft die Complex Types als modernere und flexiblere Alternative zu den Owned Types eingeführt und in Version 9.0 weiter ausgebaut. Allerdings standen für Complex Types bislang keine JSON-Abbildungen zur Verfügung. Stattdessen wurde jede Property eines Complex Types stets in eine eigene Spalte der Tabelle des übergeordneten Entitätstyps projiziert.

Seit Entity Framework Core 10.0 (ab Release Candidate 1) unterstützen Complex Types nun auch ein JSON-Mapping und können zudem optional sein. Für optionale Complex Types gibt es jedoch eine Einschränkung: Sie müssen mindestens eine verpflichtende Property enthalten. EF Core benötigt dieses Pflichtfeld, um erkennen zu können, ob der Complex Type tatsächlich null ist oder lediglich leere Werte besitzt (Listing 1).

Fehlt das Pflicht-Property, führt das zu folgender Fehlermeldung: „’ShippingAddress’ is mapped to columns by flattening the contained properties into its container’s table; this mapping requires at least one required property – to allow distinguishing between ‘null’ and empty values – but the complex type contains only optional properties. Configure the property with a shadow discriminator by adding a call to ‘HasDiscriminator()’ on the complex property configuration, or map this complex property to a JSON column instead.“

public abstract class Company
{
  [Key]
  public int CompanyID { get; set; } // Primary Key
  [StringLength(50)]
  public string Name { get; set; }
  
  public DateTime Created { get; set; } = DateTime.Now;
  public Address BillingAddress { get; set; } // 1:1
  public Address? ShippingAddress { get; set; } 
  
  [Required]
  public CompanyDescription Description { get; set; } = new(); // 1:1
  public List<Management> ManagementSet { get; set; } = new(); // 1:N
}

public record struct Address // record struct für Complex Type
{
  public Address()
  {
  }
  
  //public int AddressID { get; set; } //--> PK bei Complex Types nicht notwendig
 
  public bool AdressExists { get; set; } = true; // bei Optional Complex Type ohne weitere Pflichtfelder (neu in v10.0)
  [StringLength(50)]
  public string City { get; set; }
  [StringLength(50)]
  public string Street { get; set; }
  [StringLength(10)]
  public string Postcode { get; set; }
  [StringLength(50)]
  public string Country { get; set; }
}

Ein Beispiel für einen Complex Type mit JSON-Mapping könnte so aussehen:

modelBuilder.Entity<Company>(x =>
{
  x.ComplexProperty(p => p.Address, p=>p.ToJson()); // 1:1-Mapping. NEU: p=>p.ToJson()
}

Geplant war auch, ein 1:N-Mapping in Complex Types zu ermöglichen. Zwar beschwert sich Entity Framework Core nicht mehr über eine Zeile wie x.ComplexProperty(p => p.ManagementSet, p => p.ToJson()); und es wird auch eine JSON-Spalte ManagementSet in der Tabelle angelegt. Die enthält dann aber nicht die Daten, sondern nur die Eigenschaft Capacity (Abb. 4). Daher funktionieren auch Abfragen über diese Spalte nicht.

Fehlerhafte Persistierung eines Complex Types in einer JSON-Spalte – nur das Feld "Capacity" wird gespeichert.

Abb. 4: Falsche Persistierung der Menge im komplexen Typ

Entity Framework Core: Vektoren

Microsoft SQL Server 2025 führt einen neuen Spaltentyp vector ein, der speziell für die Speicherung von Vektordaten im Kontext von Ähnlichkeitssuchen und KI-Szenarien entwickelt wurde. In Azure SQL steht dieser Datentyp bereits seit 2024 als Vorschau zur Verfügung und gilt seit Juni 2025 offiziell als produktionsbereit.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Ein Vektor besitzt eine feste Dimension, also eine Anzahl an Elementen. Jedes dieser Elemente wird entweder als 2-Byte- oder 4-Byte-Gleitkommazahl abgelegt. Die Nutzung des kompakten 2-Byte-Formats muss zuvor separat aktiviert werden. Innerhalb des SQL Servers werden Vektoren in einem speziell optimierten Binärformat gespeichert und über entsprechende Datenbanktreiber – etwa Microsoft.Data.SqlClient ab Version 6.1 – effizient übertragen.

In T-SQL und im SQL Server Management Studio (ab Version 21) werden die Vektordaten als JSON-Array visualisiert, z. B.:

DECLARE @v VECTOR(10) = '[0.1, 2, 3.5, 4, 5, 6, 7, 8, 9.9, 10]';
SELECT @v;

Ältere Versionen der Datenbanktreiber übertragen Vektordaten noch im nvarchar(max)-Format und somit als JSON-Struktur. Für Vektoren stehen keinerlei Vergleichsoperatoren oder arithmetische Operationen zur Verfügung – also weder Gleichheits- noch Größenvergleiche noch Rechenoperationen wie Addition, Subtraktion, Multiplikation oder Division. Auch Verkettungen oder zusammengesetzte Zuweisungsoperatoren werden nicht unterstützt. Zudem lassen sich Vektorspalten nicht in speicheroptimierten Tabellen einsetzen.

Mit SQL Server 2025 steht für Vektordaten die Funktion VECTOR_DISTANCE() bereit, über die sich der Abstand zwischen zwei Vektoren berechnen lässt. Dabei können drei verschiedene Distanzverfahren verwendet werden (Abb. 5):

  1. “cosine”: 0 bis 2 (0 = identisch, = orthogonal, = entgegengesetzt)
  2. “euclidean”: 0 bis unendlich (0 = identisch)
  3. “dot”: -1 bis +1 bei normierten Vektoren, -unendlich bis +unendlich bei nicht normierten Vektoren
Vergleich der VECTOR_DISTANCE-Funktion in SQL Server 2025 mit den Metriken cosine, euclidean und dot.

Abb. 5: Vektordistanzberechnung

Die Funktion VECTOR_SEARCH(), mit der sich der jeweils nächste Nachbar finden lässt, befindet sich weiterhin im Preview-Status.

Seit Entity Framework Core 10.0 Release Candidate 1 können Entwickler:innen Vektorspalten direkt nutzen. Beim Reverse- wie auch beim Forward-Engineering werden diese SQL-Server-Spalten dem .NET-Typ Microsoft.Data.SqlTypes.SqlVector<float> zugeordnet. Dieser stammt aus dem NuGet-Paket Microsoft.Data.SqlClient (ab Version 6.1).

Zusätzlich stellt EF Core eine neue Methode EF.Functions.VectorDistance() bereit, welche die SQL-Server-Funktion VECTOR_DISTANCE() in LINQ-Ausdrücken repräsentiert (Listing 2). Vor EF Core 10.0 war hierfür ein separates NuGet-Paket namens EFCore.SqlServer.VectorSearch erforderlich. Die SQL-Funktion VECTOR_SEARCH() kann jedoch weiterhin nicht direkt in LINQ-Abfragen verwendet werden.

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.Data.SqlTypes;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace EFC_MappingScenarios.Vectors;

public class Texte
{
  public int ID { get; set; }
  public string Text { get; set; }
  [Column(TypeName = "vector(384)")] 
  public Microsoft.Data.SqlTypes.SqlVector<float> Embedding { get; set; }
}

class Context : DbContext
{
  public DbSet<Texte> TexteSet { get; set; }
  bool logActive = false;

  void Log(string message)
  {
    if (logActive) CUI.PrintAlternatingColor(message);
  }

  protected override void OnConfiguring(DbContextOptionsBuilder builder)
  {
    builder.EnableSensitiveDataLogging(true).EnableDetailedErrors();
    string connstring = Settings.ConnectionStringSQL2025 + @$";Database=EFC_MappingScenarios_" + nameof(EFC_MappingScenarios.Vectors);
    builder.UseSqlServer(connstring, x => x.UseNetTopologySuite().UseCompatibilityLevel(170)).LogTo(Log, new[] { RelationalEventId.CommandExecuted }); // WICHTIG für JSON-Spalten: 170
  }

}

class Client
{
  // https://www.bundesregierung.de/breg-de/bundesregierung/bundeskabinett?utm_source=chatgpt.com 
  static OrderedDictionary<string, string> Bundesregierung2025 = new() {
    { "Friedrich Merz", "Bundeskanzler" }, { "Lars Klingbeil", "Bundesminister der Finanzen" }, { "Alexander Dobrindt", "Bundesminister des Innern" }, { "Dr. Johann Wadephul", "Bundesminister des Auswärtigen" }, { "Boris Pistorius", "Bundesminister der Verteidigung" }, { "Katherina Reiche", "Bundesministerin für Wirtschaft und Energie" }, { "Dorothee Bär", "Bundesministerin für Forschung, Technologie und Raumfahrt" }, { "Dr. Stefanie Hubig", "Bundesministerin der Justiz und für Verbraucherschutz" }, { "Karin Prien", "Bundesministerin für Bildung, Familie, Senioren, Frauen und Jugend" }, { "Bärbel Bas", "Bundesministerin für Arbeit und Soziales" }, { "Dr. Karsten Wildberger", "Bundesminister für Digitales und Staatsmodernisierung" }, { "Patrick Schnieder", "Bundesminister für Verkehr" }, { "Carsten Schneider", "Bundesminister für Umwelt, Klimaschutz, Naturschutz und nukleare Sicherheit" }, { "Nina Warken", "Bundesministerin für Gesundheit" }, { "Alois Rainer", "Bundesminister für Landwirtschaft, Ernährung und Heimat" }, { "Reem Alabali-Radovan", "Bundesministerin für wirtschaftliche Zusammenarbeit und Entwicklung" }, { "Verena Hubertz", "Bundesministerin für Wohnen, Stadtentwicklung und Bauwesen" }, { "Thorsten Frei", "Bundesminister für besondere Aufgaben / Chef des Bundeskanzleramtes" }
  };

  public static void Run()
  {
    CUI.Demo(nameof(EFC_MappingScenarios.Vectors));

    using (var ctx = new Context())
    {
      CUI.Yellow("Model: " + ctx.Model.GetType().FullName);
      Util.RecreateDB(ctx);

      #region ---------------------- Vektor-Daten anlegen
      CUI.Head(ctx.Database.ProviderName + ": Erstelle Datensätze mit Vektor-Spalte (Lokale Vektorisierung dauert etwas...)");
      foreach (var person in Bundesregierung2025)
      {
        CUI.BusyIndicator();
        var dt = new Texte();
        ctx.TexteSet.Add(dt);

        dt.Text = person.Key + " ist " + person.Value;
        var values = EmbeddingsUtil.Get("passage:" + dt.Text);
        dt.Embedding = new SqlVector<float>(values);

        Console.WriteLine(dt.Text + " -> " + dt.Embedding.Length);
      }
      var c2 = ctx.SaveChanges();
      Console.WriteLine($"{c2} Datensätze gespeichert");
      #endregion

      #region ---------------------- Vektor-Daten abfragen 

      CUI.Head(ctx.Database.ProviderName + ": Vektorsuche");

      Suche("Dr. Karsten Wildberger");// exakte Suche
      Suche("Karsten");// nur Vorname
      Suche("Carsten Wilberger");// absichtlich falsch geschrieben, damit es nicht exakt passt
      Suche("Kriegsminister");
      Suche("Kassenwart");
      Suche("Unfug");
      Suche("Der beliebteste Politiker im Kabinett");
      Suche("Der unbeliebteste Politiker im Kabinett");
      #endregion

      CUI.PanelGreen("=== DONE");
    }
  }

  // https://learn.microsoft.com/en-us/sql/t-sql/functions/vector-distance-transact-sql?view=sql-server-ver17
  //   cosine - Cosine distance 0 bis 2 (0 = identisch, 1 = orthogonal, 2 = entgegengesetzt)
  //   euclidean - Euclidean distance 0 bis unendlich (0 = identisch)
  //   dot - (Negative)Dot product (-1 bis +1 bei normierten Vektoren, -unendlich bis +unendlich bei nicht normierten Vektoren)
  private static void Suche(string suchBegriff, string distanzVerfahren = "cosine")
  {
    using var ctx = new Context();

    CUI.Yellow("Suche ähnlichste Einträge zu: " + suchBegriff);
    var sqlVector = new SqlVector<float>(EmbeddingsUtil.Get(suchBegriff));
    var result = ctx.TexteSet
      .OrderBy(b => EF.Functions.VectorDistance(distanzVerfahren, b.Embedding, sqlVector))
      .Take(5)
      .Select(b => new { Object = b, Distance = EF.Functions.VectorDistance(distanzVerfahren, b.Embedding, sqlVector) })
      .ToList();

    foreach (var b in result)
    {
      CUI.Green(b.Object.ID + ": " + b.Object.Text + " -> Distanz: " + Math.Round(b.Distance, 2));
    }
  }
}

Im Beispiel sind die Mitglieder des Bundeskabinetts (Stand November 2025) in einer Tabelle gespeichert, wobei Name und Zuständigkeit in einer Textspalte erfasst sind. Darauf basierend werden verschiedene Ähnlichkeitssuchen über EF.Functions.VectorDistance() ausgeführt.

 

Die in Abbildung 6 gezeigten Ergebnisse wurden mit dem Embedding-Modell Multilingual-E5-small erzeugt, das 384 Dimensionen besitzt. Das von Microsoft-Mitarbeitenden entwickelte Modell ist Open Source, lokal einsetzbar und benötigt keine Cloud. Da E5 Texte vieler Sprachen in einen gemeinsamen semantischen Vektorraum überführt, funktionieren Ähnlichkeitsvergleiche auch über Sprachgrenzen hinweg. Zu Begriffen wie „Kassenwart“ oder „Kriegsminister“ liefert das Modell korrekt die Finanz- bzw. Verteidigungsminister. Welche Mitglieder der Regierung besonders beliebt oder unbeliebt sind, kann das Embedding-Modell hingegen naturgemäß nicht beurteilen.

Ergebnisse einer semantischen Vektorsuche mit EF.Functions.VectorDistance() in EF Core 10.0 – Anwendung auf politische Rollen.

Abb. 6: Ausgabe des obigen Listings

Entity Framework Core: Optionen für Parametermengen

Ein Performancethema in Entity Framework Core, bei dem Microsoft seit Jahren verschiedene Strategien ausprobiert, ist die Übergabe von Parametermengen von .NET an das Datenbankmanagementsystem, z. B. eine Liste von Orten, zu denen passende Datensätze gesucht werden:

List<string> destinations = new List<string> { "Berlin", "New York", "Paris" };
var flights = ctx.Flights
.Where(f => destinations.Contains(f.Destination))
.Take(5).ToList();

In den Entity-Framework-Core-Versionen 1.0 bis 7.0 wurden die Werte aus der Menge destinations einzeln als statische Werte übergeben:

SELECT TOP(@p) [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM [Operation].[Flight] AS [f]
WHERE [f].[NonSmokingFlight] = CAST(1 AS bit) AND [f].[FlightDate] > GETDATE() AND [f].[FreeSeats] > CAST(0 AS smallint) AND [f].[Destination] IN (N'Berlin', N'New York', N'Paris')

Das sorgte aber im Datenbankmanagementsystem für viele verschiedene Ausführungspläne. Seit Version 8.0 übergibt Entity Framework Core die Liste als ein JSON-Array, das im SQL-Befehl mit OPENJSON() gespalten wird (Listing 3). Das erschwerte dem Datenbankmanagementsystem die Optimierung der Ausführungspläne, weil die Anzahl der Parameter nicht mehr bekannt war.

SELECT TOP(@p) [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM [Operation].[Flight] AS [f]
WHERE [f].[NonSmokingFlight] = CAST(1 AS bit) AND [f].[FlightDate] > GETDATE() AND [f].[FreeSeats] > CAST(0 AS smallint) AND [f].[Destination] IN (
  SELECT [d].[value]
  FROM OPENJSON(@destinations) WITH ([value] nvarchar(30) '$') AS [d]
)

Seit Entity Framework Core 9.0 besteht die Möglichkeit, über EF.Constant() oder den globalen Aufruf TranslateParameterizedCollectionsToConstants() in der OnConfiguring()-Methode der Kontextklasse wieder auf das frühere Übersetzungsverhalten zurückzuschalten:

var flights1 = ctx.Flights
.Where(f => EF.Constant(destinations).Contains(f.Destination))
.Take(5).ToList();

Beim geänderten Standard konnten Entwickler:innen dann im Einzelfall mit EF.Parameter() das JSON-Array übergeben. In der aktuellen Version 10.0 des OR-Mappers hat Microsoft abermals einen neuen Standard implementiert, nämlich die Einzelübergabe der Werte jeweils als eigene Parameter. Diesen SQL-Befehl erzeugt Entity Framework Core im vorangegangenen Beispiel mit drei Städten:

SELECT TOP(@p) [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM [Operation].[Flight] AS [f]
WHERE [f].[NonSmokingFlight] = CAST(1 AS bit) AND [f].[FlightDate] > GETDATE() AND [f].[FreeSeats] > CAST(0 AS smallint) AND [f].[Destination] IN (@destinations1, @destinations2, @destinations3)

Im ausführlichen Test stellt man fest, dass EF Core nicht immer genau die gleiche Anzahl Parameter erzeugt, wie es Werte gibt. Stattdessen rundet Entity Framework Core zur Vermeidung des Anlegens von zu vielen Ausführungsplänen ab sechs Parametern auf 10er-Blöcke auf, d. h. für sechs Werte werden zehn Parameter erzeugt, wobei die letzten Parameter immer den gleichen Wert erhalten (Listing 4).

[Parameters=[@p='5', @destinations1='Berlin' (Size = 30), @destinations2='New York' (Size = 30), @destinations3='Paris' (Size = 30), @destinations4='Rome' (Size = 30), @destinations5='Munich' (Size = 30), @destinations6='London' (Size = 30), @destinations7='London' (Size = 30), @destinations8='London' (Size = 30), @destinations9='London' (Size = 30), @destinations10='London' (Size = 30)], CommandType='Text', CommandTimeout='300']
  SELECT TOP(@p) [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
  FROM [Operation].[Flight] AS [f]
  WHERE [f].[NonSmokingFlight] = CAST(1 AS bit) AND [f].[FlightDate] > GETDATE() AND [f].[FreeSeats] >= CAST(20 AS smallint) AND [f].[FreeSeats] <= CAST(200 AS smallint) AND [f].[Destination] IN (@destinations1, @destinations2, @destinations3, @destinations4, @destinations5, @destinations6, @destinations7, @destinations8, @destinations9, @destinations10)

Entwickler:innen können bei Bedarf weiterhin das bisherige Verhalten über EF.Constant() bzw. EF.Parameter() erzwingen. Welche Variante sinnvoll ist, hängt auch davon ab, wie stark sich die Anzahl der Werte zur Laufzeit verändert.

Auf globaler Ebene lässt sich das Standardverhalten über UseParameterizedCollectionMode(ParameterTranslationMode.Constant) in OnModelCreating() in der Kontextklasse anpassen. Als zulässige Einstellwerte stehen ConstantParameter und MultipleParameters zur Verfügung. Die erst mit EF Core 9.0 eingeführte Methode TranslateParameterizedCollectionsToConstants() existiert zwar weiterhin, ist jedoch inzwischen mit [Obsolete] gekennzeichnet.

Entity Framework Core: Analyzer warnt vor möglicher SQL Injection

Der OR-Mapper liefert seit Release Candidate 2 einen Code-Analyzer mit, der Entwickler:innen warnt, wenn man beim Aufruf von FromSqlRaw()SqlQueryRaw<T>() oder ExecuteSqlRaw() eine Zeichenkette mit Pluszeichen oder String-Interpolation zusammensetzt. Die Warnung lautet: „Method inserts concatenated strings directly into the SQL, without any protection against SQL injection. Consider using ‘FromSql’ instead, which protects against SQL injection, or make sure that the value is sanitized and suppress the warning.“

Der Analyzer erkennt die Zeichenkettenzusammensetzung, wenn sie direkt im Parameter des Methodenaufrufs stattfindet, nicht aber, wenn eine zuvor zusammengesetzte Zeichenkette übergeben wird (Abb. 7).

EF Core Analyzer warnt bei potenzieller SQL-Injection durch dynamisch zusammengesetzte SQL-Strings.

Abb. 7: Der Analyzer erkennt einige (siehe grüne Schlangenlinien), aber leider nicht alle diese gefährlichen SQL-Injektionen

Verbesserungen bei ASP.NET Core

ASP.NET Core führte in .NET 10.0 Release Candidate 1 neue Metriken für ASP.NET Core Identity zur Überwachung der Benutzerverwaltung ein, z. B. aspnetcore.identity.user.create.durationaspnetcore.identity.sign_in.sign_ins und aspnetcore.identity.sign_in.two_factor_clients_forgotten.

Zur Validierung von Instanzen von Klassen und Records in Blazor, die Microsoft schon in der Preview-Phase von .NET 10.0 verbessert hatte, gibt es seit Release Candidate 1 drei weitere Neuerungen:

  1. Validierungsannotationen können auch für Typen und nicht nur wie bisher für Properties definiert werden.
  2. Mit der neuen Annotation [SkipValidation] können Entwickler:innen Typen und Properties von der Validierung ausschließen.
  3. Ebenso werden alle mit [JsonIgnore] annotierten Properties nicht mehr validiert.

Der Persistent Component State in Blazor, den es seit .NET 10.0 Preview 6 gibt, funktioniert nun auch beim Einsatz der Enhanced Navigation beim statischen Server-side Rendering.

Verbesserungen bei MAUI

Mit der Einstellung SafeAreaEdges=”None” können .NET-MAUI-Fenster nun auch auf Android und iOS in den ansonsten geschützten Navigationsbereichen oben und unten rendern. Alternative Werte sind “Container” (schützt vor Systembars und Notch), “SoftInput” (schützt nur vor der Tastatur) und “All” (kombiniert “Container” und “SoftInput”) (Abb. 8).

Darstellung der SafeAreaEdges-Eigenschaft in .NET MAUI mit Codebeispiel und UI-Auswirkung auf Android/iOS.

Abb. 8: Safe Area Edges auf iOS. Quelle: Microsoft .NET Conf 2025

Auch bei .NET MAUI gibt es neue Metriken für die Überwachung der Layoutperformance:

  • maui.layout.measure_count
  • maui.layout.measure_duration
  • maui.layout.arrange_count
  • maui.layout.arrange_duration

Das Steuerelement <HybridWebView> bietet nun die Ereignisse WebViewInitializing() und WebViewInitialized() zur Anpassung der Initialisierung. Vergleichbare Ereignisse gab es zuvor im Steuerelement <BlazorWebView> (BlazorWebViewInitializing() und BlazorWebViewInitialized()). Das Steuerelement <RefreshView> besitzt nun auch die Eigenschaft IsRefreshEnabled zusätzlich zu IsEnabled.

Seit Preview 7 gibt es den Source Generator für XAML in .NET MAUI. Um diesen Source Generator zu aktivieren, schreibt man seit Release Candidate 2 in die Projektdatei:

<PropertyGroup>
<MauiXamlInflator>SourceGen</MauiXamlInflator>
</PropertyGroup>

Auch unter Windows kann man nun die Mikrofonberechtigungen abfragen: Permissions.RequestAsync<Permissions.Microphone>()

Verbesserungen bei Windows Forms

Das Windows-Forms-Teams schreibt in seinen Release Notes, dass der in .NET 9.0 eingeführte Dark Mode (Abb. 9) nun nicht mehr den Status „experimentell“ besitzt [2].

Dark Mode in Windows Forms unter .NET 10.0 – zahlreiche Steuerelemente in dunklem UI-Design.

Abb. 9: Einige Windows-Forms-Steuerelemente im Dark Mode

Fazit

Es ist Zeit, ein Fazit über die Neuerungen aus allen vier Beiträgen zu .NET 10.0 zu ziehen. Das soll in Form von Listen der High- und Lowlights geschehen. Meine zehn persönlichen Highlights in .NET 10.0 sind:

  1. Extensions in C#: Damit können Entwickler:innen bestehende, kompilierte Klassen um Instanzmethoden, statische Methoden, Instanz-Properties, statische Properties und Operatoren erweitern
  2. Semi-Auto Properties in C# mit dem Schlüsselwort field (sind nun offiziell unterstützt)
  3. C#-Scripting mit File-based Apps im .NET SDK
  4. Neue LINQ-Operatoren LeftJoin()und RightJoin()in LINQ und EF Core
  5. Nutzung der SQL-Server-Spaltentypen JSON und VECTOR in EF Core
  6. JSON-Patch nach RFC 6902 mit NuGet-Paket Microsoft.AspNetCore.JsonPatch.SystemTextJson
  7. Persistierung von Circuits in Blazor Server und einfachere Festlegung des Persistent Component State via Annotation
  8. Anpassbarkeit des Verbindungsproblemdialogs bei Blazor Server
  9. Das NuGet-Paket Microsoft.AspNetCore.OpenApi berücksichtigt XML-Codekommentare bei der Erstellung von OAS-Dokumenten; diese verwenden nun OAS-Version 3.1 und optional YAML statt JSON
  10. In Windows Forms können Entwickler:innen nun Screenshots von einzelnen Windows-Forms-Fenstern mit einem Screenshotwerkzeug verhindern

Ich sehe auch Enttäuschungen in .NET 10.0:

  1. Auch wenn Microsoft in .NET 10.0 Verbesserungen gemacht haben will: Das Hot Reloading in Blazor-Anwendungen ist immer noch eine große Enttäuschung, weil es oft abstürzt und man sich fragen muss, ob man mit oder ohne Hot Reloading produktiver coden kann.
  2. Auch der Blazor-Editor hat in der aktuellen Entwicklungsumgebung Visual Studio 2026 noch viele Macken: Immer wieder sieht man die berüchtigten gelben Balken in der Entwicklungsumgebung, die anzeigen, dass ein Editorfeature gerade abgestürzt ist.
  3. Die in EF Core 9.0 eingeführte, aber nicht für die Praxis brauchbare Unterstützung des NativeAOT-Compilers, wurde in Version 10.0 nicht einen Deut verbessert.
  4. Der NativeAOT-Compiler funktioniert auch immer noch nicht für Windows Forms und WPF sowie ASP.NET Core MVC, Razor Pages sowie Blazor auf dem Server (weder für static noch interactive Server-side Rendering).
  5. Blazor WebAssembly kann immer noch kein vollständiges Multi-Threading, siehe [3] und [4].
  6. Es gibt weiterhin keinen WYSIWYG-Designer für Blazor. Das sei als Schwäche erwähnt, auch wenn Microsoft schon klargestellt hat, dass es einen solchen Designer nicht liefern wird und viele Entwickler:innen einen solchen Designer nicht vermissen. Es gibt aber genug Menschen, die sich einen Designer wünschen.
  7. Microsoft unterstützt .NET MAUI immer noch nicht auf dem Linux-Desktop. Aber es gibt eine neue Perspektive: Das an WPF angelehnte, XAML-basierte Open-Source-GUI-Framework Avalonia hat am 11.11.2025 angekündigt, dass man an einer Brücke zwischen .NET MAUI und dem Avalonia-Kern arbeitet, der auf Skia-Rendering basiert [5]. Damit kann .NET MAUI in Zukunft auch auf dem Linux-Desktop und auch im Webbrowser laufen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Links & Literatur

[1] https://learn.microsoft.com/de-de/sql/sql-server/what-s-new-in-sql-server-2025?view=sql-server-ver17

[2] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/rc1/winforms.md

[3] https://github.com/dotnet/runtime/issues/68162

[4] https://github.com/dotnet/aspnetcore/issues/54365

[5] https://avaloniaui.net/blog/net-maui-is-coming-to-linux-and-the-browser-powered-by-avalonia

The post .NET 10.0 RTM: Alle Neuerungen seit dem Release Candidate im Überblick appeared first on BASTA!.

]]>
Future Coding: Wie KI und MCP den Entwickleralltag revolutionieren https://basta.net/blog/future-coding-ki-mcp-softwareentwicklung/ Tue, 14 Oct 2025 11:00:42 +0000 https://basta.net/?p=107885 KI, das große Reizwort der Branche und darüber hinaus, stand auch im Fokus der diesjährigen BASTA! Herbst Eröffnungskeynote. Die Experten Malte Lantin und Rainer Stropek verzichteten dabei jedoch darauf, eine unklare Zukunftsvision zu entwerfen, sondern zeigten ganz konkret auf, was mit Künstlicher Intelligenz im Entwicklungsprozess heute schon erreicht werden kann, wie und wo KI den Entwickleralltag verbessert, und – ganz wichtig – worauf Entwickler:innen dabei achten müssen.

The post Future Coding: Wie KI und MCP den Entwickleralltag revolutionieren appeared first on BASTA!.

]]>
Malte Lantin ist Solutions Engineer bei GitHub. Er arbeitet mit einigen der größten Kunden von GitHub und hilft dabei, GitHub als ihre zentrale DevSecOps-Plattform zu etablieren und ihre Softwareentwicklung durch KI-gestützte Entwicklerwerkzeuge zu verbessern. Bevor er 2021 zu GitHub kam, verbrachte er mehr als 10 Jahre bei Microsoft, wo er Erfahrungen als Software Programm Manager, Software Engineer und Technical Evangelist sammelte. In seiner Freizeit ist er Co-Moderator eines der beliebtesten Software Engineering-Podcasts in Deutschland, dem todo:cast Developer Podcast.

 

Rainer Stropek ist seit über zwanzig Jahren als Unternehmer in der IT-Branche tätig. Er gründete und leitete in dieser Zeit mehrere IT-Dienstleistungsunternehmen und entwickelt derzeit mit seinem Team in seinem Unternehmen software architects die preisgekrönte Software time cockpit. Rainer hat Abschlüsse von der Höheren Technischen Lehranstalt für MIS, Leonding (AT) und der University of Derby (UK). Er ist Autor von mehreren Fachbüchern und Zeitschriftenartikeln im Bereich Microsoft .NET und C#. Er tritt regelmäßig als Redner und Trainer auf renommierten Konferenzen in Europa und den USA auf. Im Jahr 2010 wurde er von Microsoft zu einem der ersten MVPs für die Windows Azure-Plattform ernannt. Seit 2015 ist er zudem Microsoft Regional Director.

Die Experten zeigten, wie KI nicht nur Werkzeuge verändert, sondern ganze Denkweisen und Arbeitsweisen prägt, insbesondere durch die Integration intelligenter Assistenten in Entwicklungsprozesse und die Entstehung einer neuen Generation von ‘AI Natives’. MCP wird als Standard vorgestellt, der die Interaktion zwischen Benutzer, KI und Anwendungen erleichtert und viele Routineaufgaben automatisiert, ohne die Bedeutung sorgfältig gestalteter Benutzeroberflächen zu verdrängen.

Die wichtigsten Take-aways der Keynote:

  1. Wendepunkt in der Softwareentwicklung durch KI
    KI verändert nicht nur die Werkzeuge, sondern auch Denkweisen und die Art der Zusammenarbeit in der Softwareentwicklung grundlegend. Es entsteht eine neue Generation von „AI Natives“, die von Anfang an mit intelligenten Assistenten programmieren und dadurch einen neuen Blick auf Softwareentwicklung haben.
  2. Integration von KI tief in Entwicklungsprozesse
    GitHub demonstriert, wie KI tief in den Entwicklungsprozess eingebunden werden kann, um die Produktivität und Qualität zu steigern. Dabei geht es nicht nur um das reine Schreiben von Code, sondern um eine umfassende Unterstützung über den gesamten Entwicklungszyklus.
  3. Model Context Protocol (MCP) als Schlüsseltechnologie
    MCP bildet die Brücke zwischen Benutzer, KI und bestehenden Anwendungen. Es ermöglicht eine nahtlose Interaktion und Automatisierung vieler Routineaufgaben, die bisher manuell über APIs oder Benutzeroberflächen erledigt wurden.
  4. Zukunft der Benutzeroberflächen
    Trotz der zunehmenden Bedeutung von KI und MCP wird es weiterhin sorgfältig gestaltete Benutzeroberflächen geben. UI-Entwicklung bleibt ein spannendes und wichtiges Feld, da KI-gestützte Interfaces nicht automatisch gute User Experiences garantieren.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Jetzt die gesamte Keynote ansehen und mehr erfahren!

The post Future Coding: Wie KI und MCP den Entwickleralltag revolutionieren appeared first on BASTA!.

]]>
Avalonia: Das bessere WPF für moderne Cross-Platform-Apps https://basta.net/blog/avalonia-cross-platform-dotnet/ Tue, 19 Aug 2025 11:17:43 +0000 https://basta.net/?p=107744 Avalonia als Cross-Platform-UI-Framework orientiert sich stark an WPF, bringt viele Verbesserungen mit und unterstützt eine breite Anzahl von Plattformen. Für .NET Frontend- und FullStack-Entwickler ist es damit mehr als einen Blick wert.

The post Avalonia: Das bessere WPF für moderne Cross-Platform-Apps appeared first on BASTA!.

]]>
Es gibt eine lange Liste von UI-Frameworks für C# und .NET – allein Microsoft hat mit Windows Forms, WPF, WinUI, .NET MAUI und ASP.NET Core Blazor ein vielseitiges Angebot im Programm. Wenn wir eine Applikation für Windows entwickeln wollen, haben wir also die Qual der Wahl. Unter macOS wird die Liste bereits kleiner, mit .NET MAUI und ASP.NET Core Blazor stehen davon nur noch zwei Frameworks zur Auswahl. Möchte man für Linux entwickeln, so fällt schließlich auch .NET MAUI raus und wir haben nur noch ASP.NET Core Blazor übrig. Hier kann allerdings noch etwas fehlen, da es einen Browser benötigt, innerhalb dessen der HTML-Content gerendert werden kann. ASP.NET Core Blazor Hybrid als Container ist hierfür eine gängige Variante, unter Linux kann sie aber nicht verwendet werden. Wir müssten uns dort selbst um einen Container für die ASP.NET-Core-Blazor-Applikation kümmern oder ein entsprechendes Open-Source-Projekt suchen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

In diesem Artikel beschäftigen wir uns mit einer völlig anderen Variante: Avalonia, ein Open-Source-UI-Framework. Es basiert auf .NET und steht für alle genannten Plattformen zur Verfügung. Darüber hinaus unterstützt es auch mobile Geräte (Android, iOS) und sogar den Browser. Avalonia ist nicht von Microsoft, sondern ein communitygetriebenes, unabhängiges Projekt, das bereits vor über einem Jahrzehnt entstand. Im Jahr 2023 hat das Team hinter Avalonia UI bereits das 10-jährige Jubiläum gefeiert.

Um Avalonia zu verstehen und einschätzen zu können, muss man sich damit beschäftigen, wo das Framework seine Wurzeln hat. Es ist als Cross-Platform-UI-Framework für den Desktop mit Unterstützung für Windows, macOS und Linux gestartet. Es hat viele Ideen und Konzepte von WPF übernommen – WPF war zu dieser Zeit noch der De-facto-Standard für UI-Entwicklung in .NET für den Desktop. Avalonia hat WPF allerdings nicht blind nachgebaut, sondern an vielen Stellen weiterentwickelt und verbessert. Man könnte es auch das bessere WPF nennen.

Die hohe Ähnlichkeit zu WPF sehen wir beispielhaft am XAML-Code eines Fensters in Listing 1. Dieser Code könnte genauso fast eins zu eins in WPF genutzt werden. Die XAML-Syntax ist gleich, die Namespaces sind überwiegend gleich und auch die hier verwendeten Controls sind gleich. Der einzige Unterschied ist der Namespace https://github.com/avaloniaui in der ersten Zeile.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Testing.AvaloniaArticle.MainWindow"
        Title="Greeting">
  <TextBlock HorizontalAlignment="Center"
             VerticalAlignment="Center"
             FontSize="36"
             Text="Hello, Avalonia!" />
</Window>

Eine IDE auswählen

Bei der Auswahl der IDE steht für die meisten C#-Entwickler:innen Visual Studio von Microsoft ganz oben auf der Liste. Das Avalonia-Team bietet hierfür ein Plug-in an, das alle notwendigen Features wie XAML-IntelliSense oder Live-Preview mitbringt. Speziell für die Entwicklung mit Avalonia steht mit der IDE Rider von JetBrains eine weitere Möglichkeit zur Verfügung. Das Besondere daran ist, dass damit auch unter Linux und macOS entwickelt werden kann und es eine hervorragende Unterstützung für Avalonia mitbringt, ohne ein Plug-in installieren zu müssen. Die Firma JetBrains nutzt Avalonia selbst und hat eigene Applikationen wie dotTrace und dotMemory mit Hilfe von Avalonia für macOS und Linux fit gemacht. Aus diesem Grund haben sie die notwendige Unterstützung in der IDE selbst in die Hand genommen.

SIE LIEBEN UI?

Entdecken Sie die BASTA! Tracks

Als dritte Alternative ist es auch möglich, Avalonia-Applikationen mit Visual Studio Code von Microsoft zu entwickeln. Auch hier stellt das Avalonia-Team ein Plug-in bereit, weist aber darauf hin, dass man hier im Vergleich zu den anderen IDEs die schlechteste Unterstützung für Avalonia vorfinden wird [1].

Mit Avalonia starten

Avalonia stellt mehrere Templates bereit, mit denen wir starten können. Diese werden vom Avalonia-Team in einem separatem GitHub-Repository gepflegt [2]. Mit dem Kommandozeilenbefehl dotnet new install Avalonia.Templates werden sie installiert. Zum Zeitpunkt der Veröffentlichung dieses Artikels erhalten wir damit folgende Templates:

  • Avalonia .NET App
  • Avalonia .NET MVVM App
  • Avalonia Cross Platform Application

Falls wir für Linux und/oder macOS entwickeln wollen, blicken wir womöglich zuerst auf die letzte Variante, Avalonia Cross Platform Application. Tatsächlich wäre aber die erste oder die zweite Variante sinnvoller. Hintergrund ist, dass es sich dabei um die Templates für reine Desktopapplikationen handelt. Solche sind bei Avalonia von vornherein Cross-Platform, also unter Windows, Linux und macOS lauffähig. Der Begriff „Cross-Platform“ im letztgenannten Template geht noch weiter, es zielt zusätzlich auf Android, iOS und den Browser. Für den Einstieg eignet sich das Template Avalonia .NET App sehr gut.

Es gibt noch eine weitere Frage, die es an dieser Stelle zu klären gilt. Wir können CompiledBindings für unsere Applikation nutzen. Standardmäßig ist das Häkchen dafür beim Template aktiv. Es handelt sich um keine endgültige Entscheidung, es empfiehlt sich aber, es immer aktiv zu lassen. CompiledBindings sorgen dafür, dass die Bindings im XAML-Code kompiliert werden – anders als etwa bei WPF. Bei WPF werden Bindings mittels Reflection zur Laufzeit aufgelöst. CompiledBindings bringen uns eine bessere Performance. Zusätzlich bekommen wir beim Kompilieren bereits Feedback in Form von Compilerfehlern, falls es etwa Tippfehler gibt.

Das CompiledBinding erklärt

Technisch basiert das CompiledBinding auf Codegenerierung. Diese beginnt allerdings nicht beim Binding, sondern bereits bei den XAML-Dateien. Anders als bei WPF werden XAML-Dateien nicht in das Zwischenformat BAML übersetzt, sondern direkt in C#-Code – eine der vielen Performanceverbesserungen gegenüber WPF.

Zur Veranschaulichung modifizieren wir das Beispielfenster aus Listing 1 so, dass die Begrüßung nicht fest in XAML hinterlegt ist, sondern aus einem ViewModel kommt. Das ViewModel dazu ist denkbar einfach, da wir keinerlei Änderungsbenachrichtigung (INotifyPropertyChanged) für diesen Zwecke implementieren müssen. Die Klasse MainWindowViewModel in Listing 2 ist völlig ausreichend.

public class MainWindowViewModel
{
  public string Greeting => "Hello, Avalonia!";
}

In Listing 3 sehen wir, welche Änderungen in XAML notwendig sind, um das CompiledBinding zur Anbindung des MainWindowViewModel zu nutzen. Der wesentliche Unterschied ist das Setzen der Eigenschaft x:DataType. Damit sagen wir dem Compiler, welchen Typ wir bei dem gebundenen DataContext erwarten.

<Window ...
        xmlns:local="clr-namespace:Testing.AvaloniaArticle"
        x:DataType="local:MainWindowViewModel">
  <TextBlock ...
             Text="{Binding Greeting}" />
</Window>

Damit das Beispiel funktioniert, darf nicht vergessen werden, eine Instanz des MainWindowViewModel zu erstellen und der Eigenschaft DataContext des Hauptfensters zuzuweisen. An dieser Stelle reagiert Avalonia exakt so wie WPF.

Ein MVVM-Framework auswählen

MVVM (Model View ViewModel) ist bei XAML-Frameworks nach wie vor das beliebteste Pattern, das gilt auch für Avalonia. Bei der Auswahl des MVVM-Frameworks sind folgende zwei Varianten für Avalonia-Projekte am häufigsten anzutreffen:

  • CommunityToolkit Mvvm
  • ReactiveUI

Aus Sicht des Autors ist es meist das Einfachste, mit CommunityToolkit.Mvvm zu starten. ReativeUI bietet zwar mehr Features und hat mit dem NuGet-Paket Avalonia.ReactiveUI eine sehr gute Integration in Avalonia, aber die Einstiegshürde ist höher, da ReactiveUI auf den Reactive Extensions for .NET aufbaut. In diesem Artikel wird aus Gründen des Umfangs nicht tiefer darauf eingegangen. Es wäre aber ratsam, ReactiveUI nur dann zu nutzen, wenn ein Verständnis der Reactive Extensions for .NET vorliegt.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

CommunityToolkit.Mvvm kommt dagegen leichtgewichtig daher und kümmert sich im Wesentlichen um die für das MVVM-Pattern notwendige Implementierung von INotifyPropertyChanged und INotifyDataErrorInfo. Zusätzlich bringt es Klassen für synchrone und asynchrone Commands und weitere eher kleine Hilfsmittel mit. Eine Besonderheit des CommunityToolkit.Mvvm ist die Nutzung von Roslyn Source Generators, um typischen Boilerplate-Code rund um INotifyPropertyChanged-Implementierungen zu generieren.

In Listing 4 sehen wir das CommunityToolkit.Mvvm zusammen mit Avalonia in Aktion. Das MainWindowViewModel erweitern wir um das Property Name, das direkt Einfluss auf das Property Greeting hat. Wir definieren dafür lediglich den Member _name, um das Property kümmert sich das CommunitityToolkit per Source Generator selbst. Das Ereignis PropertyChanged müssen wir ebenfalls nicht selbst auslösen – die Attribute ObservableProperty und NotifyPropertyChangedFor sagen dem CommunityToolkit, wie es PropertyChanged auslösen soll. Im Hintergrund wird das Property Name durch das CommunityToolkit generiert, darin wird PropertyChanged für Name und Greeting aufgerufen. Anschließend brauchen wir im XAML-Code nur noch dagegen zu binden.

// MainWindowViewModel.cs
public partial class MainWindowViewModel : ObservableObject
{
  [ObservableProperty]
  [NotifyPropertyChangedFor(nameof(Greeting))]
  private string _name = string.Empty;

  public string Greeting => string.IsNullOrEmpty(this.Name)
    ? string.Empty
    : $"Hello, {this.Name}";
}

// MainWindow.axaml
<Window ...>
  <Border VerticalAlignment="Center" HorizontalAlignment="Center"
          Classes="InputArea">
    <StackPanel Orientation="Vertical">
      <StackPanel Orientation="Horizontal">
        <TextBlock Classes="Label" 
                   Text="Name: " />
        <TextBox Text="{Binding Name}" />
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Classes="Label" 
                   Text="Greeting: " />
        <TextBox Text="{Binding Greeting}" />
      </StackPanel>
    </StackPanel>
  </Border>
</Window>

Styling mit Inspiration vom Web

Einer der größten Unterschiede im XAML-Code zwischen WPF und Avalonia ist das Styling-System. Anders als bei anderen XAML-Frameworks wie WPF wurde bei Avalonia das Verhalten von CSS nachgebaut. Ein Style ersetzt also das Styling eines Controls nicht komplett, sondern ergänzt es. Dabei können beliebig viele Styles auf ein und dasselbe Control wirken.

 

Listing 5 soll hierzu einen ersten Eindruck geben, es definiert einen Style für das vorangegangene Beispiel aus Listing 4. Jeder Style definiert über einen Selector, auf welche Controls er wirkt. Der einfachste Fall ist die Angabe des Typnamens, hier im Beispiel gehen wir bei Border.InputArea bereits etwas weiter. Border ist der Typ des Controls, InputArea ist der Klassenname (siehe Eigenschaft Classes bei dem Border-Control in Listing 4). Innerhalb des Styles definieren wir weitere sogenannte Nested Styles, die auf Child Controls wirken. Das Ergebnis sehen wir in Abbildung 1.

<Style Selector="Border.InputArea">
  <Setter Property="Background" Value="#88888888" />
  <Setter Property="Padding" Value="10" />
  <Setter Property="CornerRadius" Value="10" />
  
  <Style Selector="^ TextBox">
    <Setter Property="Width" Value="250" />
  </Style>
  <Style Selector="^ StackPanel">
    <Setter Property="Spacing" Value="5" />
  </Style>
  <Style Selector="^ TextBlock.Label">
    <Setter Property="Width" Value="100" />
    <Setter Property="VerticalAlignment" Value="Center" />
  </Style>
</Style>
Beispiel einer Avalonia-App mit Eingabefeld für den Namen und dynamischer Begrüßungsausgabe im Fenster.

Abb. 1: Begrüßungsdialog mit angewendetem Styling

Hier am Beispiel sehen wir nur einen kleinen Bruchteil der Möglichkeiten des Styling-Systems von Avalonia. Das Avalonia-Team beschreibt in der Dokumentation eine lange Liste von möglichen Selektoren [3] – ein Blick lohnt sich. Das System wird ebenso regelmäßig erweitert, etwa durch die mit Version 11.3 eingeführten und an CSS Media Queries angelehnten Container Queries.

Avalonia DevTools

Features wie Hot Reload oder als Alternative einen visuellen Designer, wie wir ihn etwa noch aus den guten alten WinForms-Zeiten kennen, fehlen in Avalonia. Mit Avalonia Accelerate arbeitet das Avalonia-Team zurzeit genau an diesen Punkten. Avalonia Accelerate ist ein kostenpflichtiges Produkt, das Zusatzfeatures für Entwickler:innen von Avalonia-Applikationen enthält.

Im Open-Source-Paket von Avalonia werden schon länger integrierte DevTools angeboten, die über das NuGet-Paket Avalonia.Diagnostics bereitgestellt werden. Die beschriebenen Templates binden das NuGet-Paket direkt mit ein. Ein Tastendruck auf F12 reicht, um das DevTools-Fenster zu öffnen. Die DevTools helfen bei mehreren Aufgaben, so lassen sich Logical Tree und Visual Tree prüfen, ob sie die erwarteten Inhalte aufweisen. Der Logical Tree bildet dabei die Baumstruktur aus den XAML-Dateien ab. Der Visual Tree zeigt, was tatsächlich am Bildschirm gerendert wird – aufgrund von Templating i. d. R. deutlich mehr. Abbildung 2 zeigt, wie die DevTools von Avalonia aussehen.

Screenshot der Avalonia DevTools mit Ansicht des Logical Tree, Property Inspector und Layout Visualizer.

Abb. 2: Avalonia DevTools

Noch ein Hinweis an alle Leser:innen, die Hot Reload oder einen Designer vermissen. In den DevTools können Werte von Eigenschaften direkt live geändert werden. Damit sieht man sofort, welche Änderungen zu welchen Effekten auf der Benutzeroberfläche führen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Neben der Anzeige der Baumstrukturen bieten die DevTools weitere Features wie ein Tracking der von den Controls ausgelösten Events. Ebenfalls können performancerelevante Kennzahlen wie Rendering- und Layoutzeit eingeblendet werden. Unterm Strich sind die DevTools auf jeden Fall mehr als einen Blick wert.

Architektur von Avalonia

Nachdem wir uns in diesem Artikel mit einigen Eigenschaften von Avalonia beschäftigt haben, möchten wir unseren Blick noch einmal von der Vogelperspektive auf das UI-Framework werfen. Das hilft uns dabei, das UI-Framework noch besser zu verstehen und die Vor- und Nachteile gegenüber anderen UI-Frameworks abschätzen zu können.

Abbildung 3 gibt uns dafür einen groben Überblick. Ganz oben steht dabei Avalonia zusammen mit allen in Avalonia zur Verfügung gestellten Controls. Darunter liegt Skia. Skia ist ein Open-Source-2D-Rendering-Framework von Google, das unter anderem auch in modernen Browsern wie Google Chrome eingesetzt wird. Ähnlich wie auch Flutter nutzt Avalonia Skia zum Rendering aller Controls. Unterhalb von Skia liegt entweder die Core CLR oder die Mono Runtime – je nachdem, auf welcher Plattform oder auf welchem Gerät wir eine Avalonia-Applikation ausführen.

Diagramm der Avalonia-Architektur: Avalonia UI, Skia Graphics Engine, Core CLR/Mono Runtime sowie unterstützte Plattformen Windows, macOS, Linux, iOS, Android und WebAssembly.

Abb. 3: Architektur von Avalonia (eigene Darstellung nach [4])

Als logische Konsequenz aus dieser Architektur verhalten sich Avalonia-Applikationen auf allen unterstützten Plattformen weitgehend gleich und sehen gleich aus. Im Vergleich zu Avalonia gibt es auch andere UI-Frameworks, die einen ähnlichen Ansatz verfolgen. WPF etwa rendert auch alle Controls selbst, baut allerdings auf das plattformabhängige Direct3D auf. Auch bei anderen Programmiersprachen finden wir Beispiele, etwa beim bereits angesprochenen Flutter von Google oder dem für C++-Entwickler:innen gedachten Qt.

Im Vergleich zu anderen UI-Frameworks in .NET ist der Ansatz von Avalonia nicht häufig anzutreffen. .NET MAUI beispielsweise geht einen anderen Weg. Dort werden Controls nicht selbst gerendert, sondern die jeweiligen nativen Controls der Plattformen genutzt. Ein Vorteil davon ist das native Look and Feel, ein Nachteil ist der deutlich größere Testaufwand. Aus Sicht des Autors ist es wichtig, solche Unterschiede zu kennen, sobald es um die Entscheidung für oder gegen ein UI-Framework geht.

Stärke am Desktop, Potenzial auf anderen Plattformen

Historisch hat Avalonia seine größte Stärke am Desktop. Die Unterstützung für die mobilen Plattformen Android und iOS ist zusammen mit der Unterstützung für den Browser erst in Version 11 am 7. Juli 2023 veröffentlicht worden. Entscheidet man sich auf diesen Plattformen für Avalonia, muss man damit rechnen, auf die eine oder andere Lücke zu stoßen. So bietet Avalonia von Haus aus kein Navigations-Control mit Behandlung des Zurück-Knopfs – andere UI-Frameworks mit Fokus auf mobile Plattformen wie .NET MAUI haben hier mehr zu bieten.

Das Potenzial, das Avalonia auf mobilen Plattformen hat, kann man mit einem Blick auf Flutter erkennen. Flutter ist von seiner Architektur her sehr ähnlich zu Avalonia und ist insbesondere für Cross-Platform-Applikationen für mobile Geräte ein sehr beliebtes UI-Framework außerhalb der .NET-Welt.

Die Community rund um Avalonia

Avalonia ist nicht nur aus der Community entstanden, die Community ist auch eine der größten Stärken des UI-Frameworks. Das Avalonia-Team pflegt eine Liste von Anlaufstellen, um sich mit der Community auszutauschen [5]. Dazu gehören ein Forum innerhalb des GitHub-Projekts auf Basis von GitHub Discussions und diverse Chats via Telegram und Discord.

Daneben existiert eine große Zahl von Open-Source-Projekten rund um Avalonia. Das GitHub-Projekt awesome-avalonia [6] ist ein guter Startpunkt, um Projekte zu finden, die für das eigene Projekt relevant sind.

Die Zukunft von Avalonia

Avalonia ist ein Open-Source-Projekt – mit dieser Tatsache steht für viele Interessierte die Frage im Raum, inwieweit das Team hinter Avalonia langfristig das Framework pflegt. Erfreulicherweise veröffentlicht Mike James, der CEO von Avalonia, regelmäßig Informationen darüber, wo die Prioritäten des Teams liegen und wie das Team aus Businesssicht aufgestellt ist. So beschreibt er in einem Blogpost vom 2. Dezember 2024, wie sich das Team zu diesem Zeitpunkt finanziert [7]. Der größte Einnahmeblock ist Avalonia XPF – ein kommerzielles Schwesterframework, das die Migration von WPF-Anwendungen auf andere Betriebssysteme erleichtert und zugleich einen Migrationspfad hin zu Avalonia bietet. Dieser Anteil dürfte sich im Juni 2025 geändert haben, denn zu diesem Zeitpunkt hat das Avalonia-Team eine 3 Millionen US-Dollar große Spende von der kanadischen Firma Devolutions erhalten [8].

Fazit

Avalonia gibt uns alle notwendigen Werkzeuge an die Hand, um Benutzeroberflächen für die verschiedensten Plattformen zu entwickeln. Insbesondere finden sich Entwickler:innen mit Erfahrung in XAML-basierten Frameworks wie WPF schnell zurecht. Aber auch der Umstieg von anderen Frameworks wie dem nach wie vor verbreiteten Windows Forms, ist kein Ding der Unmöglichkeit – hier muss allerdings Zeit zum Lernen der XAML-Syntax und des MVVM-Patterns hinzugerechnet werden.

Gerade der Support für Linux dürfte für viele Lesende spannend sein, da Microsoft selbst hier eine Lücke hinterlässt. Linux spielt in verschiedenen Bereichen der Industrie und insbesondere in der Embedded-Welt eine sehr große Rolle. Avalonia kann hier als mächtiges UI-Framework mit ebenso mächtigem .NET-Unterbau punkten.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Das Avalonia-Team stellt sich aus Sicht des Autors sinnvoll für die Zukunft auf. Sie erweitern das Open-Source-Paket mit Avalonia Accelerate um kommerzielle Zusatzkomponenten, die für professionelle Entwickler:innen zusätzliche Produktivität und Features liefern. Auch Avalonia XPF kann gerade bei einer größeren Migration eines WPF-Projekts spannend sein. Ganz nebenbei finanzieren diese Produkte die Weiterentwicklung am Open-Source-Paket Avalonia selbst, das auch für kommerzielle Projekte kostenlos zur Verfügung steht.

 

Frequently Asked Questions (FAQ)

1. Was ist Avalonia und wie unterscheidet es sich von WPF?

Avalonia ist ein Open-Source, Cross-Platform UI-Framework auf .NET-Basis, das von WPF inspiriert ist. Syntax und Controls sind nahezu identisch zu WPF, allerdings wurde die Architektur modernisiert und die Plattformunterstützung erheblich erweitert.

2. Welche Plattformen unterstützt Avalonia?

Ursprünglich unterstützte Avalonia nur Windows, macOS und Linux. Seit Version 11 (Juli 2023) sind zusätzlich Android, iOS sowie Browser-basierte Anwendungen (über WebAssembly) möglich.

3. Welche IDEs eignen sich für die Entwicklung mit Avalonia?

Visual Studio bietet mit einem offiziellen Plugin XAML-IntelliSense und Live Preview. JetBrains Rider hat die beste Integration out-of-the-box. Visual Studio Code kann ebenfalls genutzt werden, ist aber im Funktionsumfang am wenigsten komplett.

4. Wie startet man ein neues Avalonia-Projekt?

Über dotnet new install Avalonia.Templates lassen sich offizielle Templates installieren. Für Desktop empfiehlt sich „Avalonia .NET App“ oder „Avalonia .NET MVVM App“. Wer zusätzlich Android, iOS und Browser ansprechen möchte, nutzt „Avalonia Cross Platform Application“.

5. Was sind CompiledBindings und welche Vorteile bieten sie?

CompiledBindings generieren aus XAML bereits zur Compile-Zeit C#-Code. Dadurch wird Reflexion zur Laufzeit vermieden, die Performance steigt und Bindungsfehler werden bereits beim Kompilieren erkannt.

6. Welche MVVM-Frameworks werden mit Avalonia häufig eingesetzt?

Am verbreitetsten sind CommunityToolkit.Mvvm und ReactiveUI. Ersteres ist leichtgewichtig und nutzt Source Generators, während ReactiveUI sehr mächtig ist, aber auf Reactive Extensions basiert und eine höhere Lernkurve hat.

7. Wie funktioniert das Styling in Avalonia?

Avalonia orientiert sich am CSS-Modell. Styles können per Typ- oder Klassenselektoren gesetzt werden, mehrere Styles pro Control sind möglich, ebenso wie verschachtelte Styles. Mit Version 11.3 kamen moderne Features wie CSS-ähnliche Container Queries hinzu.

8. Welche Entwickler-Tools bietet Avalonia und was ist Avalonia Accelerate?

Mit Avalonia.Diagnostics stehen Open-Source DevTools bereit, die u. a. den Logical Tree, Visual Tree, Events und Performance-Daten live inspizieren lassen. Avalonia Accelerate ist ein kostenpflichtiges Zusatzpaket mit erweiterten Features wie Visual Designers und verbessertem Hot Reload.


Links & Literatur

[1] https://docs.avaloniaui.net/docs/get-started/set-up-an-editor

[2] https://github.com/AvaloniaUI/avalonia-dotnet-templates

[3] https://docs.avaloniaui.net/docs/reference/styles/style-selector-syntax

[4] https://docs.avaloniaui.net/docs/overview/what-is-avalonia

[5] https://docs.avaloniaui.net/docs/community

[6] https://github.com/AvaloniaCommunity/awesome-avalonia

[7] https://avaloniaui.net/blog/avalonia-ui-in-2024-growth-challenges-and-the-road-ahead

[8] https://github.com/AvaloniaUI/Avalonia/discussions/19108

 

The post Avalonia: Das bessere WPF für moderne Cross-Platform-Apps appeared first on BASTA!.

]]>
Kritische SharePoint-Sicherheitslücke CVE-2025-53770: Exploit aktiv im Umlauf https://basta.net/blog/sharepoint-cve-2025-53770-zero-day-exploit/ Thu, 24 Jul 2025 07:27:22 +0000 https://basta.net/?p=107714 Am 19.07.2025 wurde eine gefährliche Sicherheitslücke in Microsofts Software-Urgestein SharePoint bekannt. Wir werfen einen Blick auf die Hintergründe und erforderlichen Maßnahmen.

The post Kritische SharePoint-Sicherheitslücke CVE-2025-53770: Exploit aktiv im Umlauf appeared first on BASTA!.

]]>
Wochenenden sind Freizeit – sollte man meinen. Das traf aber nicht auf Samstag, den 19. Juli 2025, zu. Denn an diesem Tag wurde eine Sicherheitslücke in Microsoft SharePoint bekannt, eine so genannte 0-Day-Lücke. Das bedeutet, dass der Hersteller schon 0 Tage vor Veröffentlichung Bescheid wusste. Häufig ist es so, dass Finder einer Sicherheitslücke die Entwicklungsteams kontaktieren, damit Patches erstellt werden können. Erst nach Verfügbarkeit von neuen Versionen werden Details über die Lücke verraten.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Samstag, 19.07.2025

Exploit bereits aktiv: Microsoft warnt öffentlich

Microsofts Sicherheitsteam (MSRC – Microsoft Security Response Center) hat am Samstagabend (in Deutschland war es da schon Sonntagfrüh) unter anderem auf der Plattform X gemeldet, dass eine Lücke in SharePoint aktiv ausgenutzt wird [1]. Wie üblich wurde auch gleich eine CVE-Nummer vergeben – diese Liste der „Common Vulnerabilities and Exposures“ dient der vereinfachten Nachverfolgung von öffentlich gewordenen Sicherheitslücken. Die zugewiesene Nummer war CVE-2025-53770, also die 53 770. CVE im Jahr 2025 (global, also natürlich nicht nur auf Microsoft oder gar SharePoint bezogen). Der Score-Wert hatte unfassbare 9,8 (auf einer Skala bis 10). Es war also höchste Gefahr in Verzug. Ein Blogeintrag des MSRC verriet weitere Details und wurde in den Folgetagen mehrmals aktualisiert (Abb. 1) [2].

Tabelle mit Revisionshistorie zum Microsoft-Blogpost „Customer guidance for SharePoint vulnerability CVE-2025-53770“ mit Änderungen und Datumsangaben der Versionen 1.0 (Information veröffentlicht am 19.07.2025) und 2.0 (Klarstellung betroffener Produkte und Fix-Hinweise am 20.07.2025).


Abb. 1: Microsofts Handreichung für betroffene Kunden

ToolShell-Analyse legt weitere Details offen

Das allein ist ja schon schlimm genug, aber von anderer Seite wurden Informationen nachgelegt. Die Sicherheitsforscher von Eye Security waren ebenfalls auf die Sicherheitslücke aufmerksam geworden [3]. Sie haben die Lücke selbst gar nicht gefunden, aber bereits am 18.07.2025, also am Vortag der Warnung des MSRC, die mehrfache Ausnutzung festgestellt. Ein griffiger Name war schnell gefunden: „ToolShell“. Zudem haben sie Informationen zusammengetragen und somit einige zusätzliche Details veröffentlicht, die im MSRC-Post noch gefehlt haben – wichtig für potenziell betroffene Firmen, weil nun sehr konkret zu sehen war, dass Handlungsbedarf besteht. Der Blogpost war sehr detailliert, sodass mit entsprechender Detektivarbeit der Exploit nachgebaut werden konnte.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

So funktioniert der Angriff: MachineKey im Visier

Kern des Problems war eine unsichere Deserialisierung einer der in ASP.NET Web Forms(!) entwickelten Seiten von SharePoint. In der Payload eines HTTP Requests konnten serialisierte Informationen an den Server geschickt werden und im Rahmen der Deserialisierung kam es dann zur Codeausführung. Bei den massenhaft erfolgten Angriffen wurde eine besonders perfide Variante der Codeeinschleusung gewählt: Der Exploit legte ein weiteres Web Form an, das dann per HTTP GET Request mehrere interne Serverinformationen inklusive dem MachineKey des Systems auslieferte. Gerade der MachineKey ist bei den meisten ASP.NET-Installationen zentral, dient er doch unter anderem als kryptografischer Schlüssel für den Zustandsverwaltungsmechanismus Viewstate. Ist dieser einem Angreifer bekannt, hat dieser bei SharePoint im Wesentlichen freie Hand, solange der MachineKey gültig ist – das kann selbst nach einem Patch so sein!

Frühere Lücken bieten Angriffsfläche

Es stellte sich schließlich heraus, dass die neue Verwundbarkeit eine Weiterentwicklung zweier erst jüngst bekannt gewordener Sicherheitslücken in SharePoint, CVE-2025-49704 [4] und CVE-2025-49706 [5] ist. Sie sehen es an der Nummer hinter der Jahreszahl: kleiner als 53770, aber in Schlagdistanz. Es zeigte sich: Diese CVEs wurden am 8.7.2025 veröffentlicht, der zweite Dienstag im Juli, und damit der turnusmäßige „Patch Tuesday“ von Microsoft. Es erschienen also zeitgleich Patches. Diese halfen aber gegen die neue Lücke offenbar nicht.

 

Sonntag, 20.07.2025

Erste Updates, aber keine Entwarnung

Rosig war die Situation am Morgen des 20.07.2025 also noch nicht. Kein Patch da, und der Blogpost von Microsoft war noch keine große Hilfe. In dessen ersten Fassung wurden als Gegenmaßnahmen unter anderem die Verwendung von Antivirensoftware genannt, dem Antimalware Scan Interface (AMSI) oder Microsoft Defender for Endpoint. Von einem Softwareupdate war da noch nicht die Rede. Dieses wurde am Sonntag, dem 20.07.2025, nachgereicht für SharePoint Subscription Edition [6] und 2019 [7]. Am Folgetag wurde auch für SharePoint Enterprise Server 2016 wird ein Patch veröffentlicht [8]. Außerdem hat Microsoft noch einen weiteren CVE veröffentlicht, CVE-2025-53771. Dieser hat einen niedrigeren Score als CVE-2025-53770, steht aber mit dem Vorgänger in Zusammenhang, und die verlinkten Updates beheben beide Lücken.

Wenn man weiß, wie lange es teilweise zwischen Bekanntwerdung einer Sicherheitslücke und Verfügbarkeit eines Patches dauert, ist das eine beeindruckende Leistung. Offenkundig tat diese auch Not, denn laut Eye Security wurden Dutzende SharePoint-Installationen gefunden, die bereits betroffen waren. Wer auch immer ein öffentlich verfügbares SharePoint betreibt, muss den Server auf Einbruchsspuren untersuchen und den Patch einspielen.

Was Admins jetzt prüfen und anpassen müssen

Mit dem Update von SharePoint allein ist es nicht getan. Gut möglich, dass der MachineKey bereits geklaut wurde. Deswegen ist eine Rotation des Schlüssels Pflicht. In der Regel ist das mit einem Neustart des IIS getan.

Montag, 21.07.2025

Fazit: Sicherheitsprozesse auf den Prüfstand stellen

Gleichwohl gilt es, diese Episode als Anlass zu nehmen, die Sicherheitsprozesse im Unternehmen zu prüfen und gegebenenfalls zu hinterfragen. Wenn Sie potenziell betroffen sind, fragen Sie sich selbst: Wo haben Sie erstmals von diesem Problem gehört – durch das MSRC, eine entsprechende Überwachungssoftware, oder gar diesen Artikel? Eine hundertprozentige Sicherheit kann und wird es nicht geben, aber ein zeitnahes Einspielen von Sicherheitspatches ist in aller Regel zwingend. In einem Fall wie diesem, bei dem der Exploit noch vor der Verfügbarkeit eines Softwareupdates existiert, sind öffentlich erreichbare Systeme ein einfaches Ziel. Und auch interne Systeme könnten betroffen sein, dann eben nur mit kleinerem Angreiferkreis. Der Exploit ist direkt einsetzbar im Internet zu finden und benötigt keine besonderen technischen Fähigkeiten. Es herrscht also weiterhin Alarmstufe Rot – zumindest bis Ihre SharePoint-Installationen aktualisiert wurden.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Frequently Asked Questions (FAQ)

1. Was ist CVE-2025-53770 und wann wurde sie öffentlich bekannt?

CVE‑2025‑53770 ist eine kritische Zero‑Day‑Sicherheitslücke in Microsoft SharePoint, mit einem CVSS‑Score von 9,8, die aktiv ausgenutzt wurde. Sie wurde am 19. Juli 2025 öffentlich bekanntgegeben, als Microsofts MSRC via X (ehemals Twitter) warnte.

2. Wie funktioniert der “ToolShell”-Exploit technisch?

Der “ToolShell”-Exploit nutzt unsichere Deserialisierung in ASP.NET Web Forms von SharePoint aus. Code wird ausgeführt, und ein Web‑Form wird genutzt, um interne Informationen wie den MachineKey zu extrahieren – dieser Schlüssel erlaubt Angreifern persistente Kontrolle auch nach Patches.

3. In welchem Kontext wurde CVE-2025-53770 gefunden?

CVE‑2025‑53770 stellt eine Weiterentwicklung (Patch‑Bypass) vorheriger Lücken CVE‑2025‑49704 und CVE‑2025‑49706 dar, die bereits am Patch‑Tuesday im Juli veröffentlicht wurden, aber gegen die neue Variante nicht wirksam waren.

4. Wann wurden erste Patches veröffentlicht, und für welche Versionen?

Ein Patch für SharePoint Subscription Edition und 2019 wurde am 20. Juli 2025 veröffentlicht. Am 21. Juli 2025 folgte ein Update für SharePoint Enterprise Server 2016. Zusätzlich wurde mit CVE‑2025‑53771 eine zweite, verwandte Lücke benannt – und beide Patches beheben beide Schwachstellen.

5. Sind Patches allein ausreichend zur Sicherung?

Nein. Das bloße Einspielen des Updates reicht nicht aus, da der MachineKey bereits gestohlen sein kann. Administrator:innen sollten diesen unbedingt rotieren – meist durch einen IIS‑Neustart –, um sicherzustellen, dass ein Angreifer keine andauernde Kontrolle behält.

6. Was sollte als Lehre aus diesem Vorfall gezogen werden?

Unternehmen sollten ihre Sicherheitsprozesse überdenken: Wie schnell haben Sie erfahren — durch Microsoft, Monitoring oder externe Quellen — von dieser Lücke? Zeitnahes Patching ist entscheidend, insbesondere bei öffentlich erreichbaren Systemen, deren Exploits leicht nutzbar sind.

7. Wann fanden die ersten aktiven Ausnutzungen statt?

Sicherheitsforscher von Eye Security beobachteten die Ausnutzung bereits am 18. Juli 2025, einen Tag bevor Microsoft öffentlich warnte. Der Name “ToolShell” wurde von ihnen geprägt.


Links & Literatur

[1] https://x.com/msftsecresponse/status/1946737930849939793

[2] https://msrc.microsoft.com/blog/2025/07/customer-guidance-for-sharepoint-vulnerability-cve-2025-53770/

[3] https://research.eye.security/sharepoint-under-siege/

[4] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-49704

[5] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-49706

[6] https://www.microsoft.com/en-us/download/details.aspx?id=108285

[7] https://www.microsoft.com/en-us/download/details.aspx?id=108286

[8] https://www.microsoft.com/en-us/download/details.aspx?id=108288

The post Kritische SharePoint-Sicherheitslücke CVE-2025-53770: Exploit aktiv im Umlauf appeared first on BASTA!.

]]>
KI-IDEs im Vergleich: Cursor, Windsurf & Copilot https://basta.net/blog/ki-coding-tools-vergleich-cursor-windsurf-copilot/ Mon, 07 Jul 2025 07:25:35 +0000 https://basta.net/?p=107688 KI-gestützte Entwicklungsumgebungen versprechen, die Softwareentwicklung effizienter und zugänglicher zu machen. In diesem Artikel vergleichen wir drei marktführende Lösungen: Cursor, Windsurf (ehemals Codeium) und Visual Studio Code mit GitHub Copilot (im Folgenden „VS Code“). Diese Tools bieten KI-gestützte Programmierunterstützung in Form von automatischer Codevervollständigung, intelligenten Chatbots für Codefragen und Agenten, die selbstständig Aufgaben ausführen können.

The post KI-IDEs im Vergleich: Cursor, Windsurf & Copilot appeared first on BASTA!.

]]>
Trotz ähnlicher Grundkonzepte unterscheiden sich diese Tools deutlich in Bezug auf den Funktionsumfang, die technische Integration und die Alltagstauglichkeit. Um eine fundierte Empfehlung geben zu können, untersuchen wir die technischen Aspekte wie KI-Funktionen, Architektur und Integration in bestehenden Entwicklungsprozessen sowie die praktische Anwendung. Dabei liegt unser Fokus auf der Qualität der Vorschläge und auf deren Alltagstauglichkeit.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Cursor: maximale Kontrolle und Kontexttiefe

Cursor (Abb. 1) richtet sich an Entwickler:innen, die umfassende Kontrolle und eine tiefgehende Kontextverarbeitung suchen [1]. Hervorzuheben ist insbesondere der Agent-Modus, bei dem Cursor in einem isolierten Shadow Workspace arbeitet und Änderungen zunächst vorbereitet, bevor sie in den eigentlichen Code übernommen werden. Dadurch können Entwickler:innen ganze Features autonom erstellen oder komplexe Refactorings durchführen.

Cursor zeichnet sich zudem durch einzigartige Erweiterungen aus: Mit @Web können Webinhalte als Kontext integriert werden, @Docs bindet Dokumentationen ein und Screenshots lassen sich direkt in Code umwandeln. Zusätzlich können Entwickler:innen zwischen KI-Modellen wie GPT, Gemini oder Claude wechseln und sogar eigene Modelle einbinden.

Benutzeroberfläche des KI-Coding-Tools Cursor mit Chatfunktion und Codeübersicht

Abb. 1: Cursor – Chatansicht

Windsurf: ein Flow-orientierter Ansatz

Windsurf (Abb. 2) setzt mit seinem Cascade-Ansatz auf eine intuitive und flüssige Workflowerfahrung [2]. Der klar strukturierte Chat- und Schreibmodus führt Entwickler:innen systematisch durch komplexe Aufgaben. Im Bereich der Webentwicklung überzeugt Windsurf insbesondere durch eine visuelle Vorschau und direkte Deployment-Optionen aus der IDE heraus. Ein lokaler semantischer Index gewährleistet schnellen Zugriff auf relevante Codestellen, während sensible Daten sicher vor Ort bleiben und nicht in die Cloud übertragen werden.

Startbildschirm der IDE Windsurf mit Fokus auf flowbasiertes Coding und Cascade-Interface

Abb. 2: Windsurf – Welcome Screen

GitHub Copilot in VS Code: autonome Agenten und externe Inhalte

GitHub Copilot [3] (Abb. 3) hat sich im Jahr 2025 maßgeblich weiterentwickelt und integriert nun eine umfassende Agent-Funktionalität direkt in VS Code [4]. Der neue Agent-Modus ermöglicht die autonome Planung und Umsetzung komplexer Aufgaben. Copilot analysiert den gesamten Workspace selbstständig, schlägt Codeänderungen vor, führt Terminalbefehle aus und optimiert den Code iterativ bei auftretenden Problemen.

Zusätzlich bietet Copilot eine regelbasierte Generierung von Tests und Dokumentation, die Entwickler:innen direkt in VS Code konfigurieren können. Darüber hinaus ermöglicht Copilot automatisierte Codereviews innerhalb der IDE. Microsoft positioniert Copilot als unterstützenden Partner für die täglichen Aufgaben in der Softwareentwicklung.

GitHub Copilot Agent Mode in Visual Studio Code mit eingebettetem KI-Chat

Abb. 3: Copilot – Welcome Screen

Insgesamt bieten alle drei Tools verlässliche Unterstützung bei alltäglichen Programmieraufgaben, setzen jedoch unterschiedliche Schwerpunkte: Cursor bietet maximale Kontrolle und Tiefe, Windsurf ein fokussiertes, assistiertes Flow-Erlebnis (insbesondere im Frontend-Bereich) und Copilot unkomplizierte KI-Unterstützung in etablierten Entwicklungsumgebungen.

Gemeinsame Features

Im Jahr 2025 gehören zahlreiche KI-Funktionen zum Standard in AI-Codeeditoren. Die wichtigsten Features, die Cursor, Windsurf und Copilot gleichermaßen bieten, sind mehrzeilige Codevervollständigung mit Kontextverständnis, Chat-Assistenten zur Erklärung von Code und zur Behebung von Fehlern, automatische Generierung von Unit-Tests sowie die Integration verschiedener Large Language Models für unterschiedliche Anwendungsfälle.

Obwohl es viele Gemeinsamkeiten gibt, besitzt jede Lösung Alleinstellungsmerkmale, die sie für bestimmte Zielgruppen besonders attraktiv machen.

SIE LIEBEN UI?

Entdecken Sie die BASTA! Tracks

Cursor: umfangreiche KI-Integration für Power-User

Cursor bezeichnet sich selbstbewusst als „The AI Code Editor“, was die tiefgreifende Integration zahlreicher KI-Funktionen widerspiegelt. Die IDE basiert auf einem Fork von VS Code und bietet somit umfassende Kompatibilität zu existierenden Erweiterungen und Workflows. Cursor entfaltet seine Stärken insbesondere bei komplexen Programmieraufgaben wie tiefgehenden Refactorings oder umfassenden Änderungen mehrerer Dateien.

Eine besondere Stärke ist der Shadow Workspace, in dem umfangreiche Codeänderungen in einer isolierten Umgebung ausprobiert werden können, bevor sie übernommen werden. Zudem bietet Cursor spezielle KI-Integrationen. Entwickler:innen können mit Hilfe der @Web-Funktion externe Webseiten indexieren und deren Inhalte als zusätzlichen Kontext direkt in den KI-Chat einfließen lassen. So lassen sich beispielsweise aktuelle API-Dokumentationen, Changelogs oder Tutorials integrieren (Abb. 4). Vordefinierte Dokumentationen bekannter Frameworks und Bibliotheken sind bereits im Tool enthalten und sofort einsatzbereit.

Cursor Docs-Index: Verwaltung eigener technischer Dokumentationen in der IDE

Abb. 4: Cursor – Docs-Index

Mit dem Multi-Chat-Support kann man mehrere KI-Chats parallel betreiben und dabei je nach Aufgabe verschiedene Modelle effizient einsetzen.

Cursor unterstützt darüber hinaus eine flexible Auswahl an KI-Modellen, darunter OpenAI GPT-4 und Anthropic Claude. Sogar eigene Modelle können angebunden werden, wodurch die IDE auch in stark spezialisierten und individuellen Szenarien äußerst leistungsfähig ist.

Windsurf: Flow-basierter Ansatz für intuitives Arbeiten

Anlässlich der Übernahme durch OpenAI hat Windsurf kürzlich viel Aufmerksamkeit auf sich gezogen. Wie Cursor basiert es auf einem Fork von VS Code und bietet eine tiefgehende Integration von KI-Funktionen in eine vertraute Arbeitsumgebung.

Ein Alleinstellungsmerkmal von Windsurf ist die intuitive „Cascade“-Oberfläche (Abb. 5), die den klassischen Editor- und Chatmodus vereint. Entwickler:innen können hier einfach zwischen Schreiben und Chatten wechseln, wodurch die Interaktion mit der KI besonders flüssig und natürlich wirkt. Windsurf glänzt vor allem bei der Webentwicklung durch die visuelle Livevorschau direkt in der IDE. Hierbei können Entwickler:innen UI-Elemente anklicken und Windsurf generiert automatisch passende Codeänderungen. Das Feedback ist somit sofort sichtbar und intuitiv nachvollziehbar.

Windsurf IDE mit Live-Code-Vorschau und KI-generierten Vorschlägen im Cascade-Modus

Abb. 5: Windsurf – Cascade-Oberfläche

Windsurf speichert Konversationen und Programmierentscheidungen in sogenannten „Memories“ und ermöglicht den Betrieb eigener KI-Modelle auf lokaler Hardware.

Zusätzlich hebt sich Windsurf durch seine integrierten Deployment-Funktionen hervor. Damit können Anwendungen direkt aus der IDE heraus bereitgestellt werden. Das reduziert die Notwendigkeit externer CI/CD-Pipelines und vereinfacht den Entwicklungsprozess, insbesondere für kleinere Teams und Projekte.

VS Code mit GitHub Copilot: nahtlose Integration ins GitHub-Ökosystem

Die Kombination aus GitHub Copilot und VS Code überzeugt weniger durch exotische Einzelfeatures als vielmehr durch ihre tiefgehende und nahtlose Integration in bestehende Ökosysteme und Arbeitsprozesse. Copilot fühlt sich wie eine natürliche Erweiterung der beliebten IDE VS Code an und fügt sich nahtlos in die bewährten Arbeitsabläufe vieler Entwickler:innen ein.

Die wohl größte Stärke von GitHub Copilot ist die direkte und tiefe Integration mit den Funktionen der GitHub-Plattform. Copilot bietet zum Beispiel automatische Codereviews und generiert Pull-Request-Beschreibungen, was besonders in Team- und Enterprise-Umgebungen eine erhebliche Zeitersparnis mit sich bringt.

Darüber hinaus überzeugt Copilot durch die nahtlose Einbindung in das GitHub-Ökosystem, zu dem unter anderem GitHub.com, GitHub Desktop und GitHub Actions gehören.

Qualität der AI-Codevorschläge

Für die Entwicklung lautet die zentrale Frage: Wie gut sind die KI-Vorschläge tatsächlich? Mit anderen Worten: Wie gut unterstützen die Tools beim Coding, also wie korrekt, nützlich und effizient sind sie? Hier zeigt sich je nach Anwendungsfall ein differenziertes Bild. Zunächst ist wichtig zu erwähnen, dass in allen Tools die gleichen LLMs zur Auswahl stehen. Allerdings werden unterschiedliche System-Prompts und Tool-Calls eingesetzt, die die Ergebnisse entsprechend beeinflussen.

Alltags-Coding (einzelne Funktionen, Boilerplate)

Bei einfachen Szenarien, etwa beim Schreiben von Routinefunktionen, bei der Syntaxhilfe oder bei bekannten Algorithmen liefern alle drei Tools vergleichbar gute Ergebnisse. Cursor und Windsurf verwenden oft ähnliche oder sogar identische Modelle im Hintergrund. Für komplexere Aufgaben nutzen sie häufig Claude, während Copilot auf OpenAI-Modellen basiert. Bei Standardcode gibt es daher oft keinen drastischen Qualitätsunterschied. Entwicklerberichte bestätigen, dass die typische Codevervollständigung in allen drei Fällen zuverlässig funktioniert. Copilot profitiert dabei von seinen speziellen Trainingsdaten aus GitHub-Code: Er erkennt gängige Patterns und Framework-Konventionen präzise. Cursor und Windsurf generieren tendenziell längere und ausführlichere Codeblöcke (etwa detailliertere Kommentare oder mehrere zusammenhängende Zeilen), während Copilot minimalistisch Zeile für Zeile vorgeht.

Komplexe Aufgaben (projektweite Änderungen, unbekannte Probleme

Bei komplexeren Aufgaben kommen die Vorzüge von Cursor und Windsurf deutlich zum Tragen. Dank ihres erweiterten Kontextverständnisses und ihrer Agent-Fähigkeiten können beide Tools konsistente Änderungen über mehrere Dateien hinweg vorschlagen. Die praktische Erfahrung zeigt, dass beispielsweise beim Erstellen einer React-Komponente mit Modulinteraktionen in allen betroffenen Dateien korrekter Code generiert wird – inklusive passender Imports und Typdefinitionen. Copilot generiert in solchen Fällen eher einzelne Snippets, die manuell zusammengeführt und ergänzt werden müssen.

Ein besonders effektiver Ausführungsmodus ist der sogenannte Max Mode von Cursor, bei dem der verfügbare Kontextumfang und die Anzahl der internen KI-Operationen deutlich erweitert werden. Dadurch kann Cursor mehr Projektinformationen gleichzeitig berücksichtigen, umfangreiche Änderungspläne ableiten und selbst komplexe, modulübergreifende Aufgaben effizient bearbeiten. Diese Fähigkeit erweist sich als besonders wertvoll bei strukturellen Refactorings oder bei der Einführung neuer Features, die tief in bestehende Architekturen eingreifen.

Windsurf unterstützt solche komplexen Vorhaben durch seinen Flow-basierten Cascade-Ansatz. Dabei behält die KI den Projektzustand fortlaufend im Blick und gliedert umfangreiche Aufgaben in nachvollziehbare, aufeinander abgestimmte Schritte. So entstehen adaptive Vorschläge, die über mehrere Dateien hinweg konsistent bleiben. In der praktischen Anwendung zeigt sich: Beim Umbenennen einer zentralen Klasse erkennen sowohl Cursor als auch Windsurf alle Referenzen projektweit und schlagen entsprechende Anpassungen vor. Copilot hingegen erkennt solche Änderungen meist nur dateiweise.

Die „Projektweitsicht“ von Cursor und Windsurf erweist sich als besonders nützlich. Allerdings gibt es auch hier Einschränkungen. Die automatisch generierten Multi-File-Änderungen treffen nicht immer ins Schwarze. Cursors ambitionierte Vorschläge können – trotz aller Tiefe – auch neue Probleme einführen, während sie alte beheben. Tests mit Windsurf zeigen, dass nach größeren Codegenerierungsaufträgen mehrere Fehler auftreten können, die erst nach mehreren Iterationen ausgebessert werden. Copilot ist bei größeren Anforderungen gelegentlich überfordert oder missversteht die Intention, insbesondere bei reduziertem Kontextfenster oder kleinerem Modell.

Insgesamt gilt: Bei umfangreichen KI-Aktionen bleibt eine sorgfältige Codereview unerlässlich, da kein Assistent ganz fehlerfrei arbeitet.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Sprach- und Frameworkabdeckung

Alle drei Tools decken die populären Programmiersprachen (JavaScript/TypeScript, Python, Java, C#, C/C++, Go und PHP) hervorragend ab. GitHub Copilot hat durch seinen Trainingscorpus einen leichten Vorteil bei exotischeren Sprachen oder Frameworks, da es auf dem gesamten öffentlichen GitHub-Code basiert. Windsurf unterstützt eigenen Angaben zufolge über 70 Sprachen, darunter SQL, Bash, JSON und Markdown. Bei Cursor hängt die Sprachunterstützung vom gewählten Modell ab (GPT-4.1 und Claude bieten eine vergleichbare Sprachvielfalt). Durch ihre flexible Modellintegration können Cursor und Windsurf bei sehr neuen oder speziellen Frameworks oft schnellere Updates anbieten. Copilot profitiert hingegen vom kontinuierlichen Training durch Microsoft sowie vom Feedback einer großen Nutzergemeinde. Das führt zu verbesserten Prompt-Techniken und Fehlerfiltern.

Fazit zur Qualität der AI-Codevorschläge

Bei einfacheren Programmieraufgaben erweisen sich alle drei Tools als produktive Entwicklungswerkzeuge; die Unterschiede zeigen sich eher in Spezialfällen. Copilot überzeugt bei gängigen Patterns und stabilen Kurzvorschlägen, während Cursor und Windsurf bei holistischen, projektweiten Tasks dank ihres größeren Kontextverständnisses punkten. Die praktische Erfahrung zeigt: Copilot erweist sich im Alltag als besonders verlässlich, während Cursor gelegentlich mit zu vielen Informationen überfordert. Andererseits bewältigen Cursor und Windsurf komplexe Arbeitsschritte effizienter, die Copilot in dieser Form nicht abdeckt. Die Qualität der KI entwickelt sich laufend weiter. Mit neuen Modellversionen ist zu erwarten, dass alle drei Assistenten noch besser und kontextbewusster werden.

Architektur und technische Ansätze

Trotz des ähnlichen äußeren Erscheinungsbilds unterscheiden sich die Architekturen unter der Haube. Wie integrieren die Tools KI-Modelle, wo laufen diese und wie viel Kontext können sie verarbeiten?

Cursor

Die Macher von Cursor (Anysphere) setzen eine eigene Backend-Infrastruktur ein, die verschiedene KI-Modelle orchestriert. Cursor nutzt eine Mischung aus lokalen Komponenten und Cloud-Modellen: Beispielsweise läuft das Projekt-Indexing lokal: Ein eigener Dienst erstellt im Hintergrund Vektor-Embeddings aller Dateien, um schnellen semantischen Zugriff zu haben. Wenn ein Nutzer eine Frage in den Chat eingibt, werden per Embedding-Suche relevante Codeausschnitte gefunden und an das KI-Modell geschickt. Dieses Modell selbst läuft auf Servern. In der Pro-Version sind das Premium-Modelle wie Google Gemini oder Anthropic Claude. Interessant ist, dass Cursor mehrere Modellgrößen parallel einsetzt: kleine, schnelle Modelle (z. B. das eigene Codemodell cursor-fast) für Autocomplete-Vorschläge und große Modelle für komplexe Chatantworten. Für anspruchsvolle Aufgaben gibt es sogar einen Max Mode, der ein größeres Kontextfenster nutzt und dafür ggf. etwas mehr Wartezeit in Kauf nimmt.

Ein zentrales Konzept bei Cursor (und auch bei Windsurf) ist das Model Context Protocol (MCP). Es ermöglicht fortgeschrittene Automatisierungen. Man könnte beispielsweise einen MCP-Server einbinden, der Bugtickets aus Jira holt und sie dem Cursor-Agenten (Abb. 6) bereitstellt. Der Agent führt dann Codeänderungen im Kontext eines bestimmten Tickets durch. VS Code hat seit Kurzem ebenfalls eine MCP-Integration für den Copilote veröffentlicht.

Cursor Agent-Modus: Automatisierte Code-Aktionen mit Supervisor Prompt und Shadow Workspace

Abb. 6: Cursor – Agent Demo

Die Agent-Architektur in Cursor funktioniert – vereinfacht dargestellt – so: Das System verfügt über einen Supervisor Prompt, der den KI-Modellen vorgibt, wie sie vorgehen sollen (z. B. iterative Planung, Validierung von Zwischenschritten). Cursor führt Agent-Aktionen in einem isolierten Bereich (Shadow Workspace) aus. Der eigentliche Code wird erst geändert, wenn man auf „Apply“ geklickt hat. Dadurch wird das Risiko reduziert, dass die KI ungeprüfte Änderungen verbreitet.

Windsurf

Im Unterschied zu Cursor bietet Windsurf KI-Funktionalität auch als Plug-in für verschiedene Editoren an. Im Weiteren konzentrieren wir uns jedoch auf Windsurf selbst. Architektonisch setzt Windsurf ebenfalls auf Projektindexierung, sogar sehr intensiv: Die Software indexiert die gesamte Codebasis lokal (mit Embeddings) und hält diese ständig aktuell, um jederzeit Kontext liefern zu können. Der Vorteil ist, dass der Codekontext lokal und privat bleibt – d. h., die konkreten Codezeilen müssen nicht vollständig in die Cloud geladen werden, sondern nur abstrakte Vektorrepräsentationen. Auf diese Weise kann Cascade nicht nur schneller als reine Cloud-Lösungen relevante Stellen finden, sondern auch bei begrenzter Internetverbindung performen.

Die KI-Modelle hinter Windsurf sind vielfältig: Windsurf verfügt über eigene Modelle (trainiert auf Open-Source-Code) für Basisszenarien und Autocompletion. Für anspruchsvollere Aufgaben setzt Windsurf stark auf Claude 4 von Anthropic (wie auch Cursor). Mit der Übernahme durch OpenAI werden in Zukunft wahrscheinlich vermehrt OpenAI-Modelle zum Einsatz kommen. Windsurfs Architektur betont zwei Dinge: geringe Latenz und agentische Intelligenz. Ersteres wird unter anderem durch einen eigenen optimierten Model-Server und ggf. kleiner dimensionierte Modelle für schnellen Inline-Support erreicht. Letzteres – die Agent-Fähigkeit – wird durch Flows realisiert: Cascade gliedert komplexe Aufgaben in Schritte und nutzt Toolintegration (z. B. Terminal, Linter, Dateimanagement) in einem steuernden Loop. Die KI sieht in Windsurf immer den gesamten Zustand des Projekts (inklusive ihrer letzten Aktionen), wodurch eine Art „Gedächtnis“ entsteht. So kann Cascade z. B. nachvollziehen, was zuletzt manuell geändert wurde und darauf aufbauend den nächsten Vorschlag kontextuell anpassen.

Ein Aspekt, den sowohl Windsurf als auch Cursor abdecken, ist die Fehlerbehandlung: Beide erkennen, wenn generierter Code Linter- oder Kompilierungsfehler produziert, und iterieren automatisch nach. Windsurf fixt sogar autonom mit Cascade Linter Fails, bevor sie dem Entwickler auffallen. Diese Automatisierungsschleifen sind Teil des jeweiligen Agent-Designs.

 

VS Code mit Copilot

Copilot unterscheidet sich architektonisch von Windsurf und Cursor dadurch, dass es kein eigenständiger Editor ist, sondern ein an bestehende IDEs angebundener Cloud-Service (Abb. 7). In VS Code läuft Copilot als Extension, die mit GitHubs Cloud kommuniziert. Die Integration geschieht über einen spezifischen, dem Language Server Protocol ähnlichen Kanal: Microsoft hat für Copilot damit einen universellen KI-Server erstellt, den VS Code, JetBrains IDEs und sogar Vim, Neovim, Xcode usw. nutzen können. Das bedeutet: Wenn Sie in VS Code tippen, sendet die Copilot-Extension den Inhalt Ihres Editors (Fensterinhalt und die umgebenden 100 bis 150 Zeilen) an das Cloud-API und bekommt die Vervollständigungen zurück. Nichts davon läuft lokal, außer dem UI-Element, das den Vorschlag einblendet.

Copilot Agent Mode in Visual Studio Code mit Beispiel für automatisierten Chat-Workflow

Abb. 7: Copilot-Demo

Für den Chat- und Agenten-Modus greift die Copilot-Chat-Extension auf einen neuen Mechanismus zurück, der auf semantischer Codeindexierung basiert. Das System erstellt automatisch Embeddings aller Projektdateien und ermöglicht so schnelle, kontextbezogene Antworten. Dabei werden auch weitere offene Dateien oder vom Nutzer explizit angehängte Abschnitte berücksichtigt. Seit Copilot ChatGPT-4 nutzt und die neue Indexierung implementiert wurde, kann der Chat deutlich effizienter auf größeren Codebasen operieren. Die Repo-Integration nutzt dabei GitHubs eigenes Indexing-System: Wenn das Projekt ein Git-Repo ist und mit GitHub verbunden ist, werden alle Projektdateien und Commits automatisch semantisch indiziert. Das ermöglicht eine schnelle und präzise Kontextbereitstellung für das Copilot-Modell, sowohl in der IDE als auch in GitHub Codespaces.

Während Cursor und Windsurf mit ihren Agenten lange Zeit ein Alleinstellungsmerkmal besaßen, veröffentlichte GitHub im Jahr 2025 seinen eigenen Agenten für VS Code sowie in einer ersten Betaversion für Visual Studio und die JetBrains IDEs. Alle in den vorherigen Abschnitten beschriebenen Agenten-Features wie das automatische Editieren über mehrere Dateien hinweg, MCP-Tools und Linting werden ebenfalls unterstützt. Lediglich die Auswahl an LLMs und das damit verbundene Kontextfenster sind im Vergleich weiterhin stark limitiert.

Kontextfenster und System-Prompts

Alle drei Lösungen nutzen Großmodelle mit sogenanntem Prompt-Engineering. Das bedeutet, dass vor der eigentlichen Nutzereingabe ein unsichtbarer System- oder Instruktions-Prompt gesendet wird, der das Verhalten steuert (z. B. „Du bist ein hilfreicher Kodierungsassistent …“). Diese Prompts sind in Cursor/Windsurf tendenziell komplexer, da die Agenten verschiedene Tools steuern müssen. Es gibt dort Kaskaden von Aufforderungen (z. B. ein Prompt, der dem Modell beibringt, wie es Shellbefehle vorschlägt und auf Bestätigung wartet). In Copilot ist das Prompting dagegen einfach gehalten und auf Inline-Suggestions optimiert. Für die Endnutzer:innen sind diese Systemdetails jedoch weitgehend unsichtbar. Wichtig ist eher die Kontextgröße: Copilot (GPT-4.1) kann bis zu 128 k Tokens Kontext verarbeiten, Cursor und Windsurf (Claude 4) bis zu 200 k Tokens. Beide nutzen jedoch Retrieval, das heißt, sie „füttern“ das Modell nur mit relevanten Ausschnitten statt mit allen Dateien.

Insgesamt folgen Cursor und Windsurf dem Prinzip „KI komplett in den Editor integrieren“, während Copilot das Prinzip „Editor mit Cloud-KI erweitern“ verkörpert. Das spiegelt sich in der Architektur deutlich wider.

Integration in bestehende Toolchains

Neben der reinen KI-Leistung ist für viele Entwickler:innen entscheidend, wie gut sich ein Tool in ihren vorhandenen Workflow einfügt. Hier gibt es markante Unterschiede zwischen den Ansätzen.

IDE- und Editorunterstützung

GitHub Copilot bietet die breiteste Integration. Er funktioniert nahtlos in VS Code, Visual Studio, allen gängigen JetBrains IDEs (via offizielles Plug-in) sowie in Neovim/Vim und sogar Xcode. Microsoft stellt sicher, dass Copilot dort wie eine native Autocomplete-Funktion wirkt (z. B. erscheinen Copilot-Vorschläge in IntelliJ wie normale Codevervollständigungen). Ähnlich breit ist die Unterstützung von Windsurf: Als Windsurf-Plug-in gibt es Unterstützung für über 40 Editoren, darunter JetBrains, VS Code, Vim/Emacs, Jupyter Notebooks usw. Allerdings bietet das Plug-in nicht den vollen Funktionsumfang der Windsurf-Editor-App: Cascade-Agent und Flows sind in der Stand-alone-IDE beispielsweise mächtiger als im VS-Code-Plug-in, wo oft nur eine dateibezogene Vervollständigung möglich ist.

Cursor geht einen anderen Weg: Hier ist die IDE selbst Teil des Produkts. Es gibt keine Plug-ins für IntelliJ oder Ähnliches – man lädt die Cursor IDE herunter (verfügbar für Mac, Windows und Linux). Erfahrenen VS-Code-Nutzern fällt der Umstieg leicht, da Cursor den Import von VS-Code-Themes, -Einstellungen und -Extensions erlaubt. So kann man seine gewohnte Entwicklungsumgebung größtenteils in Cursor nachbilden. Dennoch bleibt es eine eigene IDE, d. h., wer z. B. tief im Visual-Studio-/JetBrains-Ökosystem verwurzelt ist, muss für Cursor die Umgebung wechseln. Zusammengefasst: Copilot integriert sich am flexibelsten in existierende IDEs, Windsurf bietet sowohl ein Plug-in für viele IDEs als auch eine eigene Editoroption und Cursor setzt auf seine All-in-one-IDE.

Versionsverwaltung (Git) und Collaboration

Alle drei Tools lassen sich mit Git verwenden, der Mehrwert variiert jedoch: In Cursor steht ein gewohntes Git-Panel zur Verfügung (dank der VS-Code-Basis) und es können Commits und Pushs ausgeführt werden. Zusätzlich kann Cursor KI-Features auf Git anwenden, beispielsweise kann per Quick Actions ein Diff erklärt werden oder ein Quick Fix-Button bei Fehlern in der Git-Diff-Ansicht angezeigt werden. Windsurf integriert Git ähnlich (als Editorfunktion) und alle drei Tools unterstützen die automatische Generierung von Commit Messages – das spart viel Zeit und sorgt für saubere Commits. Copilot selbst ist unabhängig von Git, aber GitHub als Plattform nutzt Copilot-Technik. Beim Erstellen eines Pull Requests auf GitHub.com können z. B. automatisch Beschreibungstexte generiert werden. Ein besonderes Feature von GitHub Copilot ist die Möglichkeit, Codereviews direkt in VS Code oder auf GitHub durchzuführen. Das zeigt die Richtung: Copilot soll ein KI-Coreviewer im Team werden.

Dokumentation und Knowledge Base

So kann ein Team beispielsweise interne API-Dokumente als Kontextquelle einbinden. Windsurf verfügt über ein Rules & Memories Panel, in dem Guidelines und wichtige Informationen abgelegt werden können – eine Art teaminterne Wissensbasis für die KI. Copilot bietet ebenfalls die Möglichkeit, benutzerdefinierte Regeln zu hinterlegen, und kann über die #fetch-Direktive Webdokumentationen als zusätzlichen Kontext einbinden. Die Integration und Verwaltung von Dokumentationen und Webinhalten ist jedoch in Cursor am ausgereiftesten umgesetzt.

Einbindung anderer Tools

Alle drei Tools bieten MCP-Server-Unterstützung mit gleichwertiger Integration für Drittsysteme. GitHub Copilot lässt sich über die Open-Source-VS-Code-Extensions-Plattform erweitern. Durch die kürzlich erfolgte Öffnung des Plug-in-Systems durch Microsoft ergeben sich noch mehr Möglichkeiten für Plug-ins von Drittanbietern. Zusätzlich ist Copilot nahtlos in Codespaces (Cloud-Dev-Umgebungen von GitHub) integrierbar: In einem Codespace steht Copilot ohne zusätzliche Konfiguration zur Verfügung, was die Cloud-Entwicklung vereinfacht.

Sicherheit und Datenschutz

In professionellen Umgebungen ist dieser Aspekt besonders wichtig: Copilot für Business garantiert, dass keine Kundencodefragmente zur Modellverbesserung verwendet werden. Außerdem gibt es Regler zur Blockierung von vertraulichem Code in den Vorschlägen. Cursor ist SOC-2-zertifiziert und verfügt über einen Privacy Mode, bei dem kein Code auf den Servern gespeichert bleibt. Windsurf betont, dass das Training ausschließlich auf Open-Source-Daten erfolgt und keine Nutzerdaten gespeichert oder geteilt werden. Außerdem bleiben durch das lokale Indexing viele Informationen auf dem Rechner. Für Enterprise-Kunden bietet Windsurf auch On-Premises-Optionen (eigene Server mit dem KI-Modell) an. Je nach Unternehmensrichtlinien muss geprüft werden, welcher Anbieter konform ist. Stand 2025 bieten alle drei Wege die Möglichkeit, die Nutzung datenschutzkonform zu gestalten (sei es via Vertragszusatz oder Self-Hosting bei Windsurf Enterprise).

Insgesamt gilt: Copilot punktet bei der Integration, wenn man bereits stark im GitHub-/MS-Ökosystem unterwegs ist und verschiedene IDEs nutzt. Windsurf ist attraktiv, wenn man KI in bestehenden Editoren einsetzen will (überall gleiche KI-Unterstützung via Plug-in) oder wenn man speziell im Webbereich eine Alles-in-einem-IDE sucht. Cursor richtet sich an Entwickler:innen, die bereit sind, ihre Haupt-IDE zu wechseln, um die volle KI-Power zu nutzen. Dafür belohnt Cursor mit tiefer KI-Verschmelzung, aber es ist eben ein separates Programm.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Integration in bestehende Toolchains

In der Praxis hat jede Lösung ihre Vor- und Nachteile, die von den Vorlieben und Bedürfnissen der Entwickler:innen abhängen. Im Folgenden finden Sie eine Übersicht der wichtigsten Eindrücke aus der Entwicklercommunity.

Cursor: Power-Tool mit moderner Oberfläche

Cursor überzeugt durch seine leistungsstarken Funktionen und die schnelle Umsetzung komplexer Änderungen. Besonders in der professionellen Entwicklung bietet die IDE auf Knopfdruck „jedes erdenkliche AI-Feature“. Die KI-Unterstützung ist allgegenwärtig – vom Einfügen fehlender Imports bis zum Komplett-Refactoring mit Agenten. Die mehrfach überarbeitete Benutzeroberfläche präsentiert sich nun aufgeräumt und intuitiv. Das moderne UI ermöglicht einen effizienten Workflow, ohne dass dabei Funktionsvielfalt eingebüßt wird. Die Steuerung setzt auf manuelle Kontrolle: Im Standardmodus erfordern KI-Änderungen einzelne Bestätigungen. Das bietet zwar Sicherheit, unterbricht aber den Arbeitsfluss. Die Lernkurve ist steil – die Einarbeitung in Chat, Agenten, Shortcuts und @-Tags benötigt Zeit. Das Ergebnis ist jedoch ein äußerst anpassbares Entwicklungswerkzeug. Performancetechnisch läuft Cursor auf modernen Systemen flüssig, wobei umfangreiche Agent-Aktionen einige Sekunden Verarbeitungszeit benötigen.

Windsurf: intuitiv und flüssig, aber noch jung

Windsurf überzeugt durch seine moderne, aufgeräumte Benutzeroberfläche. Die KI fügt sich harmonisch in den Workflow ein, und die Bedienung ist intuitiv gestaltet. Der Cascade-Agent führt durch die Entwicklung, während die automatische Kontexterkennung selbstständig die relevanten Dateien auswählt. Mit Hilfe des Write/Chat-Umschalters lässt sich eine klare Trennung zwischen Codegenerierung und Erklärungen erreichen. Die größte Stärke von Windsurf ist das „Flow“-Gefühl: Die KI arbeitet kontinuierlich im Hintergrund, ohne den Entwicklungsprozess zu unterbrechen.

Verbesserungsbedarf besteht jedoch in puncto Zuverlässigkeit: Der Cascade-Agent erkennt Kontexte nicht immer korrekt und die Vorschaufunktion zeigt gelegentlich Schwächen. Positiv fallen die regelmäßigen Updates (monatliche „Waves“) auf. Die Performance wurde priorisiert. Windsurf reagiert sehr schnell bei Inline Suggestions, allerdings benötigt der erste Indexing-Durchlauf bei großen Projekten mehrere Minuten.

VS Code mit Copilot: etabliert und vielseitig

Copilot in VS Code hat sich für viele Entwickler:innen zu einem stillen Helfer entwickelt. Er funktioniert meist, ohne dass man es bewusst bemerkt. Die Vorschläge wirken natürlich (sie erscheinen als grauer Text und werden mit der TAB-Taste akzeptiert) – wer IntelliSense gewohnt ist, empfindet Copilot als dessen Erweiterung. Die Stärken von Copilot sind die hohe Treffsicherheit bei Routineaufgaben – dank des Trainings mit massenhaft Code schreibt Copilot beispielsweise Schleifen, API-Calls und bekannte Algorithmen oft fehlerfrei aus dem Stand – und die breite IDE-Unterstützung über VS Code, Visual Studio, IntelliJ IDEs und XCode, wobei VS Code als Hauptplattform priorisiert wird. Copilot ist mit etwa zwei Jahren auf dem Markt ausgereift: Die Ausfallzeiten sind gering, die Latenz minimal und die Integration bereitet keine Probleme. Viele schätzen auch die geringen Kosten – Copilot kostet 10 $ pro Monat, Cursor 20 $ und Windsurf 15 $.

Mit der Einführung des Agent-Modus, des MCP-Supports und der Möglichkeit, externe Webinhalte einzubinden, hat Copilot zu seinen Konkurrenten aufgeschlossen. Der Agent-Modus ermöglicht automatisierte Aufgaben und Multi-File-Refactoring, während der MCP-Support die Integration von Drittsystemen erleichtert.

In Team- und Enterprise-Umgebungen hat Copilot einen Trumpf: die Reputation und den Support von GitHub. Große Firmen bevorzugen in der Regel einen etablierten Anbieter mit Supportvertrag. Copilot lässt sich über Azure OpenAI auch als on-Premises-ähnlicher Service buchen – eine Option für Unternehmen, die Datenhoheit verlangen.

 

Fazit: Welche IDE für welchen Anwendungsfall?

Zum Abschluss einige Empfehlungen für verschiedene Zielgruppen und Zwecke – denn „das beste Tool“ gibt es nicht, es kommt darauf an, was man damit vorhat.

GitHub Copilot (in VS Code) – empfehlenswert für …

… Entwickler:innen, die einen bewährten und kostengünstigen KI-Assistenten suchen, der sich ohne Aufwand in ihren bestehenden Entwicklungsprozess einfügt. Wenn Sie bereits VS Code oder JetBrains IDEs nutzen und bessere Autocompletion sowie gelegentliche Chathilfe wünschen, ist Copilot ideal. Er brilliert bei Alltagsaufgaben, unterstützt eine enorme Bandbreite an Sprachen und verfügt über eine große Community und Support als Rückendeckung. Mit der Einführung des Agent-Modus und MCP-Support steht Copilot den anderen Lösungen auch im Hinblick auf Features in nichts nach. Ebenso spricht viel für Copilot, wenn das Budget ein Kriterium ist oder man in einem Unternehmen mit strikten Richtlinien arbeitet (Copilot for Business bietet entsprechenden Support und Compliance).

Cursor AI – empfehlenswert für …

… Entwickler:innen, die einen bewährten und kostengünstigen KI-Assistenten wünschen, sowie für Advanced Developer und Softwarearchitekten, die komplexe Codebasen betreuen und bereit sind, in ein neues Tool zu investieren. Cursor spielt seine Stärken aus, wenn ein tiefes Projektverständnis gefragt ist – z. B. beim Refactoring großer Codebestände, beim schrittweisen Ausbau einer bestehenden Architektur oder wenn ein einzelner Entwickler sehr viel erledigen möchte (Cursor wirkt fast wie ein zweiter Entwickler, der einem nebenher Aufgaben abnimmt). Die Möglichkeit, mit verschiedenen KI-Modellen zu experimentieren, sowie die Integration von Websuche und firmeneigenen Dokumentationen sind für viele Profis ein Plus. Wenn Sie also gerne „Early Adopter“ von KI-Funktionen sind und die eigene Produktivität maximieren möchten, ist Cursor einen Blick wert. Bedenken Sie aber: Sie müssen Ihre Arbeitsgewohnheiten etwas umstellen (neue IDE) und sich mit den vielen Features vertraut machen, sonst nutzen Sie vielleicht nur 20 Prozent des Potenzials. In kleineren Teams ist Cursor äußerst leistungsfähig. In großen Teams mit heterogener Toollandschaft könnte die Einführung jedoch auf Widerstand stoßen.

Windsurf – empfehlenswert für …

… Entwickler:innen, die Wert auf einen reibungslosen KI-Workflow legen und viel im Web-/Mobile-Bereich unterwegs sind. Windsurfs eigene IDE ist hervorragend geeignet, um neue Projekte schnell zu realisieren – beispielsweise einen Prototyp, bei dem die KI gleich Code, Tests und Deployment vorbereiten soll. Dank der visuellen Vorschau und der Schritt-für-Schritt-Agentenlogik bleibt man im „High-Level“-Denken und lässt die KI vieles ausführen. Auch KI-Neulinge oder weniger erfahrene Coder:innen profitieren von Windsurf: Es fühlt sich an, als hätte man einen Mentor, der mitdenkt (z. B. schlägt Windsurf vor, was als Nächstes zu tun sein könnte). Wenn Sie hingegen Ihren Lieblingseditor nicht aufgeben wollen, bietet das Plug-in von Windsurf die Kernfunktionen von Windsurf überall. Das ist ideal für gemischte Teams, z. B. VS-Code- und IntelliJ-Nutzer, die einen einheitlichen KI-Assistenten möchten. Zu beachten ist, dass Windsurf als Produkt noch jung ist. Es ist also perfekt für diejenigen, die Experimentierfreude mitbringen und Feedback geben möchten, um zu helfen, eventuelle Probleme zu lösen. Größere Unternehmen könnten Windsurf attraktiv finden, da Windsurf bereits Selbsthosting und Datenschutzoptionen anbietet, etwa für Firmen, die kein Hochladen von Code in externe Clouds dulden. Außerdem ist Windsurf preislich fair (es gibt eine Free Tier).

Fazit

Alle drei Kandidaten zeigen, wie KI die Softwareentwicklung revolutioniert. Welche Lösung die beste ist, hängt vom persönlichen Workflow und Anwendungsgebiet ab. Professionelle Entwickler:innen und Architekt:innen sollten sich überlegen, welche Rolle die KI einnehmen soll: als dezenter Helfer im Hintergrund (Copilot), umfassender Projektassistent mit allen Schikanen (Cursor) oder als Flow-orientierter Partner, der Entwicklerergonomie großschreibt (Windsurf). Um den eigenen Perfect Fit zu finden, lohnt es sich, alle einmal auszuprobieren. Es gibt für alle kostenfreie Testphasen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Frequently Asked Questions (FAQ)

1. Was ist Cursor?

Cursor ist eine eigenständige, KI-native Entwicklungsumgebung, die auf Visual Studio Code basiert. Sie bietet tief integrierte KI-Funktionen und unterstützt Entwickler bei komplexen Aufgaben mit automatisierten Workflows.

2. Was unterscheidet Windsurf von anderen KI-IDEs?

Windsurf legt den Schwerpunkt auf Datenschutz und lokales Projektverständnis. Der Editor indexiert Code lokal und ermöglicht KI-gestützte Abläufe über mehrere Dateien hinweg, ohne sensible Daten in die Cloud zu senden.

3. Wie funktioniert GitHub Copilot?

GitHub Copilot ist keine eigenständige IDE, sondern eine Erweiterung für Editoren wie Visual Studio Code. Sein Vorteil ist die enge Integration mit GitHub, sodass Pull-Requests, Issues und Repositories direkt einbezogen werden können.

4. Welche Unterschiede gibt es beim Datenschutz?

Cursor und Windsurf setzen verstärkt auf lokale Verarbeitung und Schutz von Quellcode. Copilot arbeitet hingegen cloudbasiert, bietet aber durch GitHub eine enge Anbindung an Entwickler-Workflows.

5. Wie unterscheiden sich die Preismodelle?

Die drei Tools bieten unterschiedliche Preismodelle: Cursor und Windsurf mit gestaffelten Tarifen, Copilot mit einem günstigeren Abo, das besonders durch GitHub-Integration attraktiv ist. Die Wahl hängt von Budget und Teamgröße ab.

6. Für wen eignet sich Cursor?

Cursor richtet sich an Entwickler:innen, die eine komplett KI-native IDE suchen. Besonders bei komplexen Projekten mit vielen Dateien entfaltet Cursor seine Stärken.

7. Welche Zielgruppe passt zu Windsurf?

Windsurf eignet sich für Teams, die Datenschutz und lokale Datenhaltung priorisieren. Es bietet gleichzeitig flexible KI-Workflows über mehrere Dateien hinweg.

8. Wer profitiert am meisten von GitHub Copilot?

GitHub Copilot ist ideal für Entwickler:innen, die GitHub intensiv nutzen und eine nahtlose Integration in bestehende Workflows wünschen. Durch die Anbindung an Repositories und Issues erleichtert es den Alltag im GitHub-Ökosystem.


Links & Literatur

[1] https://cursor.com

[2] https://windsurf.com

[3] https://github.com

[4] https://code.visualstudio.com

The post KI-IDEs im Vergleich: Cursor, Windsurf & Copilot appeared first on BASTA!.

]]>
.NET 10 Preview 3 & 4: Das steckt drin https://basta.net/blog/dotnet-10-preview3-4-neuerungen/ Tue, 17 Jun 2025 08:39:53 +0000 https://basta.net/?p=107568 Nachdem Dr. Holger Schwichtenberg im Blogartikel „Neuerungen in .NET 10.0 Preview 1 und 2“ die ersten Vorschauversionen vorgestellt hat, widmet er sich nun den Highlights aus Preview 3 und 4 der kommenden Long-Term-Support-Version.

The post .NET 10 Preview 3 & 4: Das steckt drin appeared first on BASTA!.

]]>
.NET 10.0 Preview 1 ist am 25. Februar 2025 erschienen. Am 18. März 2024 folgte Preview 2. Seit dem 10. April 2025 gibt es Preview 3 und am 13. Mai 2025 kam Preview 4. Für .NET 10.0 Preview 4 brauchen Sie auf dem Entwicklungsrechner

  • das .NET 10.0 SDK [1], das die drei .NET-Laufzeitumgebungen beinhaltet, sowie
  • Visual Studio 2022 Version 17.14.

.NET 10.0 SDK kann direkt zusammen mit Visual Studio 2022 Version 17.14 installiert werden.

 

C# 14.0: Erweiterungsmitglieder (Extension Members)

Die nachträgliche Erweiterbarkeit von Klassen (auch wenn diese bereits anderenorts kompiliert sind, z. B. in den von Microsoft gelieferten Klassen in den .NET-Klassenbibliotheken) um zusätzliche Methoden, gibt es unter dem Namen „Extension Methods“ bereits seit C#-Sprachversion 3.0, die zusammen mit .NET Framework 3.5 im Jahr 2007 erschienen ist. Man kann mit Extension Methods aber lediglich eine Instanzmethode zu bestehenden Klassen ergänzen. So mussten Entwicklerinnen und Entwickler zwangsweise Konstrukte, die vom Namen her eigentlich Properties waren, leidigerweise als Methoden ausdrücken, siehe IsEmptyClassic() in Listing 1.

public static class StringExtensionClassic
{
  public static string TruncateClassic(this string s, int count)
  {
    if (s == null) return "";
    if (s.Length <= count) return s;
    return s.Substring(0, count) + "...";
  }

  public static bool IsEmptyClassic(this string s)
    => String.IsNullOrEmpty(s);
}

In der .NET-Klassenbibliothek gibt es aus diesem Grund einige Erweiterungsmethoden, die Namen besitzen, die man intuitiv als Property erwarten würde, z. B.:

  • Enumerable.Count()
  • Queryable.Count()
  • Enumerable.First()
  • Enumerable.Last()

In C# 14.0 bietet Microsoft nun mit dem neuen Blockschlüsselwort extension eine verallgemeinerte Möglichkeit der Erweiterung bestehender .NET-Klassen, die „Extension Members“ heißt.

Das Schlüsselwort extension muss Teil einer statischen, nichtgenerischen Klasse auf der obersten Ebene sein (also keine Nested Class). Nach dem Schlüsselwort extension deklariert man den zu erweiternden Typ (Receiver). In Listing 2 ist der Receiver die Klasse System.String (alternativ abgekürzt durch den eingebauten Typ string). Alle Methoden und Properties innerhalb des Extension-Blocks erweitern dann den hier genannten Receiver-Typ. Aktuell kann man in diesen Extension-Blöcken folgende Konstrukte verwenden (Listing 2):

  • Instanz-Methoden
  • statische Methoden
  • Instanz-Properties mit Getter
  • statische Properties mit Getter

Eine Klasse darf mehrere Extension-Blöcke sowie zusätzlich auch klassische Extension Methods und andere statische Mitglieder enthalten (Listing 2). Das erlaubt Entwicklerinnen und Entwicklern, eine bestehende Klasse mit Extension Methods um Extension-Blöcke zu erweitern. Es darf auch mehrere Klassen mit Extension-Blöcken für einen Receiver-Typ geben.

public static class MyExtensions
{
  extension(System.String s)
  {
  
    // Erweitern um eine Instanz-Methode
    public string Truncate(int count)
    {
      if (s == null) return "";
      if (s.Length <= count) return s;
      return s.Substring(0, count) + string.Dots;
    }
 
    // Erweitern um eine Instanz-Eigenschaft
    public bool IsEmpty => String.IsNullOrEmpty(s);
 
    // Erweitern um eine Instanz-Eigenschaft mit Getter und Setter
    public int Size
    {
      get { return s.Length; }
      set
      {
        if (value < s.Length) s = s.Substring(0, value);
        if (value > s.Length) s = s + new string('.', value - s.Length); // Neuzuweisung geht nicht !!!
      }
    }
  
    // Erweitern um eine statische Methode
    public static string Create(int count, char c = '.')
    {
      return new string(c, count);
    }
  
    // Erweitern um eine statische Instanz-Eigenschaft
    public static string Dots => "...";
  }
  
  // Es darf auch eine Extension für einen anderen Typen geben
  extension(List<int> source)
  {
    public List<int> WhereGreaterThan(int threshold)
      => source.Where(x => x > threshold).ToList();
 
    public bool IsEmpty
      => !source.Any();
 
    // Erweitern um eine Instanz-Eigenschaft mit Getter und Setter
    public int Size
    {
      get { return source.Count; }
      set
      {
        if (value < source.Count) source = source.Take(value).ToList();
        if (value > source.Count) source.AddRange(Enumerable.Repeat(0, value - source.Count));
      }
    }
  }
  
  // Es kann auch eine klassische Extension Method in der gleichen Kasse geben
  public static string TruncateClassic2(this string s, int count)
  {
    if (s == null) return "";
    if (s.Length <= count) return s;
    return s.Substring(0, count) + "...";
  }
  
  // Es kann auch andere statische Methoden in der gleichen Klasse geben
  public static Version ExtensionVersion => new Version(1, 0, 0);
}

Zu beachten ist:

  • Man kann mit Extension Members auch das Gleiche tun, was Extension Methods bisher konnten. Entwicklerinnen und Entwickler haben nun die Wahl, Extension Methods für alte oder die neue Syntax zu verwenden. Microsoft nennt die alte Syntax zur Abgrenzung „this-parameter extension methods“. Visual Studio wird Refactoring-Methoden zur Umwandlung zwischen beiden Syntaxformen anbieten.
  • Beim Versuch, in einem Extension-Block ein Field zu geben, meckert der Compiler leider mit der unzutreffenden Meldung „Extension declarations can include only methods or properties“.
  • Die Syntax für Extensions, die Microsoft für C# 13.0 in Arbeit hatte (public implicit extension Name for Typ), wurde verworfen.
  • Microsoft plant in kommenden Preview-Versionen weitere Konstrukte in Extension-Blöcken zu erlauben, z. B. Konstruktoren.
  • Listing 3 zeigt den Aufruf der alten und neuen Erweiterungen.
public class CS14_ExtensionDemo
{
  public void Run()
  {
    CUI.Demo(nameof(CS14_ExtensionDemo) + ": String");
  
    string s1old = "Hallo Holger";
    string s1oldtruncated = s1old.TruncateClassic(5);
    Console.WriteLine(s1oldtruncated); // Hello...
    string s2old = null;
    Console.WriteLine(s2old.IsEmptyClassic()); // true
  
    string s1 = "Hallo Holger";
    string s1truncated = s1.Truncate(5);
    Console.WriteLine(s1truncated); // Hello...
  
    // Das geht nicht, weil das Size Property versucht, die Zeichenkette neu zuzuweisen!
    s1.Size = 5;
    Console.WriteLine(s1); // Hallo Holger statt wie erwartet Hallo
  
    string s2 = null;
    Console.WriteLine(s2.IsEmpty); // true
  
    string s3 = string.Create(5, '#');
    Console.WriteLine(s3); // "#####"
  
    CUI.Demo(nameof(CS14_ExtensionDemo) + ": Collection");
  
    var list = new List<int> { 1, 2, 3, 4, 5 };
    var large = list.WhereGreaterThan(3);
    Console.WriteLine(large.IsEmpty);
    if (large.IsEmpty)
    {
      Console.WriteLine("Keine Zahlen größer als 3!");
    }
    else
    {
      Console.WriteLine(large.Count + " Zahlen sind größer als 3!");
    }
  
    // Das klappt: Die Liste wird auf 10 Elemente aufgefüllt
    list.Size = 10;
    foreach (var x in list)
    {
      Console.WriteLine(x);
    }
  }
}

C# 14.0: Null-Conditional Assignment mit ?.

Ein weiteres sehr hilfreiches neues Sprachkonstrukt in C# 14.0 nennt Microsoft das „Null-Conditional Assignment“. Damit können Entwicklerinnen und Entwickler eine Zuweisung an eine Eigenschaft machen ohne vorher zu prüfen, ob das Objekt null ist. Anstelle von

Website aktuelleDOTNETWebsite = Website.Load(123);
if (aktuelleDOTNETWebsite!= null)
{
  // Aktualisieren der URL
    aktuelleDOTNETWebsite.Url = "https://www.dotnet10.de";
}

darf man nun verkürzt mit dem Fragezeichen vor dem Punkt (?.) schreiben:

aktuelleDOTNETWebsite?.Url = "https://www.dotnet10.de";

Dies führt zur Laufzeit zu keinem Fehler. Allerdings passiert auch rein gar nichts, falls die Variable aktuelleDOTNETWebsite den Wert null besitzt.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

.NET SDK: Kompilieren und Starten einzelner C#-Dateien (C# Scripting)

Seit .NET 10.0 Preview 4 können Entwicklerinnen und Entwickler einzelne C#-Dateien direkt übersetzen und starten – ohne dass es eine Projektdatei geben muss. Das ist möglich mit dem .NET-SDK-CLI-Befehl dotnet run (Abb. 1).

Aktivierte OpenAPI-Unterstützung in der Projektvorlage ASP.NET Core Web API (native AOT) mit .NET 10.0 Preview

Abb. 1: Start einer eigenständigen C#-Datei mit dotnet run

Damit kann C# nun auch als Skriptsprache zum Einsatz kommen. Es gab dafür aber schon vorher Ansätze außerhalb von Microsoft:

  • CS-Script [2]
  • DOTNET-Script [3]
  • Cake [4]
  • DOTNET Scripting Host (DSH) [5]

Auch der klassische Stil mit class Program und Main()-Methode ist möglich in den eigenständigen C#-Dateien:

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("Hallo von " + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
  }
}

Für Informationen, die üblicherweise in einer Projektdatei liegen, hat Microsoft eine eigene Syntax beginnend mit der Raute # (Präprozessordirektiven) eingeführt:

  • Festlegung des SDK: #:sdk Microsoft.NET.Sdk.Web
  • Referenz auf ein NuGet-Paket: #:package [email protected].*
  • Build-Eigenschaften, z. B. Versionsnummer: #:property Version 1.1.2

Listing 4 zeigt ein Beispiel mit zwei NuGet-Paketen.

#:package [email protected]
#:package [email protected].*
#:property LangVersion preview
#:property Version 1.1.2

using Spectre.Console;
using Humanizer;

var title = "C# Script " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version + " mit " + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
var panel = new Panel(title);
AnsiConsole.Write(panel);

Console.WriteLine();

var d = new Data
{
  Version = "9.0",
  Release = "2024-12-3"
};

var dotNet9Released = DateTimeOffset.Parse(d.Release);
var since = DateTimeOffset.Now - dotNet9Released;
Console.WriteLine($"It has been {since.Humanize()} since .NET {d.Version} was released.");

class Data
{
  public string Version { get; set; }
  public string Release { get; set; }
}

Das nächste Beispiel in Listing 5 und Abbildung 2 zeigt ein ASP.NET Core Minimal WebAPI als eigenständige C#-Datei.

#:sdk Microsoft.NET.Sdk.Web
#:package Microsoft.AspNetCore.OpenApi@10.*-*
#:package [email protected]
#:property Version 1.1.4

using Humanizer;
using Microsoft.OpenApi;

// Webserver einrichten
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi(); // http://localhost:5000/openapi/v1.json

app.MapGet("/", () =>
  {
    // Daten für Operation
    var d = new Data
    {
      Version = "9.0",
        Release = "2024-12-3"
    };
    var dotNet9Released = DateTimeOffset.Parse(d.Release);
    var since = DateTimeOffset.Now - dotNet9Released;

    return $"It has been {since.Humanize()} since .NET {d.Version} was released.";
});
app.Run();

class Data
{
  public string Version { get; set; }
  public string Release { get; set; }
}
Originalzustand einer OpenAPI-Dokumentation mit Standard-Response-Beschreibung „OK“ im JSON-Format

Abb. 2: Start und Ausgabe des Webservers

.NET SDK: Umwandeln eigenständiger C#-Dateien in C#-Projekte

Wenn die Anforderungen höher werden, sind C#-Skripte keine Sackgasse. Man kann per Kommandozeilenbefehl aus einem C#-Skript ein C#-Projekt machen (Abb. 3):

dotnet project convert .\WebserverStandalone.cs
Angepasste OpenAPI-Spezifikation mit zusätzlichem Header-Parameter und angepasster Erfolgsmeldung

Abb. 3: Umwandeln einer eigenständigen C#-Skriptdatei in ein C#-Projekt

Dabei werden ein neuer Ordner und eine Projektdatei angelegt, wobei letztere die Präprozessorinformationen aus der C#-Datei übernimmt (Abb. 4).

Konsolen-Ausgabe eines eigenständigen C#-Skripts unter .NET 10.0 Preview in Visual Studio Code

Abb. 4: Das eigenständige C#-Projekt

Basisklassenbibliothek: asynchrone ZIP-Operationen

ZIP-Komprimierung gibt es in der .NET-Basisklassenbibliothek seit dem klassischen .NET Framework 4.5 und im modernen .NET seit Version .NET Core 1.0. Seit .NET 10.0 Preview 4 gibt es nun in den Klassen System.IO.Compression.ZipFile, System.IO.Compression.ZipArchive und System.IO.Compression.ZipEntry asynchrone Pendants zu bestehenden synchronen Methoden, z. B. ExtractToDirectoryAsync(), ExtractToFileAsync(), CreateFromDirectoryAsync(), OpenAsync(), OpenReadAsync(), CreateAsync() und CreateEntryFromFileAsync(). Listing 6 zeigt mehrere Beispiele für asynchrone ZIP-Operationen.

using System.IO.Compression;
using System.Text;
using ITVisions;

namespace NET10_Console;

internal class FCL10_Zip
{
  private const string ArchiveFileName = @"t:\CTempArchive.zip";
  private const string SourceDirectoryName = @"c:\temp";
  private const string DestinationDirectoryName = @"t:\CTempArchiveExtract";
  private const string TempFileName = @"t:\tempfile.pdf";
  
  public async Task Run()
  {
    CUI.Demo(nameof(FCL10_Zip));
  
    // Prüfe, ob die Datei existiert
    if (File.Exists(ArchiveFileName)) File.Delete(ArchiveFileName);
  
    // Create a Zip archive
    await ZipFile.CreateFromDirectoryAsync(SourceDirectoryName, ArchiveFileName, CompressionLevel.SmallestSize, includeBaseDirectory: true, entryNameEncoding: Encoding.UTF8);
  
    // Prüfe, ob die Datei existiert
    if (File.Exists(ArchiveFileName)) CUI.Green("ZIP-Datei erstellt: " + ArchiveFileName);
    else CUI.Red("ZIP-Datei nicht erstellt: " + ArchiveFileName);
  
    // Extract a Zip archive
    await ZipFile.ExtractToDirectoryAsync(ArchiveFileName, DestinationDirectoryName, overwriteFiles: true);
  
    // Prüfe, ob das Verzeichnis existiert
    if (Directory.Exists(DestinationDirectoryName)) CUI.Green("Verzeichnis extrahiert: " + DestinationDirectoryName);
    else CUI.Red("Verzeichnis nicht extrahiert: " + DestinationDirectoryName);
  
    #region Lesen und Schreiben von Einträgen in eine ZIP-Datei
  
    using (var archiveStream = new FileStream(ArchiveFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
    {
  
      await using (ZipArchive a = await ZipArchive.CreateAsync(archiveStream, ZipArchiveMode.Update, leaveOpen: false, entryNameEncoding: Encoding.UTF8))
      {
  
        // Suche die erste PDF-Datei in a.Entries
        var pdfFileEntry = a.Entries.Where(x =&gt; x.Name.EndsWith(".pdf")).FirstOrDefault();
        if (pdfFileEntry != null)
        {
          await pdfFileEntry.ExtractToFileAsync(destinationFileName: TempFileName, overwrite: true);
  
          await using Stream entryStream = await pdfFileEntry.OpenAsync();
 
          ZipArchiveEntry createdEntry = await a.CreateEntryFromFileAsync(sourceFileName: TempFileName, entryName: "Doppelt_" + pdfFileEntry.Name);
          CUI.Green("Erste PDF-Datei wurde verdoppelt: " + pdfFileEntry.Name);
        }
      }
    }
    #endregion
  }
}

Basisklassenbibliothek: mehr Kontrolle beim Zertifikatsexport mit X509Certificate.ExportPkcs12()

Eine Verbesserung für den Zertifikatsexport stand schon in den Release Notes zu .NET 10.0 Preview 2 [6], funktionierte dort aber nicht. Seit .NET 10.0 Preview 3 kann man tatsächlich Zertifikate mit AES-Verschlüsselung und Hashing via SHA-2-256 exportieren – mit der neuen Methode ExportPkcs12() als Ergänzung zur bestehenden Methode Export():

byte[] pfxData = cert.ExportPkcs12(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, password);

Die bisherige Methode Export() verwendet noch veraltete Algorithmen (3DES-Verschüsselung und SHA-1-Hashing). Die alten Verfahren (3DES/SHA-1) aus Export() sind aber auch über die neue Methode möglich, wenn man es wirklich will:

byte[] pfxData = cert.ExportPkcs12(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, password);

Listing 7 zeigt ein Beispiel für die Erstellung und den Export eines Zertifikats.

string certPath = "meinZertifikat.pfx";
string password = "meinPasswort"; // Passwort zum Schutz des Zertifikats

// Selbstsigniertes Zertifikat erstellen
using (var rsa = RSA.Create(2048))
{
  var request = new CertificateRequest(
    "CN=TestZertifikat",
    rsa,
    HashAlgorithmName.SHA256,
    RSASignaturePadding.Pkcs1);
  
  // Zertifikat für 1 Jahr gültig machen
  var cert = request.CreateSelfSigned(
    DateTimeOffset.Now,
    DateTimeOffset.Now.AddYears(1));
  
  // ALT: Zertifikat mit 3DES-Verschüsselung und SHA1-Hashing exportieren
  //byte[] pfxData = cert.Export(X509ContentType.Pkcs12, password);
  
  // NEU: Zertifikat als AES mit SHA-2-256 exportieren
  byte[] pfxData = cert.ExportPkcs12(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, password);
  
  // NEUE Methode, aber alte Sicherheitsverfahren: 3DES mit SHA-1
  //byte[] pfxData = cert.ExportPkcs12(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, password);
  
  // PFX-Datei speichern
  File.WriteAllBytes(certPath, pfxData);
  
  Console.WriteLine($"Test-Zertifikat erstellt und gespeichert unter: {certPath}");
}

System.Text.Json: JSON-Patch-Unterstützung

System.Text.Json beherrscht nun den JSON-Patch-Standard nach RFC 6902. JSON Patch ist ein standardisiertes Format (definiert in RFC 6902), um Änderungen an JSON-Dokumenten zu beschreiben. Es ermöglicht die Übertragung von Änderungen an einem JSON-Dokument in Form einer Liste von Anweisungen (Operationen). JSON Patch wird häufig in WebAPIs eingesetzt.

Bisher musste man in ASP.NET-Core-basierten WebAPIs für JSON Patch die ältere Community-Bibliothek Newtonsoft.Json einsetzen. Nun geht es auch mit der neueren Microsoft-Bibliothek System.Text.Json [7].

Für JSON Patch mit System.Text.Json gibt es eine Erweiterung für System.Text.Json in Form des neuen NuGet-Pakets Microsoft.AspNetCore.JsonPatch.SystemTextJson, das erstmalig mit .NET 10.0 Preview 4 erschienen ist [8].

Trotz des “AspNetCore” im Namen funktioniert dieses Zusatzpaket auch außerhalb von ASP.NET Core, wie Listing 8 beweist! Allerdings funktioniert das neue Paket nicht mit dem NativeAOT-Compiler.

public class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Email { get; set; }
  public List&lt;PhoneNumber&gt; PhoneNumbers { get; set; } = new();
  public Address Address { get; set; }
}

public class Address
{
  public string Street { get; set; }
  public string City { get; set; }
  public string State { get; set; }
  public string ZipCode { get; set; }
}

public enum PhoneNumberType
{
  Mobile,
  Home,
  Work,
  Other
}

public class PhoneNumber
{
  public string? Number { get; set; }
  public PhoneNumberType Type { get; set; }
}

public void JSONPatchDemo()
{
  // Originalobjekt
  var person = new Person
  {
    FirstName = "Holger",
    LastName = "Schwichtenberg",
    Email = "[email protected]",
    PhoneNumbers = [new PhoneNumber() { Number = "0201 649590-0", Type = PhoneNumberType.Work }],
    Address = new Address
    {
      Street = "Fahrenberg 40b",
      City = "Essen",
      State = "NRW"
    }
  };
  
  CUI.H2("Ausgabe des Objekts in seinem alten Zustand");
  CUI.Print(ITVisions.ObjectDumper.Dump(person));
  
  // JSON Patch-Document
  string jsonPatch = """
  [
    { "op": "replace", "path": "/FirstName", "value": "Dr. Holger" },
    { "op": "remove", "path": "/Email"},
    { "op": "add", "path": "/Address/ZipCode", "value": "45257" },
    { "op": "add", "path": "/PhoneNumbers/-", "value": { "Number": "0201 649590-40" } }
  ]
  """;
  
  // JSON Patch-Document laden
  JsonPatchDocument&lt;Person&gt;? patchDoc = JsonSerializer.Deserialize&lt;JsonPatchDocument&lt;Person&gt;&gt;(jsonPatch);
 
  // JSON Patch-Document anwenden auf das Person-Objekt
  patchDoc!.ApplyTo(person);
 
  CUI.H2("Ausgabe des Objekts in seinem neuen Zustand");
  CUI.Print(ITVisions.ObjectDumper.Dump(person));
}

Microsoft verspricht in der neuen Implementierung „eine verbesserte Leistung und weniger Speichernutzung im Vergleich zu der existierenden Implementierung in Newtonsoft.Json“ [9] in Verbindung mit einem ähnlichen Design wie bei Newtonsoft.Json, einschließlich der Aktualisierung eingebetteter Objekte und Arrays. Bisher nicht verfügbar ist JSON Patch für dynamische Objekte, weil Newtonsoft.Json dafür Reflection einsetzt, System.Text.Json soll aber auch mit dem NativeAOT-Compiler funktionieren.

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

Im NuGet-Paket Microsoft.AspNetCore.JsonPatch.SystemTextJson gibt es eine Klasse Microsoft.AspNetCore.JsonPatch.SystemTextJson.JsonPatchDocument<T> mit einer Methode ApplyTo (obj), die eine JSON-Patch-Operation auf das übergebene Objekt anwendet (Listing 8). Das resultierende JSON-Dokument für die Person nach Anwendung des JSON-Patch-Dokuments sieht dann aus wie in Abbildung 5.

Webserver-Ausgabe und automatisch generierte OpenAPI-Spezifikation für eine Minimal-API mit .NET 10.0 Preview

Abb. 5: Ausgabe des Listings 8

Alternativ kann man das Objekt natürlich auch als JSON ausgeben (Abb. 6). Das ist nur in dem obigen Listing nicht passiert, weil nicht der Eindruck entstehen soll, man müsse das Objekt als JSON weiterverwenden.

Abb. 6: Ausgabe des geänderten Objekts im JSON-Format

Mit JSON Patch kann man auch Werte in einem Objekt testen (Listing 9).

CUI.H2("Testen mit JSON Patch-Dokument");
string jsonPatchTest = """
[
  { "op": "test", "path": "/FirstName", "value": "Dr. Holger" },
  { "op": "test", "path": "/Email", "value": null},
  { "op": "test", "path": "/Address/ZipCode", "value": "45257" }
]
""";

// JSON Patch-Document laden
JsonPatchDocument&lt;Person&gt;? patchTestDoc = JsonSerializer.Deserialize&lt;JsonPatchDocument&lt;Person&gt;&gt;(jsonPatchTest);

// JSON Patch-Document anwenden auf das Person-Objekt
patchTestDoc!.ApplyTo(person, patchError =&gt; CUI.PrintError(patchError.ErrorMessage));

In diesem Fall gibt es keine Fehlerausgabe. Wenn man das Dokument aber verändert (Listing 10), bekommt man drei Fehlertexte (Abb. 7).

CUI.H2("Testen mit JSON Patch-Dokument");
string jsonPatchTest = """
[
  { "op": "test", "path": "/FirstName", "value": "Dr.Holger" },
  { "op": "test", "path": "/Email", "value": ""},
  { "op": "test", "path": "/Address/ZipCode", "value": "45251" }
]
""";

Abb. 7: Fehlertexte

Dabei ist die Fehlermeldung bei Email nicht gut, denn sie unterscheidet nicht zwischen Leerstring und Null. Im Objekt steht null, getestet wird auf Leerstring. In der Fehlermeldung steht in beiden Fällen ‘ ‘.

Entity Framework Core: verbesserte Suche mit CosmosDB

Entity Framework Core 10.0 unterstützt seit Preview 4 in Verbindung mit Azure CosmosDB zwei neue Suchmodi, die es dort seit März 2025 als Vorschauversion gibt: Volltextsuche [10] und hybride Suche, die die Vektorsuche und Volltextsuche mit einer Reciprocal Rank Fusion (RRF) Function kombiniert [11]. Microsoft hat zudem angekündigt [12], dass die in Entity-Framework-Core-Version 9.0 eingeführte Vektorsuche mit CosmosDB nun stabil ist.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

ASP.NET Core: OpenAPI Specification (OAS) in der WebAPI-AOT-Projektvorlage

Bisher war die Metadatengenerierung mit OpenAPI Specification (OAS) nur in der Projektvorlage ASP.NET Core Web API (Kurzname: webapi) aktiv, die keine Kompilierung mit dem NativeAOT-Compiler erlaubt. In der Projektvorlage ASP.NET Core Web API (nativeAOT) (Kurzname: webapiaot) mit aktivierter NativeAOT-Kompilierung war OAS zwar integrierbar, aber das mussten Entwicklerinnen und Entwickler manuell vornehmen.

Seit .NET 10.0 Preview 4 ist in der Projektvorlage ASP.NET Core Web API (nativeAOT) nun die Metadatengenerierung mit OAS im Standard aktiviert (Abb. 8). Mit dem Befehl

dotnet new webapiaot --name ITVisionsWebAPI

entsteht ein Projekt, das das NuGet-Paket Microsoft.AspNetCore.OpenApi referenziert und die OpenAPI-Unterstützung in der Program.cs mit

builder.Services.AddOpenApi();

und

app.MapOpenApi();

aktiviert.

In der Projektvorlage webapiaot können Entwicklerinnen und Entwickler die OpenAPI-Features wahlweise auch mit dem Parameter –no-openapi deaktivieren:

dotnet new webapiaot --name ITVisionsWebAPI --no-openapi

Abb. 8: Die neue Option für WebAPI-AOT-Projekte in Visual Studio

Mit aktiviertem OpenAPI-Support wird dann dieses NuGet-Paket eingebunden:

<PackageReference Include="Microsoft.AspNetCore.OpenApi" … />

In Program.cs steht dann:

builder.Services.AddOpenApi();
...
if (app.Environment.IsDevelopment())
{
  app.MapOpenApi();
}

Der Standard-URL des OpenAPI-Dokuments ist dann: https://localhost:PORT/openapi/v1.json.

ASP.NET Core: OpenAPI-Transformer für Minimal WebAPIs

Seit .NET 10.0 Preview 3 gibt es eine neue Methode AddOpenApiOperationTransformer(), mit der Entwicklerinnen und Entwickler das generierte OpenAPI-Dokument verändern können. Beispiel: Eine Operation /checkwebsite liefert normalerweise HTTP-Statuscode 200 mit der Beschreibung „OK“ zurück (Abb. 9).

Abb. 9: Ausschnitt aus dem OpenAPI-Dokument vor Veränderung des Antwortbeschreibungstexts und der Parameterliste

Dieser Text kann verändert werden mit Aufruf von AddOpenApiOperationTransformer(). Ebenso kann ein Parameter ergänzt werden, z. B. ein zusätzlicher Wert, der im Header des HTTP-Requests übergeben werden muss (Listing 11), (Abb. 10).

app.MapGet("/checkwebsite", Operations.CheckWebsite)
  .WithName("checkwebsite")
  .AddOpenApiOperationTransformer((operation, ContextBoundObject, ct) =&gt;
  {
    // Ergänzen eines Parameters, der im Header übergeben werden muss
    operation.Parameters.Add(new OpenApiParameter
    {
      Name = "Customer-GUID",
      In = ParameterLocation.Header,
      Description = "Your Customer GUID for Authentication",
      Required = true,
      Schema = new OpenApiSchema
      {
        Type = JsonSchemaType.String,
        Format = "uuid"
      }
    });
  
    // Änderung des Beschreibungstextes für Statuscode 200
    operation.Responses?["200"].Description = "Die Prüfung war erfolgreich";
  
    return Task.CompletedTask;
});

Abb. 10: Ausschnitt aus dem OpenAPI-Dokument nach Veränderung des Antwortbeschreibungstextes

ASP.NET Core: Validierung für ASP.NET Core Minimal WebAPIs

ASP.NET Core Minimal WebAPIs beherrschen seit .NET 10.0 Preview 3 auch die Parametervalidierung mit Data Annotations. Das konnten bisher nur die Controller-basierten WebAPIs [13].

Für Minimal WebAPIs müssen Entwicklerinnen und Entwickler das Validierungsfeature allerdings erst in der Projektdatei aktivieren mit

<PropertyGroup>
    <!-- Enable the generation of interceptors for the validation attributes -->
    <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated</InterceptorsNamespaces>
  </PropertyGroup>

und in der Startdatei mit

builder.Services.AddValidation();

Dann kann man auch in Minimal WebAPIs mit den bekannten Validierungsannotationen wie [MinLength], [MaxLength], [Range], [Url], [EmailAddress], [Phone], [CreditCard], [RegularExpression] u. v. m. [14] arbeiten, z. B.

app.MapGet("/checkwebsite", ([MinLength(5)] string name, [Url] string url)
  => WebsiteChecker.CheckWebsite(name, url));

Hier führt nun der HTTP-Aufruf https://Server:Port/checkwebsite?name=ITVisions&url=www.IT-Visions.de zur Laufzeit zur Rückgabe eines JSON-Dokuments zu zwei Validierungsfehlern (Abb. 11). Seit Preview 4 funktioniert das auch, wenn die Parameter ein Record-Typ sind.

Abb. 11: Beide Parameter beim Aufruf dieses ASP.NET Core Minimal WebAPI entsprachen hier nicht den Validierungsregeln

Blazor: deklarativer Persistent Component State

Microsoft Webframework ASP.NET Core erlaubt bei Blazor Server und Blazor WebAssembly bisher schon die Übergabe von Daten zwischen dem Prerendering und dem Hauptrendering via JSON-Anhang in HTML-Dokumenten. Dieser Persistent Component State war bisher aber recht aufwendig für die Entwicklerinnen und Entwickler in der Realisierung, denn man musste zunächst ein Objekt per Dependency Injection bekommen

@inject PersistentComponentState ApplicationState

und dann den Zustand als Objekt dort ablegen

private PersistingComponentStateSubscription? persistingSubscription;

persistingSubscription = ApplicationState.RegisterOnPersisting(() =>
  {
    ApplicationState.PersistAsJson("name", daten);
    return Task.CompletedTask;
});

und jeweils vor der Nutzung wieder explizit herausholen:

if (ApplicationState.TryTakeFromJson<Typ>("name", out var daten))
  {
    Daten = daten;
}

Das hat Microsoft in .NET 10.0 Preview 3 mit dem deklarativen Persistent Component State radikal vereinfacht. Entwicklerinnen und Entwickler müssen jetzt nur noch ein Property mit der Annotation [SupplyParameterFromPersistentComponentState] versehen und sich sonst um nichts kümmern:

[SupplyParameterFromPersistentComponentState]
public Daten? daten { get; set; }

Im Beispiel in Listing 12 sehen Sie auskommentiert die alte Variante im Vergleich zur neuen Variante.

@page "/State"
@using BO.WWWings
@* @inject PersistentComponentState ApplicationState *@

<PageTitle>Counter</PageTitle>

<h1>Flight-Counter <span class="badge bg-warning">@this.RendererInfo.Name</span></h1>

<p role="status" title="@pageState.LastChange">Current count: @pageState.CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">+</button>
<button class="btn btn-primary" @onclick="DecrementCount">-</button>

@if (this.pageState?.FlightSet != null)
{
  <hr noshade>
  <ol>
    @foreach (Flight f in this.pageState.FlightSet.Take(this.pageState.CurrentCount))
    {
      <li>Flug #@f.FlightNo (@f.FlightDate.ToShortDateString()) @f.Departure -> @f.Destination </li>
    }
  </ol>
}

@code {
  [SupplyParameterFromPersistentComponentState]
  public PageState pageState { get; set; } = null;
  
  private void IncrementCount()
  {
    pageState.CurrentCount++;
  }
  
  private void DecrementCount()
  {
    if (pageState.CurrentCount > 0) pageState.CurrentCount--;
  }
  
  // -----------------------------
  
  // private PersistingComponentStateSubscription? persistingSubscription;
  
  protected override void OnInitialized()
  {
  
    // persistingSubscription = ApplicationState.RegisterOnPersisting(() =>
    //     {
    //      ApplicationState.PersistAsJson(nameof(PageState), pageState);
    //      return Task.CompletedTask;
    //     });
  
    if (pageState == null)
    {
      DA.WWWings.WwwingsV1EnContext ctx = new();
      pageState = new() { 
        FlightSet = ctx.Flights.Take(100).ToList(), 
  
        CurrentCount = 10, Created = DateTime.Now };
    }

    // if (ApplicationState.TryTakeFromJson<PageState>(nameof(PageState), out PageState daten))
    // {
    //  pageState = daten;
    // }
  }
  
  public class PageState
  {
    public DateTime Created { get; set; }
    public DateTime LastChange { get; set; }
    public int _CurrentCount;
    public int CurrentCount
    {
      get
      {
        return _CurrentCount;
      }
      set
      {
        _CurrentCount = value;
        LastChange = DateTime.Now;
      }
    }
    public List<Flight> FlightSet { get; set; }
  }
}

Blazor: Erweiterungen der Klasse NavigationManager

Die in Blazor eingebauten Implementierungen der abstrakten Basisklasse Microsoft.AspNetCore.Components.NavigationManager haben in Blazor 10.0 Preview 4 zwei neue Mitglieder erhalten: NotFound() und OnNotFound().

Die neue Methode NotFound() signalisiert Blazor, dass eine angeforderte Ressource nicht vorhanden ist. Beim statischen Server-side Rendering bekommt der Webbrowser dann eine HTTP-Antwort mit Statuscode 404. Beim interaktiven Rendering (Blazor Server, Blazor WebAssembly, Blazor Hybrid) wird gerendert, was im Router bei <NotFound> definiert ist.

OnNotFound() ist ein Ereignis, das von Blazor ausgelöst wird, wenn die Methode NotFound() aufgerufen wird.

NavigationManager.NavigateTo() funktioniert nun auch beim statischen Server-side Rendering. Bisher lieferte dies immer den Laufzeitfehler NavigationException. Für Anwendungen, die diesen Fehler erwarten, hat Microsoft einen Schalter eingebaut, der das bisherige Verhalten zurückholt:

AppContext.SetSwitch("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException", isEnabled: true);

Blazor: Fingerprinting für Blazor WebAssembly Standalone

Das bereits in Blazor 9.0 eingeführte Fingerprinting (Anhängen eines Hashwerts auf Basis des Dateiinhalts an den Dateinamen, was vermeidet, dass Webbrowser alte Versionen aus dem Browsercache verwenden) funktioniert nun auch für die in Blazor eingebaute JavaScript-Datei in den sogenannten „Standalone Blazor WebAssembly“-Projekten, die nicht in ASP.NET Core betrieben werden, sondern auf einem statischen Webserver gehostet sein können.

Man verwendet dazu man in den Projekteinstellung

<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>

und im HTML-Kopfbereich in der index.html:

<link rel="preload" id="webassembly" />
<script type="importmap"></script>

Das <script>-Tag ist dann

<script src="proxy.php?url=_framework/blazor.webassembly#[.{fingerprint}].js"></script>

Diese Verbesserung ist seit .NET 10.0 Preview 4 auch in der Projektvorlage blazorwasm umgesetzt:

dotnet new blazorwasm -n ITVisionsDemo

Die index.html-Datei sieht dort so aus wie in Listing 13 gezeigt.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>NET10_BlazorWasmStandalone</title>
  <base href="/" />
  <link rel="preload" id="webassembly" />
  <link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="css/app.css" />
  <link rel="icon" type="image/png" href="favicon.png" />
  <link href="NET10_BlazorWasmStandalone.styles.css" rel="stylesheet" />
  <link href="manifest.webmanifest" rel="manifest" />
  <link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
  <link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
  < script type="importmap"></script>
</head>
 
<body>
  <div id="app">
    <svg class="loading-progress">
      <circle r="40%" cx="50%" cy="50%" />
      <circle r="40%" cx="50%" cy="50%" />
    </svg>
    <div class="loading-progress-text"></div>
  </div>
 
  <div id="blazor-error-ui">
    An unhandled error has occurred.
    <a href="." class="reload">Reload</a>
    <span class="dismiss">🗙</span>
  </div>
  < script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
  < script>navigator.serviceWorker.register('service-worker.js');</script>
</body>
 
</html>

Der Build-Prozess sorgt dafür, dass einige Ersetzungen stattfinden (siehe grüne Rahmen in den Abbildungen 12 und 13).

Abb. 12: Ersetzungen in der index.html durch den Build-Prozess (Teil 1)

Abb. 13: Ersetzungen in der index.html durch den Build-Prozess (Teil 2)

Wichtig: In bestehenden Projekten müssen Entwicklerinnen und Entwickler die Änderungen manuell vornehmen, um die Optimierungen zu nutzen.

Blazor: Umgebungen für Blazor WebAssembly

In Blazor-WebAssembly-Anwendungen mussten Entwicklerinnen und Entwickler die Umgebungsdefinition (Development, Staging, Production) bisher per HTTP-Header vornehmen [15]. Nun nimmt Microsoft beim Kompilieren von Blazor-WebAssembly-Anwendungen automatisch folgende Umgebungen an:

  • bei dotnet build: Development
  • bei dotnet publish: Production

Entwicklerinnen und Entwickler können die Umgebungsart auch per Projekteinstellung in der Projektdatei (.csproj) explizit setzen, z. B. für die Umgebung Staging:

<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Blazor: Response Streaming im HttpClient in Blazor WebAssembly

In Blazor-WebAssembly-basierten Anwendungen ist in der Klasse System.Net.Http.HttpClient im Standard nun das Response Streaming aktiv, um die Leistung zu erhöhen und den Speicherbedarf zu verringen (Listing 14, Abb. 14). Zuvor mussten Entwicklerinnen und Entwickler das Response Streaming manuell aktivieren mit

request.SetBrowserResponseStreamingEnabled(true);
@page "/HttpStreaming"
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using System.Net.Http
@using System.Diagnostics

@inject HttpClient HttpClient

<h1>HTTP Response Streaming</h1>

<button type="button" class="btn btn-primary" @onclick="Get">Laden</button>
<button type="button" class="btn btn-warning" @onclick="Stop">Abbrechen</button>
<input type="checkbox" @bind="streamingEnabled" /> Response Streaming aktivieren
<hr />
<p>Status: @status</p>
<p>Response Streaming aktiv: @streamingEnabled</p>
<p>Stream-Type: @streamType</p>
<p>Bytes gelesen: @byteCount</p>
<p>Dauer: @sw.ElapsedMilliseconds ms</p>

@code {
  int byteCount;
  string status = "";
  string streamType = "Unknown";
  bool streamingEnabled = false; // Set to true to enable streaming
  Stopwatch sw = new Stopwatch();
  CancellationTokenSource cts;
  
  // Laden
  async Task Get()
  {
    status = "Lade..."; 
    sw.Reset();
    sw.Start();
    cts = new CancellationTokenSource();
  
    using var request = new HttpRequestMessage(HttpMethod.Get, "https://www.it-visions.de/produkte/pdf/www.IT-Visions.de_Firmenbrosch%C3%BCre.pdf");
    if (streamingEnabled) request.SetBrowserResponseStreamingEnabled(true);
    else request.SetBrowserResponseStreamingEnabled(false);
 
    // Beim Streaming; HttpCompletionOption.ResponseHeadersRead / Der Standardwert ist ResponseContentRead, was bedeutet, dass der Vorgang erst abgeschlossen werden soll, nachdem die gesamte Antwort, einschließlich des Inhalts, aus dem Socket gelesen wurde.
    using var response = await HttpClient.SendAsync(request, streamingEnabled ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead);
  
    using Stream stream = await response.Content.ReadAsStreamAsync();
    streamType = stream.GetType().FullName; // Get the type of the stream: System.IO.MemoryStream oder System.Net.Http.HttpConnection+ContentLengthReadStream
 
    // Blockweises Lesen des Streams
    var bytes = new byte[10000];
    while (!cts.Token.IsCancellationRequested)
    {
      var read = await stream.ReadAsync(bytes, cts.Token);
      if (read == 0) // Ende des Streams erreicht
      {
        sw.Stop(); 
        status = "Fertig!";
        return;
      }
  
      byteCount += read;
 
      // UI-Update
      StateHasChanged();
      await Task.Delay(1);
    }
  }
  
  // Abbrechen
  void Stop()
  {
    sw.Stop();
    cts?.Cancel();
  }
}

Abb. 14: Screenshot des Beispiels aus Listing 14 unter .NET 9.0

In .NET 10.0 ist das Response Streaming nun automatisch aktiv und der Stream-Typ ist ein anderer (Abb. 15).

Abb. 15: Screenshot des Beispiels aus Listing 14 unter .NET 10.0

Durch das im Standard aktive Response Streaming sind allerdings nur noch asynchrone Operationen und keine synchronen Operationen mehr möglich. Ein Aufruf der synchronen Send()-Methode HttpClient.Send(request) führt dann zum Laufzeitfehler „Operation is not supported on this platform.“ Diese Verhaltensänderung gehört zu den Breaking Changes in .NET 10.0 [16].

Das Response Streaming im HttpClient ist in .NET 10.0 deaktivierbar mit:

request.SetBrowserResponseStreamingEnabled(false);

Alternativ ist die Deaktivierung global in der Projektdatei möglich:

<WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>

Blazor: neue Interoperabilitätsmethoden zwischen C# und JavaScript

In Blazor erweitert Microsoft die Schnittstellen für die Interoperabilität zwischen C# und JavaScript (IJSRuntime, IJSInProcessRuntime, IJSObjectReference und IJSInProcessObjectReference) um neue Mitglieder, um eine Instanz von einem JavaScript-Objekt zu erzeugen und dabei Konstruktorparameter zu übergeben (InvokeNew() und InvokeNewAsync()) sowie um Variablen oder Objekteigenschaften zu lesen (GetValue() und GetValueAsync()) bzw. zu setzen (SetValue()und SetValueAsync()).

Bisher konnte man von C# aus lediglich Funktionen in JavaScript mit Invoke(), InvokeVoid(), InvokeAsync() und InvokeVoidAsync() aufrufen. Die neuen Interoperabilitätsmethoden vermeiden in einigen Fällen, dass man eine JavaScript-Wrapper-Funktion schreiben muss; stattdessen kann man nun direkt Objekte erzeugen und Werte lesen/schreiben. Ein Beispiel sieht man in den Listings 15 und 16 sowie Abbildung 16.

window.JSUtil = class {
  constructor(text) {
    // zwei Eigenschaften der JavaScript-Klasse
    this.text = text;
    this.caseSensitive = false;
  }
  
  // Hole Textlänge
  getTextLength() {
    return this.text.length;
  }
  
  // Prüfe, ob Text in der aktuellen URL vorkommt
  containsTextInUrl() {
    const url = window.location.href;
    if (this.caseSensitive) return url.includes(this.text);
    else return url.toLowerCase().includes(this.text.toLowerCase());
  }

}
@inject IJSRuntime JSRuntime
@page "/interop"
@using ITVisions.Blazor
@inject BlazorUtil util

< script src="JSUtil.js"></script>
<h3>Blazor 10.0 C#/JavaScript-Interop</h3>
<hr noshade />
 
<button @onclick="Aktion">JavaScript aufrufen</button>
<br />

<ul>
@((MarkupString)Ausgabe)
</ul>


@code {
  
  protected override Task OnInitializedAsync()
  {
    return base.OnInitializedAsync();
  }
  
  public string Ausgabe { get; set; } = "";
  
  async Task Aktion()
  {
    // Eine einfache Variable setzen im Browser
    await JSRuntime.SetValueAsync<Guid>("Token", Guid.NewGuid());
    var token = await JSRuntime.GetValueAsync<string>("Token");
    Ausgabe = "<li>Aktuelles Token: " + token + "</li>";
  
    // Eine Eigenschaft des vorhandenen document-Objekts ändern
    var titleOld = await JSRuntime.GetValueAsync<string>("document.title");  
    var titleNew = "Interop-Demo: " + DateTime.Now.ToLongTimeString();
    await JSRuntime.SetValueAsync<string>("document.title", titleNew);
    Ausgabe += "<li>Title geändert von '" + titleOld + "' auf '" + titleNew + "'" + "</li>";
  
    // Ein neues JavaScript-Objekt erstellen und damit arbeiten
    var jsObj = await JSRuntime.InvokeNewAsync("JSUtil", "Localhost");
    var text = await jsObj.GetValueAsync<string>("text");
    var textLength = await jsObj.InvokeAsync<int>("getTextLength");
    await jsObj.SetValueAsync<bool>("caseSensitive", true);
    var caseSensitive = await jsObj.GetValueAsync<bool>("caseSensitive");
    var containsTextInUrl = await jsObj.InvokeAsync<bool>("containsTextInUrl");
    Ausgabe += $"<li>Die Zeichenkette {text} hat {textLength} Zeichen und kommt{(containsTextInUrl ? "" : " NICHT")} in der aktuellen URL vor. Der Vergleich war '{(caseSensitive ? "casesensitive" : "caseinsensitive")}'.</li>";
  }

}

Abb. 16: Ausgabe des Beispiels der Listing 15 und 16

Blazor: automatisches Schließen des Filterkriteriendialogs im QuickGrid via HideColumnOptionsAsync()

Die in ASP.NET Core 10.0 Preview 2 eingeführte Methode CloseColumnOptionsAsync() heißt seit Preview 4 nun HideColumnOptionsAsync(). Damit kann man den Eingabedialog für Filterkriterien im Steuerelement <QuickGrid> schließen, wenn der Filter angewendet wurde (Listing 17).

<QuickGrid @ref="flightGrid" RowClass="ApplyRowClass" ItemsProvider="@itemsProvider" TGridItem="BO.WWWings.Flight" Virtualize="true" OverscanCount="@overscanCount">
  <PropertyColumn Property="@(p => p.FlightNo)" Title="FlugNr" Sortable="true" />
  <PropertyColumn Property="@(p => p.Departure)" Title="Abflugort" Sortable="true">
  
    <ColumnOptions>
      <input type="search" @bind="departureFilter" placeholder="Filter by Departure"
        @bind:after="@(() => flightGrid.HideColumnOptionsAsync())" />
    </ColumnOptions>
 
  </PropertyColumn>
  <PropertyColumn Property="@(p => p.Destination)" Title="Zielort" Sortable="true" />
  <PropertyColumn Property="@(p => p.FlightDate)" Title="Datum" Format="dd.MM.yyyy" Sortable="true" />
</QuickGrid>

Blazor: Diagnosedaten für Blazor WebAssembly

Eine größere neue Diagnosefunktion in .NET 10.0 bekam in Preview 4 die WebAssembly-basierte Variante von Blazor, die als Single Page App im Webbrowser läuft: Entwicklerinnen und Entwickler können im Webbrowser zur Laufzeit Diagnosedaten über Performance, Speicherinhalt und diverse Metriken sammeln.

Dazu muss man als Voraussetzung die .NET WebAssembly Build Tools in der aktuellen .NET-10.0-Version als .NET-SDK-Workload installieren (Abb. 17):

dotnet workload install wasm-tools

Abb. 17: Bei dotnet workload list sollte die jeweils aktuelle Version erscheinen (hier im Bild: Preview 4 von .NET 10.0)

In den .NET WebAssembly Build Tools gibt es zwei neue Funktionen:

  • Performanceanalyse in den Browser-Entwicklerwerkzeugen („F12-Tools“)
  • Sammeln und Herunterladen von Tracedateien und Memory Dumps aus dem Browser

Für die Nutzung der Performanceanalyse in den Browser-Entwicklerwerkzeugen gibt es in der Projektdatei die in Tabelle 1 aufgelisteten Einstellungen (Listing 18).

Tabelle 1: Projekteinstellungen für Blazor-WebAssembly-Diagnosedaten (Quelle: [17])

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
...
  <!-- NEU: Blazor WebAssembly Runtime Diagnostics  -->
  <PropertyGroup>
  
    <!-- Für Anzeige von "Timings" im Performance-Tab der Browser-Developer-Tools-->
    <WasmProfilers>browser</WasmProfilers>
    <WasmNativeStrip>false</WasmNativeStrip>
    <WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
  </PropertyGroup>
</Project>

Mit diesen Einstellungen erhält man in den Browser-Entwicklerwerkzeugen in der Registerkarte Performance eine Ansicht Timings mit der Ablauffolge der aufgerufenen .NET-Methoden (Abb. 18).

Abb. 18: Aufrufhierarchie der Methoden im Browser mit Zeitverbrauch in den Browser-Entwicklerwerkzeugen unter „Performance | Timings“

Mit den folgenden Einstellungen in der Projektdatei erlaubt man das Sammeln von Tracedaten und Memory Dumps einer Blazor-WebAssembly-Anwendung im Webbrowser. Die Daten werden als .nettrace-Datei im Browser heruntergeladen (Tabelle 2, Listing 19).

Tabelle 2: Projekteinstellungen für Blazor-WebAssembly-Diagnosedaten (Quelle: [17])

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  ...
  <!-- NEU: Blazor WebAssembly Runtime Diagnostics  -->
  <PropertyGroup>
  
    <!-- Für Trace- und Memory-Dump-Files -->
    <WasmPerfTracing>true</WasmPerfTracing>
    <WasmPerfInstrumentation>all</WasmPerfInstrumentation>
    <EventSourceSupport>true</EventSourceSupport>
    <MetricsSupport>true</MetricsSupport>

  </PropertyGroup>
</Project>

Danach kann man per JavaScript APIs auf die Diagnosedaten zugreifen. Diese Befehle gibt man in der Browserkonsole in den Browser-Entwicklerwerkzeugen ein:

  • CPU-Nutzung sammeln für die nächsten 5 Sekunden:
globalThis.getDotnetRuntime(0).collectCpuSamples({durationSeconds: 5});
  • Metriken sammeln für die nächsten 5 Sekunden:
globalThis.getDotnetRuntime(0).collectPerfCounters({durationSeconds: 5});
  • Speicherabbildung erstellen:
globalThis.getDotnetRuntime(0).collectGcDump();

Entwicklerinnen und Entwickler erhalten die gesammelten Daten in einer .nettrace-Datei im Downloads-Ordner des Webbrowsers (Abb. 19).

Abb. 19: Herunterladen der Memory-Dump-Datei in Format .nettrace

Die .nettrace-Dateien mit CPU- und Performance-Counter-Dateien kann man ohne weitere Schritte in Visual Studio öffnen, indem man die Datei per File | Open öffnet oder einfach per Drag and Drop in den Dateibereich von Visual Studio zieht (Abb. 20 und 21).

Abb. 20: Ansicht der CPU-Nutzung der Blazor-WebAssembly-Anwendung in Visual Studio

Abb. 21: Nach Klick auf System.String.Join() sieht man die Anzahl der Aufrufe und die CPU-Nutzungszeit

Während man .netrace-Dateien mit CPU- und Performance-Counter-Dateien direkt in Visual Studio öffnen kann, muss man eine Speicherabbilddatei zunächst mit dem .NET-CLI-Werkzeug dotnet-gcdump [18] in eine .gcdump-Datei für Visual Studio konvertieren (Abb. 22).

Die Installation der aktuellen Version dieses Werkzeugs erfolgt mit

dotnet tool install --global dotnet-gcdump

Die Konvertierung einer .nettrace-Datei in eine .gcdump-Datei geht dann so:

dotnet-gcdump convert Dateiname.nettrace

Abb. 22: Konvertieren einer Speicherabbilddatei von .nettrace zu .gcdump

Die erzeugte .gcdump-Datei kann man dann in Visual Studio betrachten (Abb. 23). Man sieht, welche Typen wie oft instanziiert wurden und wie viele Bytes sie verbrauchen. Die Spalte Size (Bytes) sind dabei die Bytes, die der Typ selbst verbraucht. inclusive Size (Bytes) umfasst auch die von Unterobjekten verbrauchten Speicherplätze.

Abb. 23: Betrachten der Memory-Dump-Datei (.gcdump) in Visual Studio

Weitere Neuerungen

Über die in diesem Beitrag besprochenen Neuerungen hinaus bietet .NET 10.0 einige kleinere Verbesserung für Validation Context, Telemetry und ML.NET, die man in den Release Notes zu .NET 10.0 Preview 3 findet [19].

Bei Windows Forms und WPF verwendet Microsoft seit .NET 10.0 Preview 4 den gleichen Programmcode für die Arbeit mit der Zwischenablage [20]. Das erleichtert Microsoft die Wartung des Programmcodes; Entwicklerinnen und Entwickler haben davon aber erstmal nichts.

Ausblick

Bis zum geplanten Erscheinen von .NET 10.0 im November 2025 werden noch drei weitere Preview-Versionen (Preview 5, 6 und 7) und zwei Release-Candidate-Versionen erscheinen. Mit der fertigen Version kann am 11. November 2025 gerechnet werden, denn Microsoft hat bereits eine virtuelle .NET Conf für den 11. bis 13. November 2025 angekündigt.

Als gerade Versionsnummer wird .NET 10.0 wieder Long Term Support (LTS) für 36 Monate erhalten, also bis November 2028, während der Support für .NET 9.0 schon im Mai 2027 enden wird. Weiterhin ist aktuell .NET 8.0 im Support, das Microsoft noch bis November 2026 unterstützen wird.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Frequently Asked Questions (FAQ)

1. Wann wurden .NET 10 Preview 3 und Preview 4 veröffentlicht und wie installieren?

Preview 3 erschien am 10. April 2025, und Preview 4 folgte am 13. Mai 2025. Zur Nutzung benötigt man das .NET 10 SDK (inklusive der drei Laufzeitumgebungen) sowie Visual Studio 2022 Version 17.14, die gemeinsam installiert werden können.

2. Welche C#-Neuerungen bringt Preview 3?

Mit C# 14.0 führt Preview 3 sogenannte Extension Members ein – eine Erweiterung bestehender Klassen um Methoden und Eigenschaften (instanz- sowie statisch), über das neue extension-Schlüsselwort, abseits der bisherigen this-basierenden Methoden. Zudem erlaubt das Null-Conditional Assignment (?.) das sichere Zuweisen von Eigenschaften ohne vorherige Null-Prüfung.

3. Welche Features enthält das SDK ab Preview 4?

Ab Preview 4 können einzelne C#-Dateien direkt mit dotnet run ausgeführt werden – ohne Projektdatei. Die Skript-Dateien nutzen Präprozessor-Direktiven (#:sdk, #:package, #:property), um z. B. SDK, NuGet-Pakete oder Build-Parameter zu definieren.

4. Was gibt es Neues in der Basis-Klassenbibliothek (BCL)?

Die BCL erweitert seit Preview 4 die System.IO.Compression-APIs um asynchrone ZIP-Operationen, etwa CreateFromDirectoryAsync(), ExtractToDirectoryAsync(), OpenAsync() oder CreateAsync().

5. Welche Verbesserungen gibt es beim Zertifikatsexport?

Seit Preview 3 ermöglicht die neue Methode ExportPkcs12() den Export von Zertifikaten mit AES‑Verschlüsselung und SHA‑2‑256 Hashing – sicherer als der alte Export()-Ansatz mit 3DES/SHA‑1.

6. Was ändert sich bei JSON-Patch?

Preview 4 führt native Unterstützung für den JSON‑Patch‑Standard (RFC 6902) in System.Text.Json ein – via dem neuen NuGet-Paket Microsoft.AspNetCore.JsonPatch.SystemTextJson. So kann JSON‑Patch ohne Newtonsoft.Json genutzt werden (außer unter NativeAOT).

7. Welche Neuerungen bringt Entity Framework Core mit CosmosDB?

EF Core 10.0 unterstützt in Preview 4 mit Azure Cosmos DB nun Volltextsuche und eine hybride Suche, die Vektorsuche und Volltextsuche kombiniert – inklusive Reciprocal Rank Fusion (RRF). Die zuvor als Vorschau verfügbare Vektorsuche ist damit in EF Core stabilisiert.

8. Was ist neu bei ASP.NET Core / OpenAPI und Minimal APIs?

  • In Web API Native AOT–Vorlage ist OpenAPI-Spec (OAS) nun standardmäßig aktiviert.

  • Seit Preview 3 gibt es AddOpenApiOperationTransformer(), mit dem generierte OpenAPI-Dokumente modifiziert werden können (z. B. Beschreibung oder Header-Parameter).

  • Minimal WebAPIs erhalten Parametervalidierung über DataAnnotations, durch Aktivieren (AddValidation()) auch für Record-Typen ab Preview 4.

8. Welche Neuerungen bringt Blazor?

  • Persistent Component State kann jetzt einfach durch [SupplyParameterFromPersistentComponentState] verwendet werden (Preview 3).

  • In Preview 4 erweitert sich NavigationManager um NotFound() (Signalisiert 404 im statischen Rendering) und das Ereignis OnNotFound(). Zudem funktioniert NavigateTo() im statischen Rendering out-of-the-box, optional kann Verhalten per Switch angepasst werden.

  • Standalone Blazor WebAssembly nutzt nun automatisch Fingerprinting für die eingebundene JS-Datei, um Caching-Probleme zu vermeiden.

  • Response Streaming im HttpClient (WASM) ist jetzt standardmäßig aktiviert – was effizienter ist, aber synchrones Send() nicht mehr unterstützt (Breaking Change). Abschaltbar via Projektfile oder SetBrowserResponseStreamingEnabled(false).

  • JS-Interop wurde um Methoden erweitert: InvokeNew, GetValue, SetValue (sync + async), um z. B. JS-Objekte direkt zu erstellen oder Eigenschaften zu lesen/setzen.

  • In QuickGrid wurde CloseColumnOptionsAsync() umbenannt in HideColumnOptionsAsync().

  • Runtime Diagnostics für Blazor WebAssembly (Preview 4): Performance- und Memory-Dumps sind im Browser möglich, mittels wasm-tools Workload und entsprechenden Projektsettings (WasmProfilers, WasmNativeDebugSymbols).


Links & Literatur

[1] https://dotnet.microsoft.com/en-us/download/dotnet/10.0

[2] https://github.com/oleg-shilo/cs-script

[3] https://github.com/dotnet-script/dotnet-script

[4] https://cakebuild.net

[5] https://www.it-visions.de/scripting/dotnetscripting/dsh

[6] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview2/libraries.md#encryption-algorithm-can-now-be-specified-in-pkcs12pfx-export

[7] https://dotnet-lexikon.de/SystemTextJson/Lex/10722

[8] https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson/10.0.0-preview.4.25258.110

[9] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview4/aspnetcore.md#json-patch-with-systemtextjson

[10] https://learn.microsoft.com/en-us/azure/cosmos-db/gen-ai/full-text-search

[11] https://learn.microsoft.com/en-us/azure/cosmos-db/gen-ai/hybrid-search

[12] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview4/efcore.md#vector-similarity-search-exits-preview

[13] https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api

[14] https://learn.microsoft.com/de-de/dotnet/api/system.componentmodel.dataannotations?view=net-9.0

[15] https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/environments?view=aspnetcore-9.0#set-the-client-side-environment-via-header

[16] https://learn.microsoft.com/en-us/dotnet/core/compatibility/10.0

[17] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview4/aspnetcore.md#blazor-webassembly-runtime-diagnostics

[18] https://learn.microsoft.com/de-de/dotnet/core/diagnostics/dotnet-gcdump

[19] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview3/libraries.md

[20] https://github.com/dotnet/winforms/issues/12179

The post .NET 10 Preview 3 & 4: Das steckt drin appeared first on BASTA!.

]]>
Was bringt .NET 10.0? Erste Einblicke in Preview 1 & 2 https://basta.net/blog/dotnet-10-preview-neuerungen/ Mon, 26 May 2025 14:24:30 +0000 https://basta.net/?p=107474 Microsoft hat die Vorschaureihe zu .NET 10.0 gestartet und wie bei jeder Version berichtet DOTNET-DOKTOR Holger Schwichtenberg wieder über die laufenden Entwicklungen.
.NET 10.0 soll im November 2025 als Nachfolger des im November 2024 als stabile Version veröffentlichen .NET 9.0 erscheinen. Als gerade Versionsnummer wird .NET 10.0 wieder Long-Term-Support (LTS) für 36 Monate erhalten, also bis November 2028, während der Support für .NET 9.0 schon im Mai 2027 ausläuft.

The post Was bringt .NET 10.0? Erste Einblicke in Preview 1 & 2 appeared first on BASTA!.

]]>
.NET 10.0 Preview 1 ist am 25. Februar 2025 erschienen. Am 18. März 2024 folgte Preview 2. Die Vorschauversionen stehen bei Microsoft bereit zum Download [1].

 

An der Grundstruktur (drei Runtimes, ein SDK) hat sich nichts geändert [1]. Auch die unterstützten Plattformen sind gleichgeblieben. Laut dem Screenshot der Downloadseite in Abbildung 1 sind weiterhin die Sprachversionen C# 13.0, F# 9.0 und Visual Basic .NET 17.13 enthalten; das ist aber nicht richtig, denn es gibt in .NET 10.0 bereits erste neue Sprachfeatures von C# 14.0.

Screenshot der offiziellen .NET 10.0-Downloadseite mit Informationen zur Vorschauversion 10.0.100-preview.2

Abb. 1: .NET-10.0-Downloadseite [1]

Zielframework und Sprachversion setzen

Für .NET 10.0 braucht man die Preview-Version von Visual Studio 2022 Version 17.14 (zum Redaktionsschluss für diesen Beitrag verfügbar auf dem Stand Preview 2, Abb. 2). Alternativ kann man das Target Framework auch manuell setzen:

<TargetFramework>net10.0</TargetFramework>

Allerdings reicht es zur Nutzung der neuen Sprachfeatures derzeit nicht aus, nur das Target Framework zu setzen; Man muss auch noch

<LangVersion>preview</LangVersion>

angeben, um alle neuen Sprachfeatures nutzen zu können.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]
Visual Studio 2022 (17.14 Preview 2) zeigt .NET 10.0 als auswählbares Ziel-Framework für neue Projekte

Abb. 2: .NET 10.0 erscheint bereits in Visual Studio 2022 17.14 Preview 2

C# 14.0: Vereinfachung für nameof() mit generischen Typen

Bisher musste man in C# immer konkrete Typparameter angeben, um den Operator nameof() auf generische Typen anzuwenden. Jetzt, in C# 14.0, kann man als kleine Syntaxvereinfachung auch die Typparameter im Code weglassen (Listing 1).

<em>// BISHER</em>
Console.WriteLine(nameof(List<int>)); <em>// --> List</em>
Console.WriteLine(nameof(System.Collections.Generic.LinkedListNode<int>)); <em>// --> LinkedListNode</em>
<em>Console.WriteLine(nameof(System.Collections.Generic.KeyValuePair<int, string>)); // --> KeyValuePair</em>

<em>// NEU</em>
Console.WriteLine(nameof(List<>)); <em>// --> List</em>
Console.WriteLine(nameof(System.Collections.Generic.LinkedListNode<>)); <em>// --> LinkedListNode</em>
Console.WriteLine(nameof(System.Collections.Generic.KeyValuePair<,>)); <em>// --> KeyValuePair</em>

C# 14.0: Vereinfachungen bei Lambdaausdrücken

In Lambdaausdrücken kann man in C# 14.0 jetzt Parameter-Modifizierer wie scopedrefinout und _ref readonly_verwenden, ohne dabei den Datentyp benennen zu müssen. Als Beispiel für den delegate

delegate bool Extract&lt;T&gt;(string text, out T result);

musste man vor C# 14.0 schreiben:

Extract<int> ExtractOld = (string text, out int result) => Int32.TryParse(text, out result);

Jetzt in C# 14.0 können Entwickler:innen im Lambdaausdruck die Nennung der Datentypen _string_und int weglassen, weil diese Datentypen aus dem Kontext bereits klar sind:

Extract<int> ExtractNew = (text, out result) => Int32.TryParse(text, out result);

Das geht allerdings nicht, wenn ein variadischer Parameter mit params zum Einsatz kommt:

Add<int> AddOld = (out int result, params List<int> data) => { result = data.Sum(); return true; };

Nicht möglich ist also diese Verkürzung:

Add<int> AddNew = (out result, params data) => { result = data.Sum(); return true; };

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

C# 14.0: mehr Konvertierungen für Span und ReadOnlySpan

Im Rahmen der Initiative „First-Class Span Types” [2] soll C# 14.0 neue automatische Konvertierungen zwischen Arrays und Span sowie ReadOnlySpan enthalten. Dabei waren einige Konvertierungen bisher schon möglich und da das Dokument „What’s new in C# 14″ [3] nicht genau auflistet, was die Neuerungen sind, sondern nur auf die Liste aller möglichen Konvertierungen verweist, fällt es schwer, herauszufinden, was wirklich neu ist.

In einem Test konnte aber folgender Fall gefunden werden: Wenn die Klasse Developer von der Basisklasse Person erbt, dann kann ein Array

Developer[] devs = new Developer[3];

in C# 14.0 wie folgt konvertiert werden:

Span<Developer> devsSpan = devs;
ReadOnlySpan<Developer> devsROSpan = devs;
ReadOnlySpan<Person> personROSpan = devs;
ReadOnlySpan<Person> personROSpanFromSpan = devsSpan;

In C# 13.0 war hier nur der letzte Fall erlaubt, wenn man dort preview setzte. Vermutlich wird auch dieses Sprachfeatures in C# 14.0 dann als stabil gelten.

C# 14.0: Schlüsselwort field

Im Dokument „What’s new in C# 14″ [3] beschreibt Microsoft zudem das Schlüsselwort field, mit dem man sogenannte Semi-Auto Properties erstellen kann (Listing 2). Auch dieses Sprachfeature gibt es schon in der stabilen Version von .NET 9.0 – darin aber im Status Preview, d. h., man musste auch dafür preview setzen. Die Erwähnung in „What’s new in C# 14″ legt die Vermutung nahe, dass das Sprachfeature in C# 14.0 dann als stabil gelten wird.

<em>/// <summary></em>
<em>/// Semi-Auto Property</em>
<em>/// </summary></em>
public int ID
{
  get;
  set <em>// init w&#xE4;re hier auch erlaubt!</em>
  {
    if (value < 0) throw new ArgumentOutOfRangeException();
    if (field > 0) throw new ApplicationException("ID schon gesetzt");
    field = value;
  }
} = -1;

C# 14.0: partielle Konstruktoren und partielle Events

C# kennt seit Version 2.0 partielle Klassen und seit Version 3.0 partielle Methoden. Im Jahr 2024 kamen in C# 13.0 partielle Properties und Indexer hinzu. Nun in C# 14.0 sollen Entwickler:innen das Schlüsselwort partial auch auf Konstruktoren und Events in C#-Klassen anwenden können (Listing 3). So es steht in den Releases-Notes zu C# in .NET 10.0 Preview 2 [4].

partial class C
{
  partial C(int x, string y);
  partial event Action<int, string> MyEvent;
}

partial class C
{
  partial C(int x, string y) { }
  partial event Action<int, string> MyEvent
  {
    add { }
    remove { }
  }
}

Der C#-Compiler möchte das Beispiel aus der Dokumentation [5] aber noch nicht übersetzen und meldet Fehler sowohl in Visual Studio als auch beim Kommandozeilenbefehl dotnet build (Abb. 3 und 4), selbst wenn man preview in die Projektdatei schreibt. Laut [6] kommt das Feature in Visual Studio auch erst mit Version 17.4 Preview 3; diese Version war zum Redaktionsschluss noch nicht verfügbar.

Kompilierungsfehler in Visual Studio: Ungültige Verwendung von partial für Konstruktoren und Events in .NET 10.0 Preview 2

Abb. 3: Kompilierungsfehler in Visual Studio beim Versuch der Verwendung von partiellen Konstruktoren und partiellen Events in .NET 10.0 Preview 2

Kommandozeilen-Ausgabe zeigt Kompilierungsfehler beim Versuch, partielle Konstruktoren und Events in .NET 10.0 Preview 2 zu verwenden

Abb. 4: Kompilierungsfehler auf der Kommandozeile beim Versuch der Verwendung von partiellen Konstruktoren und partiellen Events in .NET 10.0 Preview 2

Visual Basic .NET: OverloadResolutionPriority

Auch beim Visual-Basic-.NET-Compiler gibt es nach langer Stagnation mal wieder kleine Neuerungen: Der Compiler hält sich nun an die in .NET 9.0 und C# 13.0 eingeführte Annotation [OverloadResolutionPriority] zur Priorisierung von Überladungen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

.NET SDK: vereinheitlichte Kommandozeilenparameterreihenfolge

In dem .NET-SDK-Kommandozeilenwerkzeug _dotnet_hat Microsoft in .NET 10.0 die Parameterreihenfolge vereinheitlicht. Es gab bisher viele Befehle, die aus einem Substantiv gefolgt von einem Verb oder Adjektiv bestanden (z. B. dotnet new listdotnet workload installdotnet nuget add).

Es gab aber auch einige Befehle, bei denen am Anfang ein Verb stand, z. B. dotnet add reference und dotnet remove package. Das hat Microsoft nun vereinheitlicht zu folgenden neuen Befehlen:

dotnet package add
dotnet package list
dotnet package remove
dotnet reference add
dotnet reference list
dotnet reference remove

Die alten Kommandozeilenbefehle mit dem Verb vorne sind aber weiterhin vorhanden, sodass hier kein Breaking Change entsteht.

.NET SDK: neuer Standard bei Workload Sets

Das .NET SDK verwendet bei der Workload-Verwaltung in .NET 10.0 nun als Standard den in .NET 9.0 eingeführten Modus „Workload Sets” anstelle des bisherigen Standards, den Microsoft „Loose Manifests” nennt [7].

Im .NET SDK gibt es zudem eine Optimierung für die Beschleunigung des Package Restore [8].

Basisklassenbibliothek: LINQ-Operatoren LeftJoin() und RightJoin()

Wie in den letzten .NET-Versionen auch, liefert Microsoft in .NET 10.0 wieder neue Operatoren für Language Integrated Query (LINQ), die bestehende Konstrukte vereinfachen. Dieses Mal sind es mit LeftJoin() und RightJoin() zwei elementare Operatoren aus der Mengenlehre und den relationalen Datenbanken. Tatsächlich waren diese Operationen in LINQ bereits möglich, allerdings umständlich mit Hilfe einer Gruppierung mit GoupJoin() und SelectMany() sowie DefaultIfEmpty(). Die neuen Methoden LeftJoin() und RightJoin() vereinfachen die Verwendung, wie Listing 4 am Beispiel des Joins zwischen den Klassen Company und _Website_zeigt, die Ausgabe ist in Abbildung 5 zu sehen.

Company[] companies =
  [
    new Company{ ID = 1, Name = "www.IT-Visions.de" },
    new Company{ ID = 2, Name = "Software & Support" },
    new Company{ ID = 3, Name = "Startup i.Gr." } <em>// hat noch keine Website</em>
  ];

Website[] websites =
  [
    new Website{ CompanyID = 1, URL = "www.IT-Visions.de" },
    new Website{ CompanyID = 1, URL = "www.dotnet10.de" },
    new Website{ CompanyID = 2, URL = "www.entwickler.de" },
    new Website{ URL = "www.Microsoft.com" }, <em>// Firma ist noch nicht angelegt</em>
  ];

CUI.H2("--- LeftJoin ALT seit .NET Framework 3.5 ---");

var AllCompaniesWithWebsitesSetOld = companies
  .GroupJoin(websites,
    c => c.ID,
    w => w.CompanyID,
    c, websites) => new { Company = c, Websites = websites })
  .SelectMany(
    x => x.Websites.DefaultIfEmpty(), <em>// Falls keine Webseite existiert, wird `null` verwendet</em>
    (c, w) => new WebsiteWithCompany
    {
      Name = c.Company.Name,
      URL = w.URL, <em>// Falls `w` null ist, bleibt URL null</em>
      City = c.Company.City
  });

foreach (var item in AllCompaniesWithWebsitesSetOld)
{
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}

CUI.H2("--- LeftJoin NEU ab .NET 10.0 ---");
var AllCompaniesWithWebsitesSet = companies.LeftJoin(websites, e => e.ID, e => e.CompanyID, (c, w) => new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL });
foreach (var item in AllCompaniesWithWebsitesSet)
{
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}

CUI.H2("--- RightJoin OLD seit .NET Framework 3.5  ---");
var WebsiteWithCompanySetOLD = websites
  .GroupJoin(companies,
    w => w.CompanyID,
    c => c.ID,
    (w, companies) => new { Website = w, Companies = companies })
  .SelectMany(
    x => x.Companies.DefaultIfEmpty(), <em>// Falls kein Unternehmen existiert, bleibt `null`</em>
    (w, c) => new WebsiteWithCompany
    {
      Name = c.Name, <em>// Falls `c` null ist, bleibt `Name` null</em>
      City = c.City, <em>// Falls `c` null ist, bleibt `City` null</em>
      URL = w.Website.URL
  });

foreach (var item in WebsiteWithCompanySetOLD)
{
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}

CUI.H2("--- RightJoin NEU ab .NET 10.0 ---");
var WebsiteWithCompanySet = companies.RightJoin(websites, e => e.ID, e => e.CompanyID, (c, w) => new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL });

foreach (var item in WebsiteWithCompanySet)
{
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}

<em>// Zum Vergleich: Inner Join, den es seit .NET Framework 3.5 gibt</em>
CUI.H2("--- InnerJoin seit .NET Framework 3.5 ---");
var CompaniesWithWebsitesSet = companies.Join(websites,
  c => c.ID,
  w => w.CompanyID,
  (c, w) => new WebsiteWithCompany
  {
    Name = c.Name,
    URL = w.URL,
    City = c.City
});
foreach (var item in CompaniesWithWebsitesSet)
{
  Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}
Terminalausgabe mit verschiedenen Join-Arten in .NET: LeftJoin, RightJoin und InnerJoin mit historischer Einordnung

Abb. 5: Ausgabe von Listing 4

Basisklassenbibliothek: TryAdd() und TryGetValue() für OrderedDictionary<T,T>

Die erst in .NET 9.0 neu eingeführte generische Klasse System.Collections.Generic.OrderedDictionary<T,T> bot bisher schon eine Methode TryAdd(), die versucht, ein Element hinzuzufügen. Neben der bestehenden Variante TryAdd(TKey key, TValue value) gibt es nun in .NET 10.0 auch die Methode TryAdd(TKey key, TValue value, out int index) (Listing 5). Diese neue Überladung liefert den Index zurück, falls es das Element in der Menge schon gibt. Analog dazu gibt es für die Methode TryGetValue() nun eine neue Überladung, die nicht nur den Wert eines Eintrags liefert, sondern auch die Position des gefundenen Elements per Index.

OrderedDictionary<string, string> websites = new OrderedDictionary<string, string>();
websites.Add("Software & Support", "www.Entwickler.de");
websites.Add("Microsoft", "www.Microsoft.com");
websites.Add("IT-Visions", "www.IT-Visions.de");

var propertyName = "IT-Visions";
var value = "www.IT-Visions.de";

<em>// bisher</em>
if (!websites.TryAdd(propertyName, value))
{
  int index1 = websites.IndexOf(propertyName); <em>// Second lookup operation</em>
  CUI.Warning("Element " + value + " ist bereits vorhanden!");
}

<em>// neu</em>
if (!websites.TryAdd(propertyName, value, out int index))
{
  CUI.Warning("Element " + value + " ist bereits vorhanden an der Position " + index + """!""");
}

<em>// neu</em>
if (websites.TryGetValue(propertyName, out string? value2, out int index2))
{
  CUI.Success($"Element {value2} wurde gefunden an der Position {index2}.");
}

Basisklassenbibliothek: IP-Adressen prüfen

Zur Prüfung von IP-Adressen gibt es in der Klasse System.Net schon seit .NET Framework 2.0 die statische Methode IPAddress.TryParse(), die eine IP-Adresse aus einer Zeichenkette extrahiert. Seit .NET Core 2.1 ist die Extraktion auch aus dem Typ ReadOnlySpan möglich. Der Rückgabewert ist ein bool-Wert und die extrahierte IP-Adresse wird in Form einer Instanz der Klasse IPAddress als out-Parameter geliefert. Wenn man nur prüfen will, ob die IP-Adresse stimmt, schreibt man _IPAddress.TryParse(eingabe, out ).

In .NET 10.0 bietet Microsoft nun in der statischen Methode _IsValid()_eine weitere Prüfungsvariante mit weniger internem Aufwand. Beispiel:

System.Net.IPAddress.IsValid("192.168.1.0")

Dabei kehrt Microsoft nun einfach eine bisher interne Methode TargetHostNameHelper.IsValidAddress() nach außen [9].

 

Basisklassenbibliothek: Verbesserung beim Zertifikatsexport

Seit .NET 10.0 Preview 2 soll laut Release Notes in der Klasse X509Certificate2 eine neue Methode ExportPkcs12() existieren, die Entwickler:innen mehr Kontrolle über die Verschlüsselungs- und Digest-Algorithmen geben soll. Die bisherige Methode Export() verwendet noch veraltete Algorithmen wie 3DES und SHA1. Die neue Methode ExportPkcs12() soll auch AES und SHA-2-256 bieten.

In den Release Notes zu .NET 10.0 Preview 2 [10] fehlt allerdings ein Beispiel und im Test konnte der Compiler weder auf der Klasse X509Certificate2 noch der Basisklasse X509Certificate die Methode ExportPkcs12() finden. Das zugehörige Issue auf GitHub steht auf dem Status Closed [11]. Da sich im Change-Log zu .NET 10.0 Preview 2 [12] auch kein Eintrag zu ExportPkcs12() finden lässt, sind die Release-Notes an dieser Stelle vermutlich voreilig und die neue Methode kommt erst in einer späteren Vorschauversion.

Basisklassenbibliothek: weitere Verbesserungen

Außerdem hat Microsoft diese drei Punkte in der .NET-Basisklassenbibliothek in .NET 10.0 Preview 1 und 2 verbessert:

  • In der Klasse X509Certificate2Collection existiert eine neue Methode _FindByThumbprint()_um ein digitales Zertifikat anhand des SHA-Fingerabdrucks zu finden.
  • Die Klasse ISOWeek bietet drei neue Methoden zum Umgang mit dem Datentyp System.DateOnly. Bisher war hier nur System.DateTime möglich:
DateOnly now = DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine(ISOWeek.GetWeekOfYear(now));
Console.WriteLine(ISOWeek.GetYear(now));
Console.WriteLine(ISOWeek.ToDateOnly(2025, 1, DayOfWeek.Tuesday)); <em>// 30.12.2024</em>
  • Die Implementierung der Verarbeitung von ZIP-Archiven wurde optimiert, z. B. wurde das Aktualisieren von ZIP-Archiven um 99,8 Prozent beschleunigt (ein Hinzufügen einer 2-GB-Datei von 177 ms auf 0,13 ms) und verbraucht nun 99,99 Prozent weniger RAM (nun 7,01 KB statt 2 GB RAM) [13].

Verbesserungen in Windows Forms und WPF

In Windows Forms in .NET 10.0 [14] sind nun einige Überladungen der GetData()-Methode für die Zwischenablage sowie Drag-and-Drop-Operationen als [Obsolet] markiert, die noch die Klasse BinaryFormatter verwenden, die Microsoft in .NET 9.0 ausgebaut hat und für die man nun ein eigenes NuGet-Paket braucht. Dafür gibt es nun neue Operationen, die mit JSON arbeiten, z. B. SetDataAsJson() und TryGetData().

In WPF gibt es laut Release Notes zu Preview 1 [15] und Preview 2 [16] nur Leistungsverbesserungen und Bug Fixes, u. a. am in .NET 9.0 eingeführten Fluent Design für WPF.

System.Text.Json: GetPropertyCount() in der Klasse JsonElement

In der JSON-Bibliothek System.Text.Json hat Microsoft die Klasse JsonElement um die Methode GetPropertyCount() erweitert, mit der man ermitteln kann, wie viele Eigenschaften ein JSON-Objekt besitzt:

JsonElement element1 = JsonSerializer.Deserialize<JsonElement>("""{ "ID" : 1, "Name" : "Dr. Holger Schwichtenberg", "Website": "www.IT-Visions.de" }""");
Console.WriteLine(element1.GetPropertyCount()); <em>// 3</em>

Bisher war nur möglich, die Anzahl der Objekte in einem Array zu ermitteln:

JsonElement element2 = JsonSerializer.Deserialize<JsonElement>("""[ 1, 2, 3, 4 ]""");
Console.WriteLine(element2.GetArrayLength()); <em>// 4</em>

System.Text.Json: RemoveRange() und RemoveAll() in JsonArray

In der Klasse JsonArray, im Namensraum System.Text.Json.Nodes, bietet Microsoft nun zwei Methoden RemoveRange() und RemoveAll()(Listing 6):

  • RemoveRange() nimmt zwei Zahlen entgegen: Index und Count. Der Index ist wie üblich nullbasiert, d. h., array.RemoveRange(5, 2) entfernt das sechste und siebte Element.
  • RemoveAll() erwartet ein Predicate-Objekt und erlaubt die Angabe eines Löschkriteriums, z. B. array.RemoveAll(n => n.GetValue() > 5);
System.Text.Json.Nodes.JsonArray array = JsonSerializer.Deserialize<System.Text.Json.Nodes.JsonArray>("""[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]""");
System.Console.WriteLine(array.Count); <em>// 9</em>
PrintJsonArray(array);

array.RemoveRange(5, 2);
PrintJsonArray(array);

array.RemoveAll(n => n.GetValue<int>() > 5);
PrintJsonArray(array);

System.Text.Json: Einstellungen für zirkuläre Referenzen

Die Annotation [JsonSourceGenerationOptions] für den Source Generator in der Bibliothek System.Text.Json erlaubt nun auch die Einstellung des Verhaltens bei zirkulären Referenzen auf Unspecified, _Preserve_oder IgnoreCycles, z. B.:

[JsonSourceGenerationOptions(ReferenceHandler = JsonKnownReferenceHandler.Preserve)]

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

ASP.NET Core: OpenAPI Specification (OAS) 3.1

In ASP.NET Core WebAPIs in .NET 10.0 werden Metadaten nun im Standard in der Version 3.1 – bisher Version 3.0 – der OpenAPI Specification (OAS) mit vollständiger Unterstützung des JSON-Schema-Standards 2020-12 [17] generiert. Die Umstellung von Version 3.0 auf Version 3.1 geht einher mit Breaking Changes [18].

Entwickler:innen können daher bei Bedarf im Startcode des ASP.NET-Core-Webservers die OAS-Version mit diesem Befehl zurückstufen:

builder.Services.AddOpenApi(options =>
{
  options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0;
});

Wenn OAS-Dokumente im Rahmen des Build-Prozesses gesteuert werden, geht die Zurückstufung wie folgt in der Projektdatei (.csproj):

<OpenApiGenerateDocumentsOptions>
  --openapi-version OpenApi3_0
</OpenApiGenerateDocumentsOptions>

ASP.NET Core: OpenAPI-Dokumente in YAML

Die OAS-Dokumente in ASP.NET Core waren bisher immer im JSON-Format. ASP.NET Core 10.0 bietet seit Preview 1 alternativ auch das YAML-Format an, das kürzere Dokumente liefert (Abb. 6). YAML als Alternative zu JSON stellt man im Startcode ein, indem man bei der Methode MapOpenApi() einen Dokumentnamen angibt, der auf .yml oder .yaml endet:

app.MapOpenApi("/openapi/{documentName}.yml");

oder

app.MapOpenApi("/openapi/{documentName}.yaml");

Die YAML-Metadaten können Entwickler:innen aber noch nicht im Build-Prozess einstellen. Microsoft will diese Option in einer kommenden Preview-Version liefern.

Vergleich eines OpenAPI-3.1-Dokuments im JSON-Format (links) und YAML-Format (rechts), basierend auf derselben API-Struktur

Abb. 6: Ein OpenAPI-3.1-Dokument im JSON- (links) und kompakteren YAML-Format (rechts)

ASP.NET Core: OpenAPI-Dokumente mit XML-Kommentaren

Die von dem NuGet-Paket Microsoft.AspNetCore.OpenApi generierten OpenAPI-Metadaten für ein ASP.NET Core WebAPI können nun auch Informationen aus den XML-Kommentaren enthalten, die zu Klassen bzw. Methoden hinterlegt sind. Diese Integration können Entwickler:innen mit einem Eintrag in der Projektdatei aktivieren:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Welche XML-Kommentare auf welche Weise in das OpenAPI-Dokument übernommen werden, steht leider nicht in den Release-Notes [19] und bedauerlicherweise ließ sich das auch nicht testen, da durch die Installation des .NET 10.0 Preview 2 SDK alle WebAPI-Projekte beim Start nur noch HTTP-Fehler 404 liefern, auch mit den aktuellsten Projektvorlagen neu angelegter Projekte. Das Problem trat auf zwei verschiedenen Testrechnern auf.

ASP.NET Core: Beschreibungstexte für Rückgabewerte

Bei Controller-basierten WebAPIs können Entwickler:innen nun in den Annotationen [ProducesAttribute][ProducesResponseTypeAttribute] und [ProducesDefaultResponseType] jeweils Beschreibungstexte angeben, die auch im OAS-Dokument erscheinen:

[HttpGet(Name = "GetWeatherForecast")]
[ProducesResponseType<IEnumerable<WeatherForecast>>(StatusCodes.Status200OK, Description = "The weather forecast for the next 5 days.")]
public IEnumerable<WeatherForecast> Get()
{
  ...
}

Das geht in Minimal WebAPIs schon seit Version 7.0 mit dem Methodenaufruf WithDescription().

Blazor: Anpassung der Verbindungsproblemmeldungen bei Blazor Server

Blazor Server stellt einen modalen Dialog dar, falls ein Abbruch der WebSockets-Verbindung zwischen Webbrowser und Webserver erfolgt ist und eine Wiederaufnahme versucht wird. Diese Darstellung war bisher nicht anpassbar.

In der Projektvorlage Blazor Web App findet man seit .NET 10.0 Preview 2 eine neue Razor Component im Ordner _Layout mit Namen ReconnectModal.razor(Listing 7). Diese Komponente enthält die Standarddarstellung des modalen Fensters bei Verbindungsproblemen. Damit können Entwickler:innen in .NET 10.0 also Einfluss auf die Texte und die Darstellung in drei Fällen nehmen:

  • Wiederherstellung wird versucht
  • Wiederherstellung ist fehlgeschlagen und wird erneut versucht
  • Wiederherstellung ist endgültig fehlgeschlagen
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22module%22%20src%3D%22%40Assets%5B%22Components%2FLayout%2FReconnectModal.razor.js%22%5D%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />

<dialog id="components-reconnect-modal" data-nosnippet>
  <div class="components-reconnect-container">
    <div class="components-rejoining-animation" aria-hidden="true">
      <div></div>
      <div></div>
    </div>
    <p class="components-reconnect-first-attempt-visible">
      Rejoining the server...
    </p>
    <p class="components-reconnect-repeated-attempt-visible">
      Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
    </p>
    <p class="components-reconnect-failed-visible">
      Failed to rejoin.<br />Please retry or reload the page.
    </p>
    <button id="components-reconnect-button" class="components-reconnect-failed-visible">
      Retry
    </button>
  </div>
</dialog>

Zur Razor Component ReconnectModal.razor gehören auch eine CSS-Datei ReconnectModal.razor.css und eine JavaScript-Datei ReconnectModal.razor.js. Auch diese Dateien sind anpassbar. ReconnectModal.razor wird am Ende der Datei MainLayout.razor eingebunden via und kann auch komplett ersetzt werden.

Blazor: NavigateTo() behält Scrollposition

Die Methode NavigateTo() in der Blazor-Klasse _NavigationManager_führt den Nutzer bzw. die Nutzerin nicht mehr zum Beginn der Seite zurück, falls der Entwickler bzw. die Entwicklerin nur URL-Parameter an die Seite anhängt:

nav.NavigateTo(nav.GetUriWithQueryParameter("count", currentCount));

Ein Rücksprung an den Seitenanfang findet allerdings bei Angabe von true für forceLoad weiterhin statt

nav.NavigateTo(nav.GetUriWithQueryParameter("count", currentCount), forceLoad: true);

oder bei Änderungen eines Teils des relativen Pfades:

nav.NavigateTo("/counter/" + currentCount);

Blazor: Verbesserungen beim QuickGrid

Im Tabellensteuerelement können Entwickler:innen nun eine Methode angeben, die bei jeder Zeile aufgerufen wird, um die CSS-Klasse der Zeile individuell auf Basis der Daten zu setzen (Listing 8).

<QuickGrid RowClass="ApplyRowClass" ItemsProvider="@itemsProvider" TGridItem="BO.WWWings.Flight" Virtualize="true" OverscanCount="@overscanCount">
  <PropertyColumn Property="@(p => p.FlightNo)" Title="FlugNr" Sortable="true" />
  <PropertyColumn Property="@(p => p.Departure)" Title="Abflugort" Sortable="true" />
  <PropertyColumn Property="@(p => p.Destination)" Title="Zielort" Sortable="true" />
  <PropertyColumn Property="@(p => p.FlightDate)" Title="Datum" Format="dd.MM.yyyy" Sortable="true" />
</QuickGrid>

@code {
  private string ApplyRowClass(BO.WWWings.Flight rowItem) => rowItem.FreeSeats < 10 ? "red" : null;
}

Außerdem gibt es im QuickGrid-Steuerelement nun eine neue Methode CloseColumnOptionsAsync()(Listing 9), die Entwickler:innen aufrufen können, damit sich die Eingabe von Filterkriterien schließt, wenn der Filter aktiviert wird: Der Aufruf CloseColumnOptionsAsync() sorgt dafür, dass die geöffnete Filtereingabe sich automatisch wieder schließt, wenn der Nutzer bzw. die Nutzerin ein Eingabetaste drückt (Abb. 7).

<QuickGrid @ref="flightGrid" RowClass="ApplyRowClass" ItemsProvider="@itemsProvider" TGridItem="BO.WWWings.Flight" Virtualize="true" OverscanCount="@overscanCount">
  <PropertyColumn Property="@(p => p.FlightNo)" Title="FlugNr" Sortable="true" />
  <PropertyColumn Property="@(p => p.Departure)" Title="Abflugort" Sortable="true">

    <ColumnOptions>
      <input type="search" @bind="departureFilter" placeholder="Filter by Departure"
        @bind:after="@(() => flightGrid.CloseColumnOptionsAsync())" />
    </ColumnOptions>

  </PropertyColumn>
  <PropertyColumn Property="@(p => p.Destination)" Title="Zielort" Sortable="true" />
  <PropertyColumn Property="@(p => p.FlightDate)" Title="Datum" Format="dd.MM.yyyy" Sortable="true" />
</QuickGrid>
Blazor-Komponente mit aktivem Spaltenfilter; Methode CloseColumnOptionsAsync() soll Filtereingabe schließen

Abb. 7: Geöffnete Filtereingabe soll sich durch CloseColumnOptionsAsync() automatisch schließen

Blazor: Fingerprinting für Blazors JavaScript-Datei

Die bei Blazor mitgelieferte JavaScript-Datei (je nach verwendeter Projektvorlage: blazor.web.jsblazor.server.js bzw. blazor.webassembly.js) erhält in .NET 10.0 die bereits in .NET 9.0 eingeführte Möglichkeit zur Komprimierung und einem Fingerabdruck des Inhalts im Dateinamen, der verhindert, dass alte Versionen aus dem Browsercache verwendet werden.

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

Entity Framework: Verbesserungen für ExecuteUpdate() und ExecuteUpdateAsync()

Bei den Methoden zum Massenaktualisieren, namentlich ExecuteUpdate() und ExecuteUpdateAsync(), bietet Entity Framework Core 10.0 nun eine bessere Übersichtlichkeit bei komplexeren Ausdrücken, indem nicht nur Expression-Lambdas, sondern auch Statement-Lambdas erlaubt sind. Anstelle von

var c0 = ctx.Flights.Where(f => f.FlightNo <= 100).ExecuteUpdate(s => s.SetProperty(f => f.FreeSeats, f => f.FreeSeats - 1)
  .SetProperty(f => f.Memo, f => (setMemo ? "Freie Pl&#xE4;tze ge&#xE4;ndert am " + DateTime.Now : f.Memo))
);

können Entwickler:innen nun auch schreiben:

var c1 = ctx.Flights.Where(f => f.FlightNo <= 100).ExecuteUpdate(s =>
  {
    s.SetProperty(f => f.FreeSeats, f => f.FreeSeats - 1);
    if (setMemo)
    {
      s.SetProperty(f => f.Memo, "Freie Pl&#xE4;tze ge&#xE4;ndert am " + DateTime.Now);
    }
});

Auch wenn die Release-Notes explizit nur von der asynchronen Methode _ExecuteUpdateAsync()_sprechen, funktioniert das Feature im Schnelltest auch mit der synchronen Variante ExecuteUpdate().

Entity Framework: RightJoin() und LeftJoin()

Die in .NET 10.0 neu eingeführten Operatoren LeftJoin() und RightJoin() werden auch in Entity Framework Core 10.0 bereits für Datenbankzugriffe unterstützt [20], [21]. Die folgenden Listings zeigen den Einsatz von RightJoin() und LeftJoin() bei Entity Framework Core 10.0 in Verbindung mit einer relationalen Datenbank. Auch in Entity Framework konnte man das Ergebnis von RightJoin() und LeftJoin() bisher schon via _GoupJoin()_und _SelectMany()_mit _DefaultIfEmpty()_erzielen.

Aus dem LINQ-Befehl mit LeftJoin() in Listing 10 entsteht der SQL-Befehl in Listing 11. Aus diesem LINQ-Befehl mit RightJoin() in Listing 12 entsteht der SQL-Befehl in Listing 13.

CUI.H2("Suche alle Fl&#xFC;ge, zu denen es keinen Piloten gibt via LeftJoin()");
var ctx = new DA.WWWings.WwwingsV1EnContext();
var fluegeOhnePilot = ctx.Flights
  .LeftJoin(
    ctx.Pilots,
    f => f.PilotPersonId,
    p => p.PersonId,
    (f, p) => new
    {
      f.FlightNo,
      f.Departure,
      f.Destination,
      f.FlightDate,
      PilotId = f.PilotPersonId == null ? "n/a" : f.PilotPersonId.ToString(),
      p.Employee.Person.GivenName,
      p.Employee.Person.Surname,
  }).Where(x => x.Surname == null).Take(20).ToList();

foreach (var item in fluegeOhnePilot)
{
  Console.WriteLine($"{item.FlightNo} {item.Departure}->{item.Destination} am {item.FlightDate}: Pilot {item.PilotId} {item.GivenName} {item.Surname}");
}
SELECT TOP(@p) [f].[FlightNo], [f].[Departure], [f].[Destination], [f].[FlightDate], CASE
  WHEN [f].[Pilot_PersonID] IS NULL THEN 'n/a'
  ELSE COALESCE(CONVERT(varchar(11), [f].[Pilot_PersonID]), '')
END AS [PilotId], [p0].[GivenName], [p0].[Surname]
FROM [Operation].[Flight] AS [f]
LEFT JOIN [People].[Pilot] AS [p] ON [f].[Pilot_PersonID] = [p].[PersonID]
LEFT JOIN [People].[Employee] AS [e] ON [p].[PersonID] = [e].[PersonID]
LEFT JOIN [People].[Person] AS [p0] ON [e].[PersonID] = [p0].[PersonID]
WHERE [p0].[Surname] IS NULL
var pilotenMitFlug = ctx.Flights
  .RightJoin(
    ctx.Pilots.OrderByDescending(x => x.PersonId).Take(3),
    f => f.PilotPersonId,
    p => p.PersonId,
    (f, p) => new
    {
      PilotId = p.PersonId,
      p.Employee.Person.GivenName,
      p.Employee.Person.Surname,
      Flight = f,
      f.Departure,
      f.Destination,
      <em>//Date = f.FlightDate == DateTime.MinValue ? "n/a" : f.FlightDate.ToShortDateString(), // TODO: LAUFZEITFEHLER. Pr&#xFC;fen in RTM!</em>

  }).OrderBy(x => x.PilotId).ToList();

foreach (var p in pilotenMitFlug)
{
  Console.WriteLine($"Pilot #{p.PilotId} {p.GivenName} {p.Surname} fliegt " + (p.Flight != null ? $"Flug #{p.Flight?.FlightNo} {p.Flight.Departure}->{p.Flight.Destination} am {p.Flight.FlightDate}" : "bisher keinen Flug"));
}
SELECT [p0].[PersonID] AS [PilotId], [p1].[GivenName], [p1].[Surname], [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM [Operation].[Flight] AS [f]
RIGHT JOIN (
  SELECT TOP(@p) [p].[PersonID]
  FROM [People].[Pilot] AS [p]
  ORDER BY [p].[PersonID] DESC
) AS [p0] ON [f].[Pilot_PersonID] = [p0].[PersonID]
INNER JOIN [People].[Employee] AS [e] ON [p0].[PersonID] = [e].[PersonID]
INNER JOIN [People].[Person] AS [p1] ON [e].[PersonID] = [p1].[PersonID]
ORDER BY [p0].[PersonID]

Es sei zudem explizit darauf hingewiesen, dass man die neuen Operatoren _RightJoin()_und LeftJoin() einschließlich der vorher schon vorhandenen Operatoren _Join()_und GroupJoin() nur für die Verbindung von Tabellen braucht, für die es im Objektmodell keine Navigationsbeziehung gibt. So kann man statt des aufwendigen RightJoin() bei einer vorhandenen Navigationsbeziehung im Objektmodell dasselbe Ausgabeergebnis mit einem Include() erreichen. In diesem Fall erhält man allerdings keine flache Liste mit Daten aus Pilot und Flug, sondern eine Objekthierarchie, daher zwei verschachtelte foreach-Schleifen (Listing 14). Entity Framework Core führt dabei nur Inner Joins aus (Listing 15).

SELECT [p0].[PersonID], [p0].[FlightHours], [p0].[FlightSchool], [p0].[LicenseDate], [p0].[LicenseType], [e].[PersonID], [e].[EmployeeNo], [e].[HireDate], [e].[Supervisor_PersonId], [p1].[PersonID], [p1].[Birthday], [p1].[City], [p1].[Country], [p1].[EMail], [p1].[GivenName], [p1].[Memo], [p1].[Photo], [p1].[Surname], [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM (
  SELECT TOP(@p) [p].[PersonID], [p].[FlightHours], [p].[FlightSchool], [p].[LicenseDate], [p].[LicenseType]
  FROM [People].[Pilot] AS [p]
  ORDER BY [p].[PersonID] DESC
) AS [p0]
INNER JOIN [People].[Employee] AS [e] ON [p0].[PersonID] = [e].[PersonID]
INNER JOIN [People].[Person] AS [p1] ON [e].[PersonID] = [p1].[PersonID]
LEFT JOIN [Operation].[Flight] AS [f] ON [p0].[PersonID] = [f].[Pilot_PersonID]
ORDER BY [p0].[PersonID], [e].[PersonID], [p1].[PersonID]

Entity Framework: weitere Verbesserungen für LINQ

Entity Framework Core bietet in Version 10.0 Preview 1 und 2 zwei Verbesserungen bei der Übersetzung von LINQ in SQL:

  • Die .NET-Methode DateOnly.ToDateTime(timeOnly) wird nun in SQL übersetzt.
  • Optimiert wurde die Übersetzung von Count() in ICollection.

.NET MAUI: ShadowTypeConverter

In .NET MAUI 10.0 Preview 2 können Entwickler:innen nun mit einer vereinfachten Syntax Schatten definieren:

<VerticalStackLayout BackgroundColor="#fff" Shadow="4 4 16 #000000 0.5" />

Dabei stehen die Zahlen in der Eigenschaft _Shadow_für Offset X, Offset Y, Radius, Farbe und Opazität. Zuvor musste man hier eigene Tags verwenden:

<VerticalStackLayout BackgroundColor="#fff" >
  <Shadow       Brush="#000000"
                Offset="4,4"
                Radius="16"
                Opacity="0.5" />
</VerticalStackLayout>

.NET MAUI: Steuerung von Farben

Entwickler:innen können beim Steuerelement __nun neben OnColor auch _OffColor_festlegen:

<Switch OffColor="Red"
        OnColor="Green" />

Beim Steuerelement lässt sich die Farbe des Symbols steuern:

<SearchBar Placeholder="Search items..." SearchIconColor="Blue" />

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

.NET MAUI: Steuerung der Geschwindigkeit der Sprachausgabe

Bei Sprachausgabe mit der Schnittstelle Klasse TextToSpeech kann man nun die Geschwindigkeit via Eigenschaft Rate angeben (Listing 16).

IEnumerable<Locale> locales = await TextToSpeech.Default.GetLocalesAsync();
  SpeechOptions options = new SpeechOptions()
  {
    Rate = 2.0f, <em>// 0.1 - 2.0</em>
    Pitch = 1.5f, <em>// 0.0 - 2.0</em>
    Volume = 0.75f, <em>// 0.0 - 1.0</em>
    Locale = locales.FirstOrDefault()
  };
await TextToSpeech.Default.SpeakAsync("Hallo aus .NET 10.0!", options);

.NET MAUI: Verbesserungen für Android

.NET MAUI unterstützt nun JDK-Version 21 und Android-Version 16 (Codename Baklava, API-Version 36). Die MAUI-Projektvorlage verwendet nun im Standard API-Version 24 (Nougat) statt API-Version 21 (Lollipop). Man kann aber weiterhin auf API-Version 21 zurückstufen, Microsoft empfiehlt aber in den Release-Notes [22], das nicht zu tun: „While API-21 is still supported in .NET 10, we recommend updating existing projects to API-24 in order to avoid unexpected runtime errors.”

Android-Projekte können Entwickler:innen nun mit dem Befehl dotnet run direkt von der Kommandozeile aus starten, z. B.:

<em>// Start auf Ger&#xE4;t (wenn nur ein Ger&#xE4;t angeschlossen ist)</em>
dotnet run -p:AdbTarget=-d

<em>// Start auf Emulator (wenn es nur einen gibt)</em>
dotnet run -p:AdbTarget=-e

<em>// Start auf einem bestimmten Emulator</em>
dotnet run -p:AdbTarget="-s emulator-5554"

.NET MAUI: Verbesserungen für iOS, Mac Catalyst, macOS und tvOS

Beim Kompilieren für die Apple-Plattformen (iOS, Mac Catalyst, macOS, tvOS) erzeugt der Compiler nun automatisch Trimmer-Warnungen. Diese Warnungen kann man aber ausschalten mit:

<PropertyGroup>
  <SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
</PropertyGroup>

.NET MAUI: weitere Neuerungen

Das Steuerelement gilt nun als veraltet. Entwickler:innen sollten verwenden. Ebenso ist die Mark-up-Erweiterung FontImageExtension veraltet. Man sollte jetzt das Tag __verwenden.

Breaking Changes

Wie in jeder .NET-Version der letzten Jahre üblich, wird es auch dieses Mal in .NET 10.0 wieder einige Breaking Changes gegenüber der Vorgängerversion 9.0 geben. Einige der Breaking Changes sind bereits in Preview 1 und 2 enthalten und unter [23] dokumentiert.

Dokumentation der Neuerungen

Der .NET-API-Browser mit der .NET-Klassendokumentation [24] ist bereits auf .NET 10.0 Preview 2.0 aktualisiert, sodass man dort die neuen Klassen und Methoden sieht.

Während letztes Jahr bei .NET 9.0 die Preview-Versionen nach Preview 1 nur noch auf GitHub angekündigt wurden, gibt es seit diesem Jahr wieder einen Blogeintrag zu jeder Preview-Version (Preview 1 [25], Preview 2 [26]). Die Blogbeiträge verlinken auf die Release-Notes-Dokumente auf GitHub. Zudem gibt es jeweils aber auch immer eine Ankündigung auf GitHub [27], die neben den Release-Notes auch auf „What’s New”-Dokumente in der Dokumentation verlinkt. Das ist aber nicht immer konsistent: In der Ankündigung zu Preview 1 auf GitHub [28] fehlt der Link auf die Release-Notes von Entity Framework Core [29] gänzlich.

API-Reviews und Unboxing auf YouTube

Das .NET-Entwicklungsteam überträgt seit einiger Zeit die Diskussionen bezüglich neuer APIs (API-Reviews) live im Netz auf YouTube. Nun gibt es diese API-Reviews auch öffentlich beim Entwicklungsteam von ASP.NET Core und Blazor [30]. Die Termine der API-Reviews findet man unter [31]; die Videos der Vergangenheit findet man auf dem YouTube-Kanal der .NET Foundation [32].

Zu jeder Preview gibt es jetzt auch neu einen Unboxing-Live-Stream auf YouTube. Die Aufzeichnung ist in den Blogbeiträgen verlinkt.

Weitere Pläne für .NET 10.0

Auf GitHub findet man Roadmaps für einzelne Bereiche von .NET 10.0, z. B. C# 14.0 [33] und ASP.NET Core und Blazor [34]. Demzufolge arbeitet das C#-Team an den Extension Types (der Verallgemeinerung der Extension Methods, sodass Entwickler:innen auch Eigenschaften und Konstruktoren ergänzen können), was eigentlich schon als Sprachfeature für C# 13.0 geplant war.

Die Pläne des ASP.NET-Core-Entwicklungsteams zeigen die Abbildungen 8 und 9. Ein großes Thema sind demnach die Verbesserungen der Authentifizierungsmöglichkeiten z. B. mit Passkeys und Demonstration of Proof of Possession (DPop) Authentication Tokens. Die Microsoft Identity Platform soll in die Blazor-Web-App-Projektvorlage integriert werden [35]. Minimal WebAPIs sollen auch Validierung mit Data Annotations unterstützen [36].

Der bisher schon verfügbare Persistent Component State zur Übergabe von Daten vom Prerendering bei Blazor soll nun deklarativ via Annotation [SupplyParameterFromPersistentComponentState] möglich sein [37]. Auch soll man in Blazor Server die Circuits zur späteren Wiederaufnahme persistieren können [38]. Blazor WebAssembly soll ein Performance-Profiling erhalten (Abb. 8).

Übersicht geplanter Verbesserungen für Blazor: bessere State-Persistence, schnellere Ladezeiten, erweitertes Tracing

Abb. 8: Geplante Verbesserungen für Blazor

Auflistung geplanter Verbesserungen für ASP.NET Core WebAPIs, OpenAPI-Dokumentation und den Kestrel-Webserver

Abb. 9: Geplante Verbesserungen für ASP.NET Core WebAPIs und den Kestrel-Webserver

Andere Entwicklungsteams haben ihre Roadmaps leider bisher nicht auf .NET 10.0 aktualisiert:

  • .NET MAUI [39]
  • Windows Forms [40]
  • WPF [41]

Auch bei Entity Framework Core gibt es keine Roadmap für Version 10.0. Man kann aber natürlich auf GitHub nach dem Milestone filtern [42], findet dort aber auch viele kleinere Bugfixes in der Liste und kann auf den ersten Blick keine Strategie erkennen. Bei Entity Framework Core findet man zum Beispiel geplante Verbesserungen für komplexe Typen: Diese sollen zukünftig optional sein können [43] und ein JSON-Mapping unterstützen [44]. Damit wird es dann auch möglich, Mengen komplexer Typen zu haben [45].

Ausblick

Bis zum geplanten Erscheinen von .NET 10.0 im November 2025 werden noch fünf weitere Preview-Versionen und zwei Release-Candidate-Versionen erscheinen, über die ich selbstverständlich auch berichten werde.

 

Frequently Asked Questions (FAQ)

1. Wann erschienen .NET 10.0 Preview 1 und Preview 2, und was sind die nächsten Schritte?

Preview 1 wurde am 25. Februar 2025 veröffentlicht, gefolgt von Preview 2 am 18. März 2025. .NET 10.0 soll voraussichtlich im November 2025 als LTS-Version erscheinen, mit insgesamt fünf weiteren Preview-Versionen und zwei Release Candidates.

2. Wie setzt man .NET 10.0 und neue Sprachfeatures ein?

Um neue Sprachfeatures zu verwenden, muss das Target-Framework auf net10.0 gestellt und in der Projektdatei zusätzlich <LangVersion>preview</LangVersion> gesetzt werden. Außerdem ist Visual Studio 2022 Version 17.14 Preview 2 erforderlich.

3. Welche nützlichen Sprachfeatures kommen mit C# 14.0?

  • Von nameof() mit generischen Typen können nun Typparameter weggelassen werden (z. B. nameof(List<>)).

  • Lambda-Ausdrücke erlauben nun Parameter-Modifizierer wie scoped, ref, in, out, _ref readonly, ohne explizite Typphase.

  • Neue automatische Konvertierungen zwischen Arrays, Span<T> und ReadOnlySpan<T>, z. B. Developer[] → Span<Developer>.

  • Das neue Schlüsselwort field unterstützt semi-automatische Properties („Semi-Auto Properties“).

  • Partielle Konstruktoren und Events werden als partial gekennzeichnet, sind aktuell aber noch nicht voll funktionsfähig (Debugger/Compiler-Unterstützung geplant ab VS 17.4 Preview 3).

4. Welche Verbesserungen gibt es in der Basisklassenbibliothek (BCL)?

  • SDK: Vereinheitlichung der dotnet CLI-Kommandos (z. B. dotnet package add statt dotnet add package).

  • Workload Sets sind nun Standard beim .NET SDK, mit besserem Package-Restore.

  • LINQ-Erweiterungen: Einfache Nutzung von LeftJoin() und RightJoin() statt zuvor komplexer Konstrukte.

  • OrderedDictionary<T,T> bekommt neue Overloads: TryAdd(key, value, out index) und TryGetValue(key, out value, out index).

  • System.Net: IPAddress.IsValid() ergänzt TryParse() zur leichteren IP-Prüfung.

  • Certificate APIs: ExportPkcs12() für sichereren PKCS#12-Export – noch nicht vollständig verfügbar; X509Certificate2Collection erhält FindByThumbprint().

  • ISOWeek-Klasse: Neue Methoden für DateOnly-Verarbeitung (z. B. GetWeekOfYear()).

  • ZIP-Performance optimiert: Deutlich geringerer RAM-Verbrauch (z. B. 2 GB → 7 KB) und deutlich schneller.

5. Welche neuen Features gibt es in .NET MAUI?

  • ShadowTypeConverter: Definieren von Schatten vereinfacht durch kurze Syntax (Shadow="4 4 16 #000000 0.5").

  • Farbsteuerung: Bei Switch kann nun OffColor und bei SearchBar die Symbolfarbe über SearchIconColor gesteuert werden.

  • TextToSpeech: Sprachgeschwindigkeit (Rate) jetzt einstellbar (z. B. Rate = 2.0f).

  • Android-Support: JDK 21 und neue API-Level automatisch (empfohlen ab API 24); Steuerung per dotnet run -p:AdbTarget=….

  • Apple-Plattformen (iOS, macOS, tvOS): Automatische Trimmer-Warnungen, optional abschaltbar in der Projektdatei.

6. Was ändert sich bei ASP.NET Core und OpenAPI?

  • OpenAPI Specification wird nun standardmäßig in Version 3.1 unterstützt (zuvor 3.0) – bringt Breaking Changes.

  • OpenAPI-Ausgabe kann jetzt auch im YAML-Format generiert werden.

  • XML-Kommentare lassen sich in OpenAPI-Dokumente einbinden – aktiviert über Projektdatei.

  • Response-Beschreibungen: Annotations wie [ProducesResponseType(..., Description="…")] erscheinen nun im OpenAPI-Dokument.

7. Welche Blazor-Verbesserungen gibt es in der Vorschau?

  • ReconnectModal.razor erlaubt Anpassung der UI bei Verbindungsabbrüchen (Blazor Server).

  • NavigateTo() behält nun Scrollposition – Sprung nur bei forceLoad = true oder Pfadänderung.

  • QuickGrid: Zeilenklassen per Funktion setzen; CloseColumnOptionsAsync() schließt Filtereingaben automatisiert.

  • Fingerprinting von JS-Datei verhindert Cache-Probleme.


Links & Literatur

[1] https://dotnet.microsoft.com/en-us/download/dotnet/10.0

[2] https://github.com/dotnet/csharplang/issues/7905

[3] https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14

[4] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview2/csharp.md#partial-events-and-constructors

[5] https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/partial-events-and-constructors

[6] https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md

[7] https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/10.0/default-workload-config

[8] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview1/sdk.md

[9] https://github.com/dotnet/runtime/issues/111282

[10] https://github.com/dotnet/core/blob/main/releasenotes/10.0/preview/preview2/libraries.md#encryption-algorithm-can-now-be-specified-in-pkcs12pfx-export

[11] https://github.com/dotnet/runtime/issues/80314

[12] https://github.com/dotnet/runtime/releases/tag/v10.0.0-preview.2.25163.2

[13] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview1/libraries.md#ziparchive-performance-and-memory-improvements

[14] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview1/winforms.md

[15] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview1/wpf.md

[16] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview2/wpf.md

[17] https://json-schema.org/specification-links#2020-12

[18] https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-9.0#openapi-31-breaking-changes

[19] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview2/aspnetcore.md#populate-xml-doc-comments-into-openapi-document

[20] https://github.com/dotnet/efcore/issues/12793

[21] https://github.com/dotnet/efcore/issues/35367

[22] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview1/dotnetmaui.md

[23] https://learn.microsoft.com/en-us/dotnet/core/compatibility/10.0

[24] https://learn.microsoft.com/en-us/dotnet/api/

[25] https://devblogs.microsoft.com/dotnet/dotnet-10-preview-1

[26] https://devblogs.microsoft.com/dotnet/dotnet-10-preview-2

[27] https://github.com/dotnet/announcements/issues

[28] https://github.com/dotnet/announcements/issues/344

[29] https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview1/efcore.md

[30] https://github.com/dotnet/aspnetcore/issues/60003

[31] https://apireview.net

[32] https://www.youtube.com/@NETFoundation/streams

[33] https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md

[34] https://github.com/dotnet/aspnetcore/issues/59443

[35] https://github.com/dotnet/aspnetcore/issues/51202

[36] https://github.com/dotnet/aspnetcore/issues/46349

[37] https://github.com/dotnet/aspnetcore/issues/26794

[38] https://github.com/dotnet/aspnetcore/issues/30344

[39] https://github.com/dotnet/maui/wiki/Roadmap

[40] https://github.com/dotnet/winforms/blob/main/docs/roadmap.md

[41] https://github.com/dotnet/wpf/blob/main/roadmap.md

[42] https://github.com/dotnet/efcore/milestone/204

[43] https://github.com/dotnet/efcore/issues/31376

[44] https://github.com/dotnet/efcore/issues/31252

[45] https://github.com/dotnet/efcore/issues/31237

The post Was bringt .NET 10.0? Erste Einblicke in Preview 1 & 2 appeared first on BASTA!.

]]>
KI-Agenten mit Semantic Kernel https://basta.net/blog/ki-agenten-semantic-kernel-dotnet/ Thu, 24 Apr 2025 09:52:25 +0000 https://basta.net/?p=107382 Du möchtest erfahren, wie du mit Semantic Kernel deine eigenen KI-Agenten entwickeln kannst? Schritt für Schritt bauen wir ein intelligentes System, das mit Large Language Models (LLMs) interagiert und sich mit selbsterarbeiteten Lösungen flexibel an Problemstellungen heranwagt.

The post KI-Agenten mit Semantic Kernel appeared first on BASTA!.

]]>
Stell dir vor, du hast ein intelligentes System, das eigenständig Probleme analysiert, Entscheidungen trifft und sich kontinuierlich verbessert – genau das ermöglichen KI-Agenten. Diese nächste Generation der Automatisierung geht weit über klassische Workflows hinaus und eröffnet völlig neue Möglichkeiten für IT-Operations, Supportsysteme und viele weitere Anwendungsbereiche. Im Gegensatz zu traditionellen Systemen mit festen Regeln passen sich moderne KI-Agenten dynamisch an, lernen aus Erfahrungen und treffen eigenständig Entscheidungen. Diese Systeme lösen komplexe Aufgaben und lernen aus Interaktionen.

In diesem Artikel werden wir uns zunächst mit den theoretischen Grundlagen von KI-Agenten und agentenbasierten Systemen befassen. Anschließend folgt eine praxisnahe Einführung in die Entwicklung eigener KI-Agenten mit dem Semantic Kernel. Ziel ist es, ein tiefgehendes Verständnis für die Konzepte zu vermitteln und gleichzeitig eine praktische Anleitung bereitzustellen, mit der Entwickler:innen eigene Projekte umsetzen können.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Zeitachse und Entwicklung

Frühe 2000er: Traditionelle Workflow-Engines

In den frühen 2000ern wurde die Automatisierung weitgehend von Workflow-Engines wie Nintex, Windows Workflow Foundation, IBM WebSphere Process Server und BizTalk Server dominiert. Diese Tools ermöglichten es Unternehmen erstmals, Geschäftsprozesse ohne tiefgehende Programmierung zu automatisieren. Bekannte Merkmale waren:

  • Workflow-Engines zeichneten sich durch starre, vordefinierte Logiken und Entscheidungsbäume aus.
  • Entwickler:innen implementierten Automatisierungen mithilfe von if-then-else Bedingungen und regelbasierten Konfigurationen.
  • Der Fokus lag auf wiederholbaren, strukturierten Aufgaben mit klar definierten Ergebnissen.
  • Systeme waren wenig anpassungsfähig und erforderten umfangreiche Neukonfigurationen bei Prozessänderungen.

Diese Tools steigerten zwar die Effizienz in strukturierten Umgebungen, waren jedoch begrenzt in der Verarbeitung unstrukturierter Daten und der Anpassung an dynamische Geschäftsanforderungen.

Mitte der 2010er: Aufstieg von KI und maschinellem Lernen

Während regelbasierte Systeme in den frühen 2000ern dominierten, begann in den 2010ern eine neue Ära mit datengetriebenen Ansätzen. Mit dem zusätzlichen Aufkommen von maschinellem Lernen verlagerte sich der Fokus zu mehr automatisierter Entscheidungsfindung. Dies führte zu flexibleren und intelligenteren Automatisierungslösungen, die auf Mustern und Vorhersagen basierten. Wichtige Entwicklungen waren:

  • _Einführung von prädiktiven Analysen:_Systeme begannen, Datenmuster zu nutzen, um Vorhersagen zu treffen und manuelle Eingriffe zu reduzieren (SAS Advanced Analytics, IBM SPSS Modeler, RapidMiner).
  • _Übergang von regelbasierten zu lernbasierten Modellen:_KI-Anwendungen analysierten historische Daten und passten sich verändernden Bedingungen an (TensorFlow (2015), Microsoft Azure Machine Learning (2015), H2O.ai).
  • _Frühe Anwendungen:_Betrugserkennung, Empfehlungssysteme und Prozessoptimierung (PayPal Fraud Detection, Amazon Recommendation Engine, Google RankBrain (2015)).
  • Diese Veränderungen legten die Grundlage für eine intelligentere Automatisierung, jedoch wurde KI noch weitgehend als Ergänzung zu regelbasierten Workflows und nicht als eigenständige Entscheidungsinstanz genutzt (UiPath, Automation Anywhere, Blue Prism).

Frühe 2020er: Aufkommen von Generativer KI

Ende der 2010er Jahre erlebte die Welt den Aufstieg von Generativer KI, wie z. B. GPT, das bemerkenswerte Fähigkeiten im Verstehen und Generieren natürlicher Sprache zeigte. Mit der Fähigkeit, menschenähnliche Texte zu generieren und kontextbezogene Antworten zu liefern, wurde KI nicht mehr nur als unterstützendes Werkzeug, sondern als eigenständige Entscheidungsinstanz betrachtet. Hauptmerkmale sind:

  • KI-Systeme wurden kontextbewusster und konnten menschenähnliche Texte verstehen und generieren.
  • Generative KI weckte das Interesse an intelligenter Automatisierung, die über traditionelle Workflows hinausging.
  • Anwendungen dehnten sich auf Chatbots, virtuelle Assistenten und KI-gestützte Inhaltserstellung aus.

Mit diesen Fortschritten wurde der Grundstein für KI-Agenten gelegt – autonome Systeme, die nicht nur Entscheidungen treffen, sondern auch aus Interaktionen lernen und sich kontinuierlich verbessern.

Gegenwart: KI-Agenten und agentenbasierte Systeme

Mit den kontinuierlichen Fortschritten in der KI stehen wir nun an der Schwelle zu Agenten-basierter Technologie – autonomen Entitäten, die ihre Umgebung wahrnehmen, Entscheidungen treffen und handeln, um bestimmte Ziele zu erreichen.

Beschreibung von KI-Agenten

Im Gegensatz zu herkömmlichen Workflows sind KI-Agenten in der Lage, unstrukturierte Daten zu verarbeiten und komplexe Entscheidungen zu treffen. Diese Agenten lernen aus Interaktionen und können, wenn implementiert, ihre Leistung kontinuierlich verbessern. KI-Agenten sind zielorientiert und passen ihr Verhalten dynamisch an neue Gegebenheiten an. Zur Optimierung ihrer Entscheidungsfähigkeit nutzen sie fortschrittliche Techniken wie Reinforcement Learning, selbstüberwachtes Lernen und tiefe neuronale Netze.

AI-Ops-Agenten: Eine Gruppe von KI-Agenten, die Probleme am lokalen System angehen

Abb. 1: AI-Ops-Agenten: Eine Gruppe von KI-Agenten, die Probleme am lokalen System angehen

KI-Agenten stellen einen Paradigmenwechsel dar – von statischer Automatisierung hin zu adaptiven, sich selbst verbessernden Systemen, die in dynamischen, realen Umgebungen funktionieren können (Abb. 1).

Agentenbasierte Systeme (oft auch agentische Systeme genannt) führen den Ansatz von KI-Agenten fort. Unter diesem Begriff versteht man das Skalieren der Fähigkeiten von KI-Agenten. Während KI-Agenten oftmals als eine Gruppe von Agenten bezeichnet werden, die ein bestimmtes Thema verarbeiten, übernehmen agentenbasierte Systeme die Orchestrierung über mehrere Gruppen von KI-Agenten, wodurch eine hohe Skalierbarkeit erreicht wird (Abb. 2).

Ein Beispiel: Während eine Gruppe von KI-Agenten nur in der Lage ist, auf die Frage „Warum ist mein Netzwerk so langsam?” zu antworten, nutzt ein agentisches System die Fähigkeiten ganzer Gruppen von KI-Agenten (Netzwerk, Application Monitoring, TicketSysteme, usw.), um ein komplexeres Problem zu lösen.

Skalierbares Agentic-System

Abb. 2: Skalierbares Agentic-System

Grundlegende KI-Technologien für KI-Agenten

KI-Agenten verwenden Schlüsseltechnologien der Künstlichen Intelligenz zur Analyse von Daten, zum Verstehen von Sprache und zur Verarbeitung visueller Informationen. Machine Learning, eine Unterkategorie der Künstlichen Intelligenz, ermöglicht es KI-Agenten, Datenmuster zu erkennen und Vorhersagen oder Entscheidungen zu treffen, basierend auf gesammelten Erfahrungen. Dies führt zu folgenden Spezialisierungen:

  • Neuronale Netze und Deep Learning: Von Gehirnstrukturen inspirierte neuronale Netze helfen KI-Agenten, komplexe Daten zu verstehen. Deep Learning nutzt mehrschichtige Netze für Aufgaben wie Bild- und Spracherkennung.
  • _Natürliche Sprachverarbeitung (NLP):_NLP ermöglicht die Verständigung in menschlicher Sprache – wichtig für Chatbots, virtuelle Assistenten und automatisierte Systeme.
  • _Computer Vision:_KI-Agenten interpretieren visuelle Daten und treffen Entscheidungen, hilfreich in Robotik, Qualitätskontrolle und Gesichtserkennung.
  • _Reinforcement Learning:_Durch Belohnungssysteme lernen KI-Agenten optimale Entscheidungen zu treffen, besonders in dynamischen Umgebungen.
  • _Transformermodelle und LLMs:_Transformermodelle, eine Architektur für neuronale Netze, ermöglichen eine effiziente Verarbeitung und Generierung von Texten. Sie bilden die Grundlage für LLMs, die anhand großer Mengen an Textdaten trainiert werden und fortschrittliche Sprachverarbeitung, Textverständnis und Generierung ermöglichen.

Von der Theorie zur Praxis: Eigenen KI-Agent erstellen

In diesem Abschnitt entwickeln wir einen eigenen KI-Agenten mit Semantic Kernel. Wir gehen dabei schrittweise vor und beginnen mit dem Erstellen eines Projektes.

Warum Semantic Kernel?

Es gibt mehrere Frameworks zur Entwicklung von KI-Agenten, darunter LangChain/-Graph, AutoGen/2 und Semantic Kernel (Kasten: „Hinweise zu Semantic Kernel”). Wir verwenden Semantic Kernel, da es speziell für .NET optimiert ist und eine einfache Integration mit Large Language Models (LLMs) ermöglicht. Zudem wird es aktiv von der Community und Microsoft weiterentwickelt, was eine langfristige Unterstützung und Verbesserungen sichert.

Hinweise zu Semantic Kernel

  • Preview Status: Semantic Kernel ist noch in der Preview-Phase, wird aber aktiv weiterentwickelt.
  • unterstützte Sprachen: Neben C# unterstützt Semantic Kernel auch Python und Java, was Entwicklern Flexibilität bietet.
  • Fokus auf Sprachmodelle: Semantic Kernel eignet sich besonders für KI-Agenten, die auf LLMs basieren.

In den nächsten Schritten werden wir:

  1. Ein .NET-Projekt erstellen
  2. Semantic Kernel installieren
  3. Einen einfachen KI-Agenten bauen
  4. Ein Agenten-Team aufsetzen

Die Voraussetzungen dafür sind:

  • _Technische Kenntnisse:_Erfahrung mit C#, .NET und Terminal-Befehlen
  • Zugang zu einem LLM: Azure OpenAI, OpenAI API oder lokal gehostete Modelle mit Ollama oder LMStudio

Kurze Einführung in den Semantic Kernel

Semantic Kernel ist ein Framework, das als Middleware zwischen Anwendungen und LLMs fungiert. Es bietet eine Abstraktionsschicht, die es Entwickler:innen ermöglicht, KI-Funktionen einfach in ihre Software zu integrieren. Durch den modularen Aufbau können verschiedene KI-Modelle und Plug-ins flexibel kombiniert werden.

Wichtige Merkmale sind:

  • Einfache Integration: Lässt sich nahtlos in bestehende .NET-Projekte einbinden.
  • Modularität: Flexible Anpassung von KI-Komponenten an spezifische Anforderungen.
  • Skalierbarkeit: Unterstützt den Einsatz in großen Umgebungen und verteilten Systemen.

Durch den Einsatz von Semantic Kernel können Entwickler:innen leistungsfähige KI-Agenten erstellen und ihre Anwendungen mit intelligenten Funktionen erweitern.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Schritt-für-Schritt-Anleitung: Integration des Semantic Kernel mit dem Agent-Framework

Wir beginnen mit der Einrichtung eines neuen Projekts, fügen die notwendigen Abhängigkeiten hinzu und konfigurieren den Zugriff auf ein LLM. Am Ende dieses Abschnitts hast du eine funktionierende Basis für deinen KI-Agenten.

Ein nützliches Beispiel: „AI Ops”

Ich plane ein Analyse-System für AI Ops, das generative KI zur automatisierten Lösung von IT-Tickets nutzt. Das Ziel ist es, dass KI-Agenten Problembeschreibungen analysieren und lösen. Diese Agenten benötigen Zugriff auf Netzwerk, CPU und Speicher. Es wird einen Analyse-Agent und einen Bewertungs-Agent im Team geben. Das System soll eigenständig Probleme und Lösungen auf Expertenniveau finden können (Abb. 1).

Projekt erstellen und Semantic Kernel installieren

Der erste Schritt ist das Anlegen eines .NET-Projektes nach diesem Schema:

dotnet new console -o MyAIAgenten
cd MyAIAgenten

Für die Installation und Arbeit mit dem Semantic-Kernel-Framework benötigst du folgende NuGet-Pakete:

dotnet add package microsoft.SemanticKernel
dotnet add package microsoft.semantickernel.Agenten.abstractions --version 1.35.0-alpha
dotnet add package microsoft.semantickernel.Agenten.core --version 1.35.0-alpha
dotnet add package microsoft.semantickernel.Agenten.openai --version 1.35.0-alpha
Einige dieser Pakete sind noch in der Preview-Phase. Die Versionsnummern können sich ändern. Falls du eine neuere Version verwendest, überprüfe die offizielle Semantic-Kernel-Dokumentation.

Zugang zu einem LLM

Um einen KI-Agenten zu erstellen, benötigst du Zugriff auf ein LLM. Es gibt viele Möglichkeiten – beispielsweise eine der folgenden drei:

  1. Azure OpenAI: Erstelle einen Azure OpenAI-Service im Azure-Portal und notiere den API-Schlüssel.
  2. OpenAI API: Registriere dich auf der OpenAI-Webseite und generiere einen API-Schlüssel.
  3. Lokale Modelle: Nutze Ollama oder LMStudio für ein lokal gehostetes Modell.

Falls du LMStudio verwendest, beachte, dass Function Calling derzeit nicht unterstützt wird. Ich empfehle daher Ollama als Model-Host für Semantic Kernel.

Ich nutze für dieses Projekt den Azure OpenAI-Service, weshalb ich die nachfolgenden Schritte entsprechend darauf abgestimmt habe. Die API von OpenAI bedarf nur geringfügige Änderungen im Code, da beide Vorgehensweisen in Semantic Kernel nahezu identisch implementiert sind.

Umgebungsvariablen setzen

Um Informationen wie API-Schlüssel als Konfiguration zu hinterlegen, speichere ich diese in Umgebungsvariablen. So lässt sich das Projekt auch leicht in anderen Umgebungen wie Docker verschieben.

Nun ist es an der Zeit eine Datei namens .env im Projektverzeichnis zu erstellen und folgende Zeilen hinzuzufügen:

AZURE_OPENAI_API_KEY=<dein_api_schluessel>
AZURE_OPENAI_ENDPOINT=<dein_api_endpunkt>
AZURE_OPENAI_MODEL_ID=<dein_deployment_model>

Das Deployment-Modell ist der Name des Modells, das du in Azure OpenAI bereitgestellt hast.

Installiere als Nächstes das DotNetEnv-Paket, um die Umgebungsvariablen in deinem Code zu laden: dotnet add package DotNetEnv. Falls noch nicht geschehen, erstelle eine .gitignore und füge .env hinzu, um nicht versehentlich sensiblen Inhalt in deinem Git Repo zu speichern.

Nachdem wir Semantic Kernel in unser .NET-Projekt integriert haben, erstellen wir nun einen einfachen KI-Agenten, der mit einem LLM interagiert. Mit diesem Wissen können wir nun beginnen den Code zu schreiben.

Semantic Kernel initialisieren

Um Semantic Kernel einzurichten und mit einem LLM zu verbinden, passen wir die Program.cs an wie in Listing 1.

Listing 1: Program.cs

class Program
{
static async Task Main(string[] args)
{
<em>//Alternative zu appsettings.json..:</em>
DotNetEnv.Env.Load(); <em>// lade die Env Vars</em>
<em>// Kernel initialisieren</em>
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
Environment.GetEnvironmentVariable("AZURE_OPENAI_MODEL_ID"),
Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"),
Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"));
var kernel = builder.Build();

<em>// Klon anlegen, um eine Netzwerk-F&#xE4;higkeit bereitzustellen</em>
var networkKernel = kernel.Clone();
var pluginNetwork = KernelPluginFactory
.CreateFromType<NetworkMonitor>();
networkKernel.Plugins.Add(pluginNetwork);

<em>// zweiter Kernel mit Host-Monitoring-F&#xE4;higkeit</em>
var hostMetricsKernel = kernel.Clone();
var pluginHost = KernelPluginFactory
.CreateFromType<HostMetrics>();
hostMetricsKernel.Plugins.Add(pluginHost);
<em>//&#x2026; weiterer Code &#x2013; ein</em> <em>wenig logging und weiteres house keeping</em>
}
}
Die Methode DotNetEnv.Env.Load() lädt die Umgebungsvariablen aus der .env-Datei und stellt sie im Programm zur Verfügung. Dies ist besonders nützlich, um API-Schlüssel und andere Konfigurationswerte zu verwalten, ohne sie direkt im Code zu hinterlegen.

Zuerst wird der KernelBuilder erstellt. Mit AddAzureOpenAIChatCompletion bekommt der Kernel die Fähigkeit, mit entsprechendem LLM zu interagieren. Für diejenigen, die sich für eine Implementierung mit OpenAIs API entschieden haben, würde die Funktion dann builder.AddOpenAIChatCompletion heißen.

Wir klonen den Kernel, um spezialisierte Funktionen durch Plugins hinzuzufügen – zum Beispiel Netzwerkchecks oder Host-Metriken. Das Klonen ermöglicht eine Trennung der Verantwortlichkeiten. Es verhindert, dass Agenten falsche Funktionen aufrufen. Ein Agent kann nur die im Kernel vorhandenen Plug-ins verwenden, um seine vorgesehenen Aufgaben zu erfüllen.

In unserem Beispiel haben wir einen Agenten, der Netzwerkchecks durchführen kann.

KernelPluginFactory.CreateFromType<NetworkMonitor>();
networkKernel.Plugins.Add(pluginNetwork);

Um das Plug-in besser verstehen zu können, möchte ich nachfolgend einen Auszug aus der Plug-in-Klasse zeigen.

Listing 2: Klasse NetworkMonitor

[KernelFunction()]
[Description("Returns information about the network adapters")]
public static List<string> adapterInfos()
{
List<string> adapterInfos = new List<string>();
foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
{
string info = $"Name: {ni.Name}, Typ: {ni.NetworkInterfaceType}, Status: {ni.OperationalStatus}";
adapterInfos.Add(info);
}
return adapterInfos;
}

Mit Semantic Kernel können wir Methoden mit speziellen Decorators versehen. KernelFunction stellt die Verbindung zwischen Semantic Kernel und der Methode her. Description hilft dem LLM, die Methode inhaltlich zu verstehen (Listing 2). Dies ermöglicht es dem LLM, gezielt Funktionen aufzurufen, ohne direkten Zugriff auf das System zu haben. Parameter können ebenfalls übergeben werden und sollten unbedingt in der Description mit beschrieben sein.

Das Konzept ist simpel: Bei der Lösungssuche, etwa bei der Überprüfung von Netzwerkadaptern, generiert das LLM einen Function Call auf  adapterInfos()’. Diese Funktionsaufrufe sind entscheidend für die Verbindung zwischen LLM und lokalen Ressourcen (eine tiefere Diskussion darüber würde jedoch den Rahmen sprengen).

Kurz gesagt, durch Decorators im Semantic Kernel erstellt das LLM ein JSON, füllt es mit Werten und gibt es an den Semantic Kernel zurück. Das LLM hat dabei keinen direkten Zugriff auf das System.

Unser erster Agent

Nun definieren wir einen ersten KI-Agenten, der als Netzwerkanalyst fungiert (Listing 3).

Listing 3

ChatCompletionAgent agentNetworkChecker = new()
{
Name = nameNetworkChecker,
Instructions =
"""
**Role:** You are a Network Checker with over 10 years of experience. You specialize in diagnosing network connectivity issues.

<em><&#x2026; (gek&#xFC;rzt) Der Prompt definiert die Rolle des Agenten und enth&#xE4;lt Anweisungen zur Analyse von Netzwerkproblemen></em>
""",
Kernel = networkKernel,
Arguments = new KernelArguments(new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
LoggerFactory = loggerFactory
};

Ein Agent benötigt mindestens Name, Instructions (Promp – textuelle Beschreibung der Aufgabe) und Kernel. Die Arguments-Konfiguration erlaubt es dem LLM, selbst zu entscheiden, ob es eine Plug-in-Funktion aufruft oder eine direkte Antwort generiert. Dieser Agent hier bekommt mit dem networkKernel Zugriff auf verschiedene netzwerknahe Implementierungen, also Plug-ins.

Der KI-Agent ist fertiggestellt. Jetzt fehlt nur noch der Aufruf des Agenten, um eine erste Interaktion zu starten (Listing 4).

Listing 4

<em>// erstelle ein ChatHistory object, um den Chatverlauf zu verwalten.</em>
ChatHistory chat = [];

<em>// F&#xFC;ge die User-Message hinzu</em>
chat.Add(new ChatMessageContent(AuthorRole.User, "<user input>"));

<em>// Generiere die Agent-Antwort(en)</em>
await foreach (ChatMessageContent response in agentNetworkChecker.InvokeAsync(chat))
{
<em>  //Verarbeite die Antwort(en)...</em>
}

Ein Agentensystem

Während ein einzelner KI-Agent bereits nützlich ist, entfaltet sich das volle Potenzial erst in einer Gruppe von Agenten. Hier arbeiten mehrere spezialisierte Agenten zusammen, um komplexe Aufgaben zu bewältigen. Für unser Analyseteam werden zusätzlich zum Network-Agent noch weitere Agenten benötigt, die ich hier nicht mehr detailliert aufführe. Das Grundprinzip bleibt jedoch gleich: Jeder Agent benötigt einen Namen, einen spezifischen Prompt und einen Kernel, der optional mit Plug-ins erweitert werden kann:

ChatCompletionAgent agentNetworkChecker = &#x2026; ; <em>// pr&#xFC;ft das Netzwerk</em>
ChatCompletionAgent agentCommonChecker = &#x2026; ; <em>// pr&#xFC;ft CPU, Storage, &#x2026;</em>
ChatCompletionAgent agentAnalyst = &#x2026; ; <em>// Analysiert die Ergebnisse der Agenten</em>
ChatCompletionAgent agentResolver = &#x2026; ; <em>// bewertet das Analyseergebnis</em>
Damit unsere Agenten effizient zusammenarbeiten, definieren wir eine AgentGroupChat-Struktur. Diese steuert, welcher Agent als nächstes spricht und wann die Analyse endet. Diese Steuerung erfolgt über die SelectionStrategy und TerminationStrategy, die in Listing 5 zu sehen ist (für Fortgeschrittene gibt es noch viele weitere Möglichkeiten in die Orchestrierung einzugreifen).

Listing 5: GroupChat-Definition

AgentGroupChat groupChat =
new(agentAnalyst, agentNetworkChecker, agentResolver, agentCommonChecker)
{
ExecutionSettings = new()
{
SelectionStrategy = new KernelFunctionSelectionStrategy(selectionFct, kernel)
{
InitialAgent = agentAnalyst,
HistoryReducer = new ChatHistoryTruncationReducer(1),
HistoryVariableName = "lastmessage",
<em>//return the response from the current agent/or its name</em>
ResultParser = (result) =>{
var selection = result.GetValue<string>() ?? agentAnalyst.Name;
return selection;
}
},
TerminationStrategy = new KernelFunctionTerminationStrategy( terminationFct, kernel)
{
Agenten = new[] { agentResolver },
HistoryVariableName = "lastmessage",
HistoryReducer = new ChatHistoryTruncationReducer(1),<em>//last message</em>
MaximumIterations = 10, <em>// </em><em>maximum 10 rounds</em>
ResultParser = (recall) => <em>{//check agent response for presence of word &#x201C;YES&#x201D; </em>
var result =recall.GetValue<string>();
var yes = result?.ToLower().Contains("yes");
return yes ?? false;
}
}
}
};
Zusammenfassend lässt sich sagen: Wir übergeben alle Agenten an die AgentGroupChat-Instanz und definieren Regeln für die Agentensteuerung. Die SelectionStrategy sorgt für eine sinnvolle Gesprächsführung, während die TerminationStrategy sicherstellt, dass der Chat nicht endlos läuft.

Die SelectionStrategy legt fest, welcher Agent als nächstes antwortet. Dazu definieren wir Regeln, die sich an der letzten Nachricht orientieren (Listing 6).

Listing 6

KernelFunction selectionFct = AgentGroupChat.CreatePromptFunctionForStrategy(
$$$"""
**Task:** Determine which agent should act next based on the last response. Respond with **only** the agent's name from the list below. Do not add any explanations or extra text.

**Agenten:**

- {{{nameAnalyst}}}
- {{{nameNetworkChecker}}}
- {{{nameCommonChecker}}}
- {{{nameResolver}}}

**Selection Rules:**

- **If the last response is from the user:** Choose **{{{nameAnalyst}}} **.
- **If the last response is from {{{nameAnalyst}}}  and they are requesting network details:** Choose **{{{nameNetworkChecker}}}**.
- **If the last response is from {{{nameAnalyst}}}  and they are requesting non-network system checks:** Choose **{{{nameCommonChecker}}}**.
- **If the last response is from {{{nameAnalyst}}}  and they have provided an analysis report:** Choose **{{{nameResolver}}}**.
- **If the last response is from {{{nameNetworkChecker}}} or {{{nameCommonChecker}}}:** Choose **{{{{nameAnalyst}}} }**.
- **Never select the same agent who provided the last response.**

**Last Response:**

{{$lastmessage}}
""",
safeParameterNames: "lastmessage"
);
Das Template definiert klare Regeln für die Agentenauswahl. Beispielsweise antwortet der NetworkChecker, wenn der Analyst Netzwerkdetails anfordert.

Die geschweiften Klammern {{{nameAnalyst}}} sind generell Platzhalter, die zur Laufzeit mit den tatsächlichen Agentennamen ersetzt werden. Das Muster {{{$}}} ist ein Platzhalter für Variableninhalte, die zur Laufzeit während des Chats eingefügt werden.

Hier ist es die Variable „ lastmessage“, die zuvor über das Feld HistoryVariableName der SelectionStrategy bekannt gemacht wurde. Beim Verwenden des Templates wird dort die ChatHistory vom Framework eingesetzt.

Die Zeile HistoryReducer = new ChatHistoryTruncationReducer(1) sorgt im Übrigen dafür, dass nur die letzte ChatMessage aus der Historie übergeben wird, da für die Selektion des nächsten Agenten ältere Nachrichten keine Relevanz haben. Zugleich spart dies Kosten, denn es reduziert die Anzahl der verwendeten Token, die derzeit die Währung der LLMs sind und mit der Länge des Texts in ihrer Menge steigen.

Die TerminationStrategy wird ähnlich wie die SelectionStrategy verwaltet. Sie legt fest, wann der Chat endet. Dazu wird das LLM angewiesen, eine Abschlussantwort zu geben, etwa „YES” oder „APPROVED”. Ein JSON-Schema erfasst diese Antwort strukturiert für die Automatisierungsprozesse. Zudem gibt es ein Fallback: Falls der Analyst ständig neue Details vom NetworkChecker anfordert und keine Entscheidung trifft, wird die Anzahl der Runden auf zehn begrenzt.

Unsere KI-Agenten sind nun vollständig – das Team kann Nachrichten verarbeiten, Entscheidungen treffen und den Chat sinnvoll beenden. Um unser Agenten-Team zu nutzen, fügen wir eine Nachricht hinzu und starten die asynchrone Verarbeitung. Die Antworten der Agenten werden dann in einer Schleife ausgegeben (Listing 7).

Listing 7

groupChat.AddChatMessage(new ChatMessageContent(AuthorRole.User, <User Input Nachricht>));
await foreach (ChatMessageContent response in groupChat.InvokeAsync())
{
Console.WriteLine($"{response.AuthorName.ToUpperInvariant()}:\n{response.Content}");
}

Mit diesem Konstrukt können wir diese KI-Agenten nun überall einbetten. Zum Beispiel in einem Support-Chatbot, einem automatisierten Incident-Management-System oder einer internen Analyse-Pipeline. In meinem GitHub-Projekt [1] findest du eine vollständige Implementierung mit einer interaktiven Schleife, in der Nutzer:innen direkt mit den Agenten kommunizieren können.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Fazit

KI-Agenten sind mehr als nur eine technologische Innovation – sie verändern die Art und Weise, wie wir Automatisierung und Entscheidungsfindung betrachten. Mit Semantic Kernel steht ein leistungsfähiges Werkzeug bereit, das es Entwickler:innen ermöglicht, intelligente Systeme zu bauen, die autonom zu einem gegebenen Problem nach einer Lösung suchen können.

Die Entwicklung in diesem Bereich schreitet rasant voran. Neue Modelle, verbesserte Orchestrierungsmechanismen und optimierte Agenten-Interaktionen werden in den kommenden Jahren die Möglichkeiten weiter ausbauen.

Links & Literatur

[1] https://github.com/totosan/tutorial-aiagents

The post KI-Agenten mit Semantic Kernel appeared first on BASTA!.

]]>
.NET MAUI 9: Entdecken Sie die neuesten Funktionen und Verbesserungen https://basta.net/blog/dotnet-maui-9-funktionen-verbesserungen-2/ Mon, 17 Feb 2025 07:23:30 +0000 https://basta.net/?p=107126 .NET MAUI 9 fokussiert sich auf Stabilität und erleichtert die Migration von Xamarin.Forms. Neue Projektvorlagen und das HybridWebView bieten eine solide Grundlage. Dank Syncfusion erhalten Entwickler:innen über zehn neue Open-Source-Steuerelemente. Entdecken Sie die Möglichkeiten in diesem Artikel.

The post .NET MAUI 9: Entdecken Sie die neuesten Funktionen und Verbesserungen appeared first on BASTA!.

]]>
Mit der Veröffentlichung von .NET MAUI 9 setzt Microsoft die Reise fort, die plattformübergreifende App-Entwicklung auf eine stabile Basis zu stellen. Was im Mai 2022 mit .NET MAUI 6 als ambitioniertem Nachfolger von Xamarin.Forms begann, wurde zunächst von Kinderkrankheiten überschattet: Stabilitätsprobleme und fehlende Features sorgten für Frust in der Entwicklercommunity. Mit den Versionen 7 und vor allem 8 verbesserte sich die Situation deutlich, da Microsoft auf neue Features weitgehend verzichtete und stattdessen konsequent auf Stabilisierung und Fehlerbehebung setzte.

Dieser Kurs wird in Version 9 konsequent fortgesetzt. Unternehmen, die neue plattformübergreifende Apps entwickeln oder von Xamarin.Forms auf .NET MAUI umsteigen, profitieren gleichermaßen von einer stabilen Plattform, die sich auf Verlässlichkeit konzentriert. Gleichzeitig liefert .NET MAUI 9 sinnvolle Ergänzungen, die die Arbeit mit der Plattform vielseitiger und produktiver machen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Quality first

Entwickler:innen lieben neue Features, doch genau diese lieferte Microsoft mit der neuen Version von .NET MAUI nur in sehr begrenztem Umfang. Stattdessen konzentrierte sich das Team fast ausschließlich auf die Fehlerbehebung. Dieser Fokus war essenziell, nicht nur, um den Anforderungen moderner Anwendungen gerecht zu werden, sondern vor allem, weil seit dem Supportende von Xamarin.Forms am 1. Mai 2024 immer mehr Unternehmen gezwungen sind, von Xamarin.Forms auf .NET MAUI umzusteigen. In dieser Situation sind Stabilität und Zuverlässigkeit entscheidend – Showstopper dürfen schlichtweg nicht auftreten.

Das Ergebnis dieser Strategie kann sich sehen lassen: Zwischen den Versionen 7 und 8 wurden insgesamt 689 Issues in 1 618 Pull Requests geschlossen. Von Version 8 auf 9 stiegen diese Zahlen auf über 2 800 geschlossene Issues und mehr als 1 900 Pull Requests. Die tatkräftige Unterstützung aus der Community hat diese beeindruckende Steigerung ermöglicht.. Nachdem zuvor Einzelpersonen Bugfixes zu Microsofts plattformübergreifendem Framework beigetragen hatten, beteiligte sich der Komponentenhersteller Syncfusion im Sommer dieses Jahres in großem Umfang am MAUI-Projekt. Allein zwischen dem 30. Juli und dem 1. Oktober stammte mehr als die Hälfte aller Community-Pull-Requests von Syncfusion-Mitarbeiter:innen.

Mit der TitleBar macht MAUI weiteres Land auf dem Desktop gut

Obwohl .NET MAUI als echtes Cross-Platform-Framework positioniert wird, das sich gleichermaßen für mobile Geräte und den Desktop eignet, wird es in der Community – ähnlich wie sein Vorgänger Xamarin.Forms – oft als reines Mobile-Framework wahrgenommen, das primär unter Android und iOS eingesetzt wird.

Um dieser Wahrnehmung entgegenzuwirken, hat Microsoft in den letzten Versionen von MAUI gezielt Desktopfeatures eingeführt. Funktionen wie Menüs, Kontextmenüs und Maus-Events haben den Desktop bereits gestärkt. Mit der aktuellen Version 9 kommt nun ein weiteres Feature für den Windows-Desktop hinzu: die TitleBar Control.

Dieses Steuerelement erlaubt es Entwickler:innen, die Titelleiste von Windows-Desktop-Anwendungen umfassend anzupassen. Wie bei modernen Office-Anwendungen kann die Titelleiste nun neben einem App-Icon und einem Titel auch einen Untertitel sowie drei separate Inhaltsbereiche enthalten. Diese bieten in den Bereichen LeadingContent, Content und TrailingContent Platz für interaktive Elemente wie Suchleisten, Buttons oder zusätzliche Icons, was die Titelleiste funktionaler und moderner macht.

Listing 1 zeigt die Definition einer TitleBar. Das Beispiel veranschaulicht, wie man Icons, eine Suchleiste und weitere Inhalte in die Titelleiste einfügen kann. Zusätzlich lassen sich Titel, Untertitel und das App-Icon individuell anpassen, um die Titelleiste an das Design Ihrer Anwendung anzupassen. Das Ergebnis ist in Abbildung 1 zu sehen.

<?xml version="1.0" encoding="utf-8" ?>
<Window xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="DontLetMeExpire.AppWindow">
  <Window.TitleBar>
    <TitleBar Title="Don't let me expire"
              Icon="logo.png" 
              Subtitle="Stop wasting food"
              HeightRequest="50">
      <TitleBar.LeadingContent>
        <Label Text="🍇" VerticalOptions="Center"/>
      </TitleBar.LeadingContent>
      <TitleBar.Content>
        <SearchBar 
          Placeholder="Search" 
          MaximumWidthRequest="300" 
          MaximumHeightRequest="40"/>
      </TitleBar.Content>
      <TitleBar.TrailingContent>
        <Label Text="🍎" VerticalOptions="Center"/>
      </TitleBar.TrailingContent>
    </TitleBar>
  </Window.TitleBar>
</Window>

Abb. 1: Das TitleBar-Steuerelement verleiht Windows-Apps ein modernes Design, das sich an Office-Anwendungen orientiert

Abb. 1: Das TitleBar-Steuerelement verleiht Windows-Apps ein modernes Design, das sich an Office-Anwendungen orientiert

Derzeit ist das TitleBar-Steuerelement ausschließlich für Windows verfügbar. Microsoft plant jedoch, die Unterstützung in einem zukünftigen Update auf Mac Catalyst zu erweitern. Damit würde das Steuerelement auch auf dem Mac verfügbar sein und zur plattformübergreifenden Konsistenz beitragen, was die Entwicklung von Desktop-Apps für mehrere Plattformen erheblich erleichtert.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

HybridWebView: Web trifft auf native App-Entwicklung

Das neue HybridWebView-Steuerelement in .NET MAUI 9 bietet die Möglichkeit, Webinhalte nahtlos in plattformübergreifende Anwendungen zu integrieren. Ursprünglich als experimentelles Projekt von Microsoft-Mitarbeiter Eilon Lipton gestartet [1], hat sich das Steuerelement in den letzten zwei Jahre zu einem vollwertigen Bestandteil des Frameworks entwickelt.

Während das klassische WebView-Steuerelement, das es bereits seit der ersten .NET-MAUI-Version und schon unter Xamarin gab, die Darstellung von Webinhalten wie HTML, CSS und JavaScript ermöglichte, geht HybridWebView einen Schritt weiter: Es erlaubt eine bidirektionale Kommunikation zwischen der Webansicht und der nativen App. Dadurch können Webtechnologien und native Funktionen optimal miteinander kombiniert werden.

HybridWebView ist eine ideale Wahl für hybride Anwendungen, die auf Frameworks wie Angular, React, Svelte oder Vue basieren. Anders als die Blazor-Integration über BlazorWebView, die ausschließlich auf das .NET-Ökosystem ausgelegt ist, ermöglicht HybridWebView die nahtlose Einbindung beliebiger Webframeworks. Bestehende Webanwendungen können einfach in den Ordner Resources\Raw\wwwroot kopiert werden. Da die Inhalte lokal bereitgestellt werden, ist keine Internetverbindung für die Ausführung erforderlich.

Für neue Projekte bietet sich HybridWebView besonders für Teams an, die sich stärker auf Webtechnologien wie HTML, CSS und JavaScript fokussieren und weniger Erfahrung oder Vorlieben in C# und XAML haben. Es erlaubt diesen Teams, ihre vorhandene Webexpertise zu nutzen, ohne dabei auf die Vorteile nativer Funktionen wie den Zugriff auf Gerätesensoren, Offlinefähigkeiten oder die tiefere Integration ins Betriebssystem verzichten zu müssen.

Darüber hinaus eignet sich HybridWebView perfekt für Szenarien, in denen bestehende HTML- und JavaScript-Anwendungen als App ausgeliefert und durch native Funktionen ergänzt werden sollen. Es schlägt eine Brücke zwischen der Flexibilität des Webs und der Leistungsfähigkeit nativer Apps – eine ideale Lösung für hybride Projekte, die das Beste aus beiden Welten verbinden möchten.

Das HybridWebView in der Praxis

Im folgenden Beispiel betrachten wir die Einbettung des HybridWebView-Steuerelements in eine .NET-MAUI-App sowie die bidirektionale Kommunikation zwischen der App und der eingebetteten Webanwendung.

In Listing 2 sehen wir die Definition einer einfachen Bildschirmmaske, die aus einem Eingabefeld, einem Button und einer HybridWebView besteht. Beim Klick auf den Button wird der Inhalt des Eingabefelds über die HybridWebView an die Webanwendung gesendet. Gleichzeitig sollen Nachrichten aus der Webanwendung empfangen und in der App verarbeitet werden können.

<Grid RowDefinitions="auto, *" ColumnDefinitions="*,auto">
  <!-- Eingabefeld für die Nachricht -->
  <Entry
    x:Name="messageText"
    Grid.Column="0"
    Placeholder="Bitte Nachricht eintragen" />

  <!-- Button zum Senden der Nachricht -->
  <Button
    Clicked="OnSendMessageButtonClicked"
    Grid.Column="1"
    Text="Senden" />

  <!-- HybridWebView für die Webansicht -->
  <HybridWebView
    x:Name="hybridWebView"
    Grid.Row="1"
    RawMessageReceived="OnHybridWebViewRawMessageReceived" />
</Grid>

Den zugehörigen C#-Code für die Interaktion sehen wir in Listing 3. Hier gibt es zwei zentrale Ereignisbehandlungsroutinen:

  • OnSendMessageButtonClicked: Diese Methode ruft auf dem HybridWebView Control die Funktion SendRawMessage auf, um Nachrichten von der App an die Webanwendung zu senden.
  • OnHybridWebViewRawMessageReceived: Diese Methode verarbeitet Nachrichten, die aus der Webanwendung empfangen werden, und zeigt sie in einem einfachen Dialogfenster an.
private void OnSendMessageButtonClicked(object sender, EventArgs e)
{
  hybridWebView.SendRawMessage(messageText.Text);
}

// Nachricht aus der Webansicht empfangen und anzeigen
private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
{
  await DisplayAlert("Nachricht empfangen", e.Message, "OK");
}

Damit die Kommunikation zwischen der nativen App und der Webanwendung funktioniert, muss der Webanwendung zusätzlicher JavaScript-Code hinzugefügt werden. Dieser stellt sicher, dass Nachrichten empfangen und gesendet werden können. Einen vollständigen Überblick über den benötigten Code findet man in der offiziellen .NET-MAUI-Dokumentation [2]. Listing 4 zeigt einen Auszug aus der Datei hybridwebview.js, die als Kommunikationsschnittstelle dient.

window.HybridWebView = {
  "Init": function Init() {
    function DispatchHybridWebViewMessage(message) {
      const event = new CustomEvent("HybridWebViewMessageReceived", { detail: { message: message } });
      window.dispatchEvent(event);
    }

    // Weitere Implementierung
  },

  "SendRawMessage": function SendRawMessage(message) {
    // Implementierung der Methode SendRawMessage
  },
};

window.HybridWebView.Init();

Die Kommunikation innerhalb der JavaScript-Anwendung mit der .NET-MAUI-App zeigen Listing 5 und Listing 6. In Listing 5 sehen wir den Empfang von Nachrichten in JavaScript aus .NET, während Listing 6 den Versand von Nachrichten aus JavaScript an .NET zeigt.

function receiveMessageFromMaui() {
  const hybridWebView = window.HybridWebView;
  if (hybridWebView) {
    window.addEventListener("HybridWebViewMessageReceived", (message) => {
      alert("Nachricht von der App: " + message);
    });
  }
}
const message = 'Hallo von JavaScript';
const hybridWebView = window.HybridWebView;
if (hybridWebView) {
  hybridWebView.SendRawMessage(message);
}

Der Beispielcode ist bewusst einfach gehalten. Er zeigt jedoch, wie leistungsfähig und gleichzeitig einfach die bidirektionale Kommunikation zwischen JavaScript und .NET MAUI mit dem HybridWebView-Steuerelement umgesetzt werden kann. In einer echten Anwendung könnte man problemlos die gesamte Benutzeroberfläche mit HTML und JavaScript realisieren und C# zur Interaktion mit der Hardware nutzen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Überraschender Rückenwind für .NET MAUI: Das Open-Source-Toolkit von Syncfusion

Während die Veröffentlichung der TitleBar und des HybridWebView bereits längere Zeit vor dem offiziellen .NET-MAUI-9-Release bekannt waren, sorgte die Partnerschaft mit dem Komponentenhersteller Syncfusion für die größte Überraschung in der Community. Neben der aktiven Mitarbeit an Issues im .NET-MAUI-Repository entschied sich Syncfusion, über zehn Steuerelemente unter der Open-Source-MIT-Lizenz bereitzustellen. Die Liste der neuen Steuerelemente umfasst unter anderem:

  • ein Chart Control mit mehreren Diagrammtypen
  • ein Kalendersteuerelement mit anpassbaren Ansichten
  • ein Bottom Sheet zum Einblenden zusätzlicher Inhalte
  • eine Carousel View für touchfähige Galerien
  • Diverse Steuerelemente zur Texteingabe, darunter ein Numeric Entry
  • Buttons und Chips
  • einen Navigation Drawer (seitliches Navigationsmenü)
  • ein Tab Control
  • visuelle Effekte wie Shimmer und Effects View für moderne UI-Elemente
  • verschiedene weitere Steuerelemente

Viele Entwickler:innen dürften sich besonders über die Chart-Steuerelemente freuen. Die Visualisierung von Daten in Diagrammen ist ein gängiges und oft unverzichtbares Feature in modernen Apps. Da .NET MAUI bisher keine nativen Steuerelemente für diesen Zweck bereitstellte, waren Entwickler:innen  bislang häufig auf kommerzielle Bibliotheken angewiesen.

Eine detaillierte Vorstellung des Toolkits hat Tam Hanna bereits in seinem Artikel „Syncfusion Toolkit für .NET MAUI“ in der Ausgabe 2.2025 des Windows Developer vorgenommen [3]. Daher wird in diesem Artikel auf eine ausführliche Betrachtung inklusive Codebeispielen verzichtet. Stattdessen möchte ich die Steuerelemente in den Abbildungen 2 und 3 in Aktion zeigen. Abbildung 2 zeigt den Einsatz des Chart Controls, während Abbildung 3 das Shimmer Control demonstriert, eine moderne Form der Ladeanimation.

Abb. 2: Dank Syncfusion kann man nun auch Diagramme in .NET-MAUI-Apps anzeigen

Abb. 2: Dank Syncfusion kann man nun auch Diagramme in .NET-MAUI-Apps anzeigen

Abb. 3: Mit dem Syncfusion Shimmer Control kann man Ladezeiten in Apps ansprechend überbrücken

Abb. 3: Mit dem Syncfusion Shimmer Control kann man Ladezeiten in Apps ansprechend überbrücken

Neue Projektvorlagen: Blazor Hybrid und Multi-Project-Apps

Eine auf den ersten Blick offensichtliche Neuerung von .NET MAUI 9 betrifft die Projektvorlagen. Mit .NET MAUI 9 wurden zwei neue Vorlagen eingeführt, die Entwickler:innen beim Start neuer Projekte zur Verfügung stehen. Abbildung 4 zeigt die beiden neuen Vorlagen .NET MAUI Blazor Hybrid and Web App und .NET MAUI Multi-Project App im Dialog zur Anlage eines neuen Projekts.

Abb. 4: .NET MAUI 9 bringt zwei neue Projektvorlagen mit

Abb. 4: .NET MAUI 9 bringt zwei neue Projektvorlagen mit

Die Vorlage .NET MAUI Blazor Hybrid and Web App richtet sich speziell an Entwickler:innen, die sowohl mit Blazor als auch mit MAUI arbeiten möchten. Sie ermöglicht es, Code für Webanwendungen und native MAUI-Apps gemeinsam zu nutzen. Die Projektstruktur umfasst ein .NET-MAUI-Projekt, ein oder zwei Blazor-Projekte und eine Razor-Klassenbibliothek mit der Endung .Shared (siehe Abbildung 5).

Abb. 5: Die Struktur einer „.NET MAUI Blazor Hybrid and Web App“ bringt eine Razor-Klassenbibliothek für gemeinsam genutzten Quellcode mit

Abb. 5: Die Struktur einer „.NET MAUI Blazor Hybrid and Web App“ bringt eine Razor-Klassenbibliothek für gemeinsam genutzten Quellcode mit

In der Shared-Bibliothek befinden sich die gemeinsam genutzten Benutzeroberflächen in Razor, wie z. B. Counter.razor, Home.razor und Weather.razor. Sowohl das .NET-MAUI- als auch das Blazor-Projekt greifen auf diese geteilten Komponenten zu, wodurch sie sowohl im Webbrowser als auch in nativen Anwendungen angezeigt werden können. Abbildung 6 zeigt die Ausgabe einer solchen Anwendung – dargestellt im Webbrowser, auf Android und als Windows-Desktop-App.

Abb. 6: Mit Blazor kommt man sowohl ins Web als auch auf den Desktop und mobile Endgeräte

Abb. 6: Mit Blazor kommt man sowohl ins Web als auch auf den Desktop und mobile Endgeräte

Die zweite neue Vorlage, .NET MAUI Multi-Project App, orientiert sich an einer traditionelleren Struktur und erinnert an das bekannte Modell aus Xamarin.Forms. Ein Projekt wird für den plattformübergreifenden Code angelegt, während für jede Zielplattform (z. B. Android, iOS oder Windows) ein separates Projekt erstellt wird (siehe Abbildung 7).

Abb. 7: Die Struktur einer „.NET MAUI Multi-Project App“ entspricht der traditionellen Xamarin.Forms-Projektstruktur

Abb. 7: Die Struktur einer „.NET MAUI Multi-Project App“ entspricht der traditionellen Xamarin.Forms-Projektstruktur

Diese Vorlage ist besonders für Entwickler:innen interessant, die von Xamarin.Forms auf MAUI migrieren und mit der Multi-Project-Struktur vertraut sind. Darüber hinaus kann sie in Projekten hilfreich sein, bei denen eine strikte Trennung zwischen Plattform- und plattformübergreifendem Code erforderlich ist. Für die Migration bestehender Xamarin.Forms-Anwendungen kann diese Vorlage ebenfalls Vorteile bieten, da die Struktur den Übergang erleichtert.

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

Best Practices frei Haus

Eine weitere Neuerung in .NET MAUI 9 ist die Option Include Sample Content, die bei der Anlage eines neuen Projekts verfügbar ist. Diese Option erlaubt es Entwickler:innen, mit einer voll funktionsfähigen Beispielanwendung zu starten, anstatt von Grund auf neu beginnen zu müssen. Die Vorlage demonstriert einige Best Practices, wie den Einsatz des MVVM-Entwurfsmusters, Datenpersistenz per SQLite sowie den Einsatz des .NET MAUI Community Toolkits und der Syncfusion-Steuerelemente. Dank der sauberen Struktur, die Abbildung 8 zeigt, kann eine mit dieser Vorlage generierte App als interessantes Lernprojekt angesehen werden. Abbildung 9 zeigt die laufende App, die sowohl den Dark als auch den Light Mode unterstützt.

Abb. 8: Wählt man bei der Projektanlage die Option „Include sample content“ erhält man eine umfangreiche Beispielanwendung

Abb. 8: Wählt man bei der Projektanlage die Option „Include sample content“ erhält man eine umfangreiche Beispielanwendung

Abb. 9: Die Beispielanwendung in Aktion

Abb. 9: Die Beispielanwendung in Aktion

Weitere Neuerungen in .NET MAUI 9

Neben den neuen Steuerelementen und Projektvorlagen bringt .NET MAUI 9 auch einige kleinere, aber dennoch praktische Verbesserungen mit sich.

Eine dieser Neuerungen sind zwei optionale Handler für iOS und Mac Catalyst, die die Performance der CollectionView und der CarouselView verbessern. Diese neuen Handler können in der Datei MauiProgram.cs registriert werden, wie Listing 7 zeigt.

#if IOS || MACCATALYST
builder.ConfigureMauiHandlers(handlers =>
{
  handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
  handlers.AddHandler<Microsoft.Maui.Controls.CarouselView, Microsoft.Maui.Controls.Handlers.Items2.CarouselViewHandler2>();
});
#endif

Eine weitere Verbesserung betrifft Eingabefelder: .NET MAUI 9 bietet eine erweiterte Unterstützung für die Eingabe von Datums- und Uhrzeitwerten sowie Passwörtern über die virtuelle Bildschirmtastatur.

Darüber hinaus unterstützt die neue Version nun Android 15 (API Level 35) und führt Android Asset Packs ein. Diese Erweiterung ist vor allem für Entwickler:innen von Spielen und datenintensiven Anwendungen interessant. Während die bisherige Größenbeschränkung von Google Play für Standardpakete bei 200 MB lag, erlauben Asset Packs das Hochladen von bis zu 2 GB großen Paketen. Diese Funktionalität bietet deutlich mehr Flexibilität bei der Entwicklung und Verteilung großer Apps und ist ein bedeutender Schritt für umfangreiche und speicherintensive Anwendungen.

 

Fazit

.NET MAUI 9 setzt weiterhin den Schwerpunkt auf Stabilität und Fehlerbehebung – ein entscheidender Faktor, insbesondere für Unternehmen, die bestehende Xamarin.Forms-Apps zu .NET MAUI migrieren müssen.

Auch wenn Microsoft selbst in diesem Release nur wenige neue Funktionen liefert, stechen die neuen Projektvorlagen hervor, die die Entwicklung für Web- und Desktopanwendungen gleichermaßen vereinfachen. Besonders die Option Include Sample Content, die Best Practices direkt integriert, erleichtert den Einstieg und bietet eine solide Grundlage für neue Projekte. Die Integration des HybridWebView und das TitleBar-Steuerelement zeigen zudem, wie MAUI sich zunehmend als echtes Cross-Platform-Framework etabliert – nicht nur für mobile Geräte, sondern auch für den Desktop.

Durch die Partnerschaft mit Syncfusion schließt .NET MAUI zudem wichtige Lücken, die Entwickler:innen bisher mit kommerziellen Bibliotheken füllen mussten.

Insgesamt bietet .NET MAUI 9 eine stabile und vielseitige Basis, die die plattformübergreifende App-Entwicklung effizienter und attraktiver macht.


Links & Literatur

[1] https://github.com/Eilon/MauiHybridWebView

[2] https://learn.microsoft.com/de-de/dotnet/maui/user-interface/controls/hybridwebview?view=net-maui-9.0#create-a-net-maui-hybridwebview-app

[3] https://entwickler.de/dotnet/syncfusion-toolkit-for-net-maui

The post .NET MAUI 9: Entdecken Sie die neuesten Funktionen und Verbesserungen appeared first on BASTA!.

]]>
Meistern Sie .NET Aspire: Effektive Tools für Entwickler https://basta.net/blog/optimale-softwareentwicklung-net-aspire/ Mon, 10 Feb 2025 12:20:01 +0000 https://basta.net/?p=107063 .NET Aspire ist eine innovative Erweiterung des .NET-Ökosystems, die die Entwicklung moderner, skalierbarer und leistungsfähiger Anwendungen erleichtert. Diese Plattform bietet Entwickler:innen eine Vielzahl von Tools und Bibliotheken, die speziell für die Anforderungen der heutigen Softwareentwicklung konzipiert sind. Einige der wichtigsten Möglichkeiten, die Aspire Entwickler:innen bietet, werden hier vorgestellt und erklärt.

The post Meistern Sie .NET Aspire: Effektive Tools für Entwickler appeared first on BASTA!.

]]>
Software wird seit vielen Jahren immer komplexer, wodurch es für Entwickler:innen immer schwieriger wird, Fehler zu beheben oder auch nur die Software am Entwicklungsrechner zu starten. In der Cloud gibt es mittlerweile eine Vielzahl von Tools, die, nachdem sie konfiguriert sind, den Betrieb vereinfachen. Für verteilte Systeme wird häufig Kubernetes verwendet, Deployments sind via CI/CD Pipelines automatisiert und alle Cloud-Anbieter stellen Tools zum Monitoring, Sammeln von Logs etc. zu Verfügung. Für die lokale Entwicklung hat es aber bis jetzt nur vereinzelt Lösungen gegeben, die allerdings alle mit ihren eigenen Nachteilen kommen.

Die wahrscheinlich am häufigsten eingesetzte Methode zum Testen von verteilter und komplexer Software am Entwicklungsrechner ist Docker Compose. Hierbei werden alle benötigten Komponenten in einer YAML-Datei konfiguriert und anschließend gemeinsam gestartet. Der Vorteil ist, dass diese YAML-Datei in Git eingecheckt wird und so einfach zwischen den Entwickler:innen geteilt werden kann. Diese Datei kann auch als Dokumentation dienen, da sie die Abhängigkeiten zwischen den Anwendungen beschreibt und festhält, welche Umgebungsvariable verwendet wird und welche Ports benötigt werden. Ein Beispiel dafür ist die Konfiguration des eigenen Frontends, eines API-Backends, einer Datenbank und eines Cache. Mit dem Befehl docker-compose up können alle Anwendungen gestartet und getestet werden. Der Nachteil dieser Lösung ist allerdings, dass die Anwendungen als Docker-Container laufen und nicht in einer Entwicklungsumgebung debuggt werden können.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Aus diesem Grund hat Microsoft im Jahr 2020 Project Tye [1] vorgestellt, um die Entwicklung und die Fehlersuche in verteilten Systemen zu vereinfachen. Die Idee war gut, die ersten Versionen vielversprechend. Leider fehlte jedoch der Fokus, um die gemeldeten Probleme zu beheben. Daher verlief sich Project Tye mit der Zeit und wurde schließlich 2023 archiviert. Project Tye wirkt auf den ersten Blick wie eine gute Idee, die zu einem grandiosen Fehlschlag wurde. Allerdings hat Microsoft aus den Fehlern und Problemen gelernt und hat zeitgleich mit der Einstellung von Project Tye .NET Aspire vorgestellt.

.NET Aspire wurde erstmals im November 2023 auf der .NET Conf vorgestellt und hat seitdem erhebliches Interesse geweckt. Seit seiner offiziellen Veröffentlichung im Mai 2024 ist es zu einem zentralen Thema auf jeder Entwicklerkonferenz geworden. Microsoft zeigt kontinuierlich sein Engagement für .NET Aspire durch regelmäßige Erweiterungen und Updates, die die Plattform ständig verbessern und Fehler beheben. Diese kontinuierliche Weiterentwicklung stellt sicher, dass .NET Aspire immer auf dem neuesten Stand der Technik ist und Entwickler:innen immer bessere Werkzeuge zur Verfügung stellt.

Entwickler:innen können .NET Aspire einfach zu bestehenden Projekten hinzufügen und damit Abhängigkeiten zwischen verschiedenen Komponenten konfigurieren. Der Vorteil gegenüber anderen Lösungen, wie zum Beispiel Docker Compose, ist, dass .NET Aspire als Code konfiguriert wird und so die Verwendung von IDEs zum Debuggen erlaubt. Nachdem Aspire zu einem Projekt hinzugefügt wurde, konfiguriert es das Sammeln von Logs, Metriken und Traces und stellt zusätzlich ein Dashboard zu Verfügung. Der oder die Entwickler:in muss sich dabei um nichts kümmern, hat aber die Möglichkeit, die Konfiguration den eigenen Anforderungen anzupassen. Zusätzlich können weitere Tools, zum Beispiel PostgreSQL, RabbitMQ oder Azure Service Bus über NuGet installiert und mit wenigen Zeilen Code konfiguriert werden.

Listing 1 zeigt die Konfiguration eines Aspire-Projekts. Dieses Projekt hat Redis und PostreSQL via NuGet installiert und ein Frontend, das beide referenziert. Zusätzlich wird PostgreSQL mit der Methode WithPgAdmin() konfiguriert, wodurch Aspire einen Container mit pgAdmin startet und diesen automatisch für den Zugriff auf den PostgreSQL-Server konfiguriert.

// Create a distributed application builder given the command line arguments.
var builder = DistributedApplication.CreateBuilder(args);

// Add a Redis server to the application.
var cache = builder.AddRedis("cache");

var postgres = builder.AddPostgres("database")
  .WithPgAdmin();

// Add the frontend project to the application and configure it to use the 
// Redis server, defined as a referenced dependency.
builder.AddProject<Projects.MyFrontend>("frontend")
       .WithReference(cache)
       .WithReference(postgres)
       .WaitFor(cache);

Zusätzlich zur verbesserten lokalen Entwicklung kann Aspire verwendet werden, um das Projekt schnell und einfach auf Azure zu deployen. Wie bei der Entwicklung übernimmt Aspire dabei die gesamte Konfiguration und kann mit wenigen Befehlen deployt werden.

In den folgenden Abschnitten wird die Funktionsweise von Aspire anhand einiger praktischer Demos gezeigt.

Voraussetzungen für .NET Aspire

Um Aspire verwenden zu können, werden die folgenden Tools benötigt:

  • .NET 8.0 oder .NET 9.0  SDK
  • .NET Aspire SDK
  • eine OCI-compliant Container-Runtime, z. B. Docker Desktop oder Podman
  • eine Entwicklungsumgebung, z. B. Visual Studio ab Version 2022 17.9 oder Visual Studio Code

.NET-Aspire-Projekte sind für die Ausführung in Containern konzipiert. Sie können entweder Docker Desktop oder Podman als Containerlaufzeit verwenden. Docker Desktop ist die am häufigsten verwendete Laufzeit. Podman ist eine Open-Source-Alternative zu Docker, die ohne Daemon auskommt und OCI-Container (Open Container Initiative) erstellen und ausführen kann. Wenn sowohl Docker als auch Podman installiert sind, verwendet .NET Aspire standardmäßig Docker. .NET Aspire kann mit der Umgebungsvariable DOTNET_ASPIRE_CONTAINER_RUNTIME konfiguriert werden, um stattdessen Podman zu verwenden (Listing 2).

# Windows
[System.Environment]::SetEnvironmentVariable("DOTNET_ASPIRE_CONTAINER_RUNTIME", "podman", "User")

# Linux
export DOTNET_ASPIRE_CONTAINER_RUNTIME=podman

.NET-Aspire-Demoprojekte von Microsoft

Nach der ganzen Theorie ist es jetzt Zeit für etwas Praktisches. Microsoft hat auf GitHub [2] ein Repository mit Demoprojekten für .NET Aspire veröffentlicht. Dieses Repository enthält unter anderem Beispiele für Aspire mit Azure Functions, Node.js oder Python. Diese Beispiele befinden sich im samples-Ordner des GitHub-Repositorys. Für diesen Artikel wird der Aspire-Shop verwendet.

Microsoft hat in der Vergangenheit schon öfter einen Onlineshop für Demos verwendet. Dieser konnte zwar via Docker Compose gestartet werden, war ansonsten aber aufgrund der Vielzahl an Services und Datenbanken recht kompliziert für neue Benutzer:innen. Aspire auf der anderen Seite bietet ein Dashboard mit allen gestarteten Containern und deren Status an. Dadurch müssen Entwickler:innen nicht mehr die Dokumentation oder Docker-Compose-Dateien durchsuchen, um die Ports und Pfade für die verschiedenen Container herauszufinden.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Nach dem Öffnen der Aspire-Shop-Solution sollte das AspireShop.AppHost-Projekt als Start-up-Projekt ausgewählt sein. Falls nicht, muss es als Start-up-Projekt konfiguriert werden; anschließend kann die Anwendung gestartet werden. Daraufhin öffnet sich das Aspire-Dashboard, welches eine Übersicht über alle Ressourcen im Projekt gibt und dessen Status bzw. Endpoints anzeigt. Abbildung 1 zeigt, dass einige Anwendungen schon gestartet sind, sowie zwei davon den Status „Unhealthy“ haben bzw. zwei noch nicht gestartet sind.

Das Aspire-Dashboard zeigt den Status aller Anwendungen im Projekt

Abb. 1: Das Aspire-Dashboard zeigt den Status aller Anwendungen im Projekt

Je nach Internetgeschwindigkeit sollte es beim ersten Start wenige Minuten dauern, bis alle Container heruntergeladen und gestartet sind. Dann sollte der Status für alle Container „Running“ sein. Mit Hilfe der Endpoints kann jetzt ganz einfach durch die verschiedenen Anwendungen navigiert werden. So können zum Beispiel die Endpoints für das Swagger UI des Catalog Services, pgAdmin für Postgres oder das Frontend ganz einfach aufgerufen und ausprobiert werden.

Das Dashboard macht es neuen Entwickler:innen deutlich leichter, sich in einem Projekt zurecht zu finden. Zusätzlich bietet Aspire nützliche Informationen für das Monitoring und Debugging an. Auf der linken Seite im Dashboard kann so zwischen verschiedenen Ansichten für die Konsolenausgabe aller Container, Logs, Traces und Metriken gewechselt werden; zudem kann innerhalb dieser Ansichten gefiltert bzw. sortiert werden. Abbildung 2 zeigt, dass es mit Hilfe von OpenTelemetry eine Vielzahl von Metriken für den basketservice gibt, welche sowohl grafisch als auch als Tabelle angezeigt werden können.

Das Aspire-Dashboard bietet eine Vielzahl von Metriken an

Abb. 2: Das Aspire-Dashboard bietet eine Vielzahl von Metriken an

Vor allem verteilte Systeme sind dafür bekannt, unübersichtlich zu sein, wie auch dafür, dass die Fehlersuche für Entwickler:innen aufwendig und kompliziert sein kann. Hier ist Aspire sehr hilfreich, weil es OpenTelemetry, Health Checks und Service Discovery automatisch hinzufügt. Wie das genau funktioniert, sehen wir uns an einem bestehenden Blazor-Projekts an.

Hinzufügen von Aspire zu einem bestehenden Projekt

Aspire kann zu einem bestehenden Projekt mit Visual Studio, Visual Studio Code, JetBrains Rider oder via .NET CLI hinzugefügt werden. Für das .NET CLI kann der Befehl dotnet new aspire-servicedefaults -n ServiceDefaults verwendet werden. Allerdings müssen dann die Referenzen zu den bestehenden Projekten manuell hinzugefügt werden und das Aspire-Projekt muss per Hand konfiguriert werden.

Aus diesem Grund wird für diesen Abschnitt Visual Studio verwendet, um .NET Aspire zu einem bestehenden Blazor-Projekt mit dem Namen BlazorWithAspire hinzuzufügen. Grundsätzlich kann Aspire aber zu jedem .NET-Projekt ab .NET 8 hinzugefügt werden. Dazu klickt man mit der rechten Maustaste auf das Blazor-Projekt und wählt Add  | .NET Aspire Orchestrator Support aus. Dies generiert zwei neue Projekte, BlazorWithAspire.AppHost und BlazorWithAspire.ServiceDefaults.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Das AppHost-Projekt konfiguriert alle Projekte in der Programm.cs-Datei, die mit Aspire gestartet werden sollen. In diesem Fall ist es nur das BlazorWithAspire-Projekt:

var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.BlazorWithAspire>("blazorwithaspire");
builder.Build().Run();

Wenn während der Entwicklung neue Projekte erstellt werden, können diese hier hinzugefügt und konfiguriert werden.

Das zweite Aspire-Projekt, ServiceDefaults, ist im Moment wahrscheinlich das interessantere Projekt, um die Funktionsweise von Aspire zu verstehen. In diesem Projekt befindet sich eine Datei, Extensions.cs, die alle Aspire-Features konfiguriert, damit diese out of the box verwendet werden können. So werden zum Beispiel Health Checks, Metriken und Logs konfiguriert. Listing 3 zeigt einen Ausschnitt aus dieser Datei, in dem diverse Features wie OpenTelemetry und Service Discovery hinzugefügt werden.

builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
  {
    // Turn on resilience by default
    http.AddStandardResilienceHandler();
    // Turn on service discovery by default
    http.AddServiceDiscovery();
});

In dieser Datei wird die ganze „Magie“ von .NET Aspire konfiguriert. Wenn das AppHost-Projekt jetzt gestartet wird, sollte sich das Aspire-Dashboard mit einer Anwendung öffnen. Zusätzlich können die Logs, Metriken und Traces der Anwendung angezeigt werden. Abbildung 3 zeigt die Dauer der Requests der Blazor-App an, ohne dass die Blazor-App dafür konfiguriert werden musste.

.NET Aspire zeigt die Metriken der Blazor-App

Abb. 3: .NET Aspire zeigt die Metriken der Blazor-App

Die Demo hat bis jetzt gezeigt, dass mit .NET Aspire zusätzliche Dienste wie Logs und Metriken zur Anwendung hinzugefügt werden können, ohne diese zu verändern. Ein weiterer interessanter Aspekt der Demo ist, dass sie zeigt, wie Aspire die konfigurierten Anwendungen startet. Im Abschnitt über den Onlineshop wurde erwähnt, dass es ein paar Minuten dauern kann, bis alle Container heruntergeladen und gestartet sind. Das ist zwar korrekt, könnte aber den Eindruck erwecken, dass alle Anwendungen als Container gestartet werden – .NET Aspire startet .NET-Anwendungen allerdings nicht als Container. Das kann ganz einfach mit dem docker ps-Befehl überprüft werden. Nachdem Aspire die Demo-Blazor-Anwendungen gestartet hat, zeigt docker ps aber keinen laufenden Container an. Der Grund dafür ist, dass Microsoft das Debugging für Entwickler:innen so einfach wie möglich machen möchte und deshalb die Anwendungen ohne Container startet.

Anwendungen mit Aspire schnell auf Azure deployen

Die vorangegangenen Abschnitte haben eine Einführung in .NET Aspire gegeben und aufgezeigt, wie damit der Entwicklungsprozess vereinfacht werden kann. Aspire kann aber auch beim Deployment der gesamten Umgebung auf Azure helfen.

Dafür wird zusätzlich zu den bereits installierten Tools noch die Azure Developer CLI [3] benötigt. Mit dieser kann die Anwendung einfach in eine oder mehrere Azure-Container-Apps deployed werden. Das Developer CLI und Aspire sorgen dafür, dass alle benötigten Ressourcen erstellt und konfiguriert werden. So kann zum Beispiel, je nach Konfiguration des eigenen Projekts, ein Azure Key zum Speichern von sensitiven Daten wie Passwörtern oder Connection Strings gespeichert werden oder eine PostgresSQL-Datenbank erstellt werden. Die Entwickler:in muss sich dabei um nichts selbst kümmern.

Eine weitere Möglichkeit, .NET Aspire kennenzulernen, ist die .NET Aspire Starter App-Vorlage. Diese Vorlage erstellt ein Blazor-Frontend, ein API, optional ein Testprojekt mit .NET Aspire. Abbildung 4 zeigt, dass beim Erstellen des Projekts in Visual Studio Redis zum Projekt hinzugefügt werden kann. Aspire kümmert sich dabei um die Integration und Konfiguration von Redis.

Aspire kann automatisch Redis zum Projekt hinzufügen und konfigurieren

Abb. 4: Aspire kann automatisch Redis zum Projekt hinzufügen und konfigurieren

Die Vorlage erstellt eine Blazor-Anwendung mit einem Frontend, API-Backend und Redis. Die Program.cs-Datei im AppHost-Projekt konfiguriert, dass das Frontend das API und Redis verwendet; zusätzlich wird das Frontend mit dem Parameter WithExternalHttpEndpoints() konfiguriert. Dieser Parameter ist für das Deployment nützlich, da nur das Frontend einen öffentlichen URL bekommt und das Backend und Redis nicht über das Internet erreichbar sein sollen. Listing 4 zeigt die gesamte Program.cs-Datei.

var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");

var apiService = builder.AddProject<Projects.BlazorWithAspireForAzure_ApiService>("apiservice");

builder.AddProject<Projects.BlazorWithAspireForAzure_Web>("webfrontend")
  .WithExternalHttpEndpoints()
  .WithReference(cache)
  .WaitFor(cache)
  .WithReference(apiService)
  .WaitFor(apiService);

builder.Build().Run();

Zum Testen der Anwendung kann sie gestartet und im Frontend die verschiedenen Menüpunkte aufgerufen werden. Diese Aufrufe sieht man auch im Aspire-Dashboard im Bereich Traces. Abbildung 5 zeigt zum Beispiel, dass der weather Endpoint vom Frontend aufgerufen wurde, dieser weatherforcast auf dem API aufgerufen und dann Daten in Redis geschrieben hat.

Die Trace zeigt den Request vom Frontend an das Backend und Redis

Abb. 5: Die Trace zeigt den Request vom Frontend an das Backend und Redis

Nachdem die Anwendung getestet ist, kann sie mit dem Azure Developer CLI deployt werden. Zunächst kann mit dem Befehl azd auth login in der Kommandozeile im zuvor angelegten Projekt das Log-in durchgeführt werden; anschließend wird mit azd init der Deployment Wizard gestartet. Das Deployment für diese Demo wurde mit den folgenden Angaben konfiguriert:

  • Use code in the current directory: Dieser Befehl scannt den aktuellen Ordner nach Projekten und listet diese auf.
  • Confirm and continue initializing my app: Wenn das CLI das richtige Projekt gefunden hat, kann mit diesem Projekt der Wizard fortgesetzt werden.
  • Aspire-Demo: Als letzter Schritt muss ein Name für die Ressource-Gruppe angegeben werden, in die die Anwendung später deployt wird. Diese Ressource-Gruppe wird beim Deployment ebenfalls erstellt.

Nachdem der Name für die Ressource-Gruppe eingegeben ist, erstellt das Developer CLI zwei Dateien: azure.yaml und next-steps.md. Die Datei azure.yaml beinhaltet den Pfad zum zuvor konfigurierten Projekt; die next-steps.md-Datei gibt eine detaillierte Anleitung, wie die Anwendung deployt werden kann und bietet zusätzlich Links zur Dokumentation für weitere Informationen an. Abbildung 6 und Abbildung 7 zeigen das Konsolenfenster, in dem die Demoanwendung mit dem Azure Developer CLI konfiguriert wurde.

Das Azure Developer CLI listet das Demoprojekt auf

Abb. 6: Das Azure Developer CLI listet das Demoprojekt auf

Das Azure Developer CLI erstellt die Deployment-Dateien

Abb. 7: Das Azure Developer CLI erstellt die Deployment-Dateien

Der letzte Befehl für das Deployment ist azd up. Dieser Befehl erstellt alle benötigten Ressourcen in Azure, bevor die Demoanwendung deployt wird. Mit den Befehlen azd provision und azd deploy kann das Deployment auf zwei Schritte aufgeteilt werden. Nach der Eingabe des azd provision-Befehls (und auch nach der Eingabe von azd up) muss die gewünschte Azure Subscription und Region angegeben werden. Danach erstellt das CLI die benötigten Ressourcen, wie in Abbildung 8 gezeigt. Das Developer CLI verwendet Bicep-Skripte [4] für das Deployment. Dadurch können Änderungen ganz einfach mit denselben Befehlen deployt werden, und Bicep aktualisiert nur die angepassten Ressourcen.

Alle benötigten Azure-Ressourcen wurden mit dem Azure Developer CLI automatisch erstellt

Abb. 8: Alle benötigten Azure-Ressourcen wurden mit dem Azure Developer CLI automatisch erstellt

Folgende Ressourcen wurden erstellt:

  • Ressource-Gruppe: Diese Gruppe beinhaltet alle Ressourcen.
  • Container-Registry: In diese Registry werden die Container hochgeladen, die für die Azure-Container-Apps verwendet werden.
  • Log Analytics Workspace: Alle Logs und Metriken werden im Workspace gespeichert und können dort durchsucht und angezeigt werden.
  • Container-Apps-Environment: Vereinfacht gesagt handelt es sich hierbei um einen Behälter, in dem eine oder mehrere Container-Apps erstellt werden können. Alle Container in dieser Umgebung können untereinander kommunizieren.
  • Managed Identity (wird nicht in der Konsole in Abbildung 8 angezeigt): Die Managed Identity wird von den Container-Apps verwendet und wurde berechtigt, um die Images aus der Container-Registry herunterzuladen.

SIE LIEBEN AZURE?

Entdecken Sie die BASTA! Tracks

Nachdem die Ressourcen angelegt wurden, kann die Anwendung mit azd deploy deployt werden. Dieser Befehl scannt das Aspire-Projekt, erstellt für alle Anwendungen einen Container und lädt diese Container in die Container-Registry hoch.

Die Anwendungen und das Aspire-Dashboard wurden deployed

Abb. 9: Die Anwendungen und das Aspire-Dashboard wurden deployed

Abbildung 9 zeigt, dass Frontend, API-Backend, Redis und Aspire-Dashboard deployt wurden. Das Backend und Redis haben ein internal in der URL, das Frontend hat das allerdings nicht. Aspire weiß aufgrund der Konfiguration der Anwendungen, welche Container über das Internet erreichbar sein sollen und welche nicht. In der Demo-App wurde nur das Frontend mit dem zuvor beschriebenen WithExternalHttpEndpoints()-Parameter konfiguriert, wodurch nur dieser Container eine öffentliche URL bekommt. Aspire deployt auch das Aspire-Dashboard, welches fast genau wie in der lokalen Umgebung genutzt werden kann. Der einzige Unterschied zwischen der lokalen und der Azure-Instanz ist, dass für das Aufrufen des Azure-Dashboards ein Log-in benötigt wird. Aspire integriert automatisch das Log-in mit Entra in das Dashboard, wodurch nur User im Azure Tenant Zugriff auf die Anwendung haben.

Das Aspire-Dashboard ist in Azure deployt

Abb. 10: Das Aspire-Dashboard ist in Azure deployt

Abbildung 10 zeigt das Dashboard nach dem Log-in in Azure. Was dabei auffällt, ist, dass alle Endpoints via HTTPS aufgerufen werden können. Das ist ein nützliches Feature der Azure-Container-Apps. Alle Container-Apps werden mit einem automatisch erstellten TLS-Zertifikate erstellt und verwenden HTTPS, ohne dass die Entwickler:in etwas konfigurieren muss. Abbildung 11 zeigt die zuvor erstellten Ressourcen und die Container-Apps im Azure Portal.

Alle erstellten Ressourcen im Azure Portal

Abb. 11: Alle erstellten Ressourcen im Azure Portal

Im Azure Portal sieht man auch die Konfiguration der einzelnen Container-Apps. Abbildung 12 zeigt die Umgebungsvariablen des Frontends.

Die Umgebungsvariablen des Frontends wurden automatisch konfiguriert

Abb. 12: Die Umgebungsvariablen des Frontends wurden automatisch konfiguriert

Hier sind zum Beispiel die Endpoints der Redis- und Backend-Container konfiguriert, wie auch der Connection String für Redis. Hier sieht man zudem, dass sensitive Daten, zum Beispiel der Connection String, als Secret referenziert werden.

Das letzte interessante Feature des Deployments ist das Erstellen der Container für Frontend und Backend. Etwas weiter oben wurde die Azure-Container-Registry erwähnt, welche die Container-Images für die Container-Apps beinhaltet. Während der Entwicklung oder des Deployments wurde aber kein Dockerfile oder Ähnliches konfiguriert, um die Images zu erstellen. Um die Container-Images ohne Dockerfiles zu erstellen, nutzt Aspire ein .NET-Feature, das mit .NET 8 eingeführt wurde [5]. Seit .NET 8 kann das .NET SDK Docker Images erstellen, ohne dass dafür ein Dockerfile benötigt wird. Das erlaubt das einfache und schnelle Deployment des Aspire-Projekts.

Wenn die Ressourcen nicht mehr benötigt werden, können sie mit dem Befehl azd down gelöscht werden. Das Azure Developer CLI listet alle Ressourcen auf; nachdem das Löschen bestätigt ist, wird die gesamte Ressource-Gruppe gelöscht. Das dauert normalerweise nur wenige Minuten. Abbildung 13 zeigt aber, dass es manchmal auch etwas länger dauern kann.

Die gesamte Umgebung wurde gelöscht

Abb. 13: Die gesamte Umgebung wurde gelöscht

Migration von Docker Compose zu .NET Aspire

Viele Projekte verwenden für die Konfiguration der Abhängigkeiten während der Entwicklung Docker Compose. Diese Abhängigkeiten können eine Datenbank, Redis oder eine Queue wie etwa RabbitMQ sein, welche einfach mit einem Befehl gestartet werden können. Dadurch müssen sich vor allem neue Entwickler:innen nicht um die Konfiguration aller Komponenten kümmern und können so schnell produktiv im Projekt mitarbeiten.

Da .NET Aspire deutlich mehr Funktionalität als Docker Compose mitbringt, ist es empfehlenswert, bei neuen Projekten auf Aspire anstatt Docker Compose zu setzen. Für bestehende Projekte kann sich eine Migration von Docker Compose auszahlen, da diese oft recht schnell und einfach durchgeführt werden kann. Das größte Hindernis für eine Migration ist, dass Aspire externe Komponenten via NuGet-Paket einbindet. Sollte es noch kein NuGet-Paket für das gewünschte Tool geben, kann dieses derzeit nicht mit Aspire konfiguriert werden.

Um eine Migration von Docker Compose zu .NET Aspire zu demonstrieren, wird die zuvor erstellte Anwendung verwendet und ein fiktives Beispiel erstellt, in dem diese Anwendung neben Frontend und Backend auch PostgreSQL als Datenbank, Redis als Cache und RabbitMQ als Queue verwendet. Alle externen Komponenten werden in diesem fiktiven Beispiel in Docker Compose konfiguriert und an die Container der Blazor-Anwendung angehängt.

Wie bereits zuvor beschrieben kann einem bestehenden Projekt .NET Aspire (mit einem Rechtsklick auf das Webprojekt; dann mit Add  | .NET Aspire Orchestrator Support) hinzugefügt werden. Das erstellt zwei neue Projekte: Projektname.AppHost und Projektname.ServiceDefaults. In der Program.cs-Datei des AppHost-Projekts wird Aspire konfiguriert. Diese Datei sollte in etwa wie in Listing 4 aussehen. Nun können die benötigten externen Komponenten über NuGet-Pakete installiert werden. In Visual Studio kann dazu mit der rechten Maustaste auf das AppHost-Projekt geklickt werden und unter Add | .NET Aspire package ausgewählt werden. Alternativ kann auch das NuGet-Menü geöffnet und dort nach Aspire gesucht werden. Abbildung 14 zeigt, dass es bereits eine Vielzahl NuGet-Pakete für .NET Aspire gibt und diese die unterschiedlichsten Technologien unterstützen. So gibt es zum Beispiel NuGet-Pakete für SQL Server, Azure Key Vault, MongoDB und auch für AWS.

Aspire bietet eine Vielzahl von NuGet-Paketen an

Abb. 14: Aspire bietet eine Vielzahl von NuGet-Paketen an

Für diese Demo werden die Redis, PostgreSQL und RabbitMQ Pakete installiert. Anschließend müssen diese noch in der Program.cs-Datei im AppHost-Projekt konfiguriert werden.

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

var postgres = builder.AddPostgres("database")
  .WithDataVolume()
  .WithPgAdmin();

var rabbitMq = builder.AddRabbitMQ("rabbitMq")
  .WithManagementPlugin();

var apiService = builder.AddProject<Projects.BlazorWithAspireForAzure_ApiService>("apiservice")
  .WithReference(postgres)
  .WithReference(rabbitMq);

builder.AddProject<Projects.BlazorWithAspireForAzure_Web>("webfrontend")
  .WithExternalHttpEndpoints()
  .WithReference(cache)
  .WaitFor(cache)
  .WithReference(apiService)
  .WithReference(postgres)
  .WithReference(rabbitMq)
  .WaitFor(apiService);

builder.Build().Run();

Listing 5 zeigt die vollständige Program.cs-Datei, in der Frontend und API konfiguriert sind, sowie Redis, PostgreSQL und RabbitMQ hinzugefügt wurden. Redis wird mit der Methode AddRedis hinzugefügt und in einer Variable gespeichert. Diese Variable wird im Frontend referenziert, damit das Frontend auf Redis zugreifen kann; zusätzlich wartet das Frontend, bis Redis verfügbar ist.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Als nächstes wird PostgreSQL mit der Methode AddPostgres erstellt. Diese Methode verwendet die Erweiterungen WithDataVolume() und WithPgAdmin(). Mit dem Volume kann die Datenbank persistiert werden und bleibt dadurch beim Neustart der Container bestehen. Die PgAdmin-Methode konfiguriert das Management-Tool pgAdmin, damit die PostgreSQL-Datenbank mit einem UI verwaltet werden kann.

Zuletzt wird RabbitMQ mit der Methode AddRabbitMQ hinzugefügt und im Frontend sowie im API referenziert. Damit können beide Anwendungen auf RabbitMQ zugreifen und Nachrichten verschicken und empfangen. RabbitMQ bietet auch die Erweiterungsmethode, WithManagementPlugin(), wodurch automatisch das Management-UI von RabbitMQ konfiguriert wird und auch im Aspire-Dashboard angezeigt wird.

Wenn die AppHost-Anwendung jetzt gestartet wird, sollten nach dem Herunterladen der Container alle Anwendungen laufen. Abbildung 15 zeigt das Aspire-Dashboard mit dem Frontend und dem API sowie allen zuvor konfigurierten Tools inklusiver der Management-UIs.

Das Aspire-Dashboard mit allen konfigurierten Tools

Abb. 15: Das Aspire-Dashboard mit allen konfigurierten Tools

Das RabbitMQ-Management-UI fehlt in dieser Auflistung, weil es ein Teil von RabbitMQ ist und daher unter dem URL für RabbitMQ aufgerufen werden kann. Beim ersten Aufruf des Management-UIs werden Benutzername und Passwort benötigt. Diese können aus den Umgebungsvariablen des RabbitMQ-Containers ausgelesen werden. Es ist generell zu empfehlen, sich durch die Umgebungsvariablen der verschiedenen Anwendungen zu klicken, um einen besseren Einblick in die Konfiguration zu bekommen.

.NET Aspire kann aber nicht nur durch NuGet-Pakete erweitert werden, es kann auch durch die Implementierung der Aspire-Features erweitert werden. Die Konfiguration dazu befindet sich im ServiceDefaults-Projekt. Dort kann zum Beispiel das Logging oder das Tracing um weitere Datenquellen erweitert werden. In .NET wird mit RabbitMQ oft MassTransit [5] als Tracing-Tool verwendet. Dieses kann einfach als neue Quelle im Tracing hinzugefügt werden, wodurch alle MassTransit-Traces ebenfalls im Aspire-Dashboard angezeigt werden können. Die Implementierungsdetails dazu würden hier allerdings den Rahmen sprengen.

Fazit

.NET Aspire ist ein neues Tool von Microsoft, das Entwickler:innen bei der Entwicklung und Fehlersuche in komplexen Systemen unterstützen soll. Dazu bringt es viele nützliche Features wie Logging, Tracing und ein Dashboard mit, das ohne Konfiguration der Entwickler:innen funktionieren. Bei Bedarf kann das Verhalten dieser Tools aber auch an die eigenen Anforderungen angepasst werden. Zusätzlich kann gemeinsam mit dem Azure Developer CLI ein Aspire-Projekt mit wenigen Befehlen in die Cloud deployt werden.

Obwohl .NET Aspire erst vor einigen Monaten erschienen ist, ist es bereits so umfangreich, dass dieser Artikel nur einen kleinen Einblick in den vollen Funktionsumfang geben konnte. Die Zukunft sieht ebenfalls positiv aus, da laufend neue Features hinzukommen und Microsoft seinen Fokus auf das Projekt gelegt hat.


Links & Literatur

[1] https://devblogs.microsoft.com/dotnet/introducing-project-tye/

[2] https://github.com/dotnet/aspire-samples

[3] https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd

[4] https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep

[5] https://learn.microsoft.com/en-us/dotnet/core/docker/publish-as-container

[6] https://masstransit.io

The post Meistern Sie .NET Aspire: Effektive Tools für Entwickler appeared first on BASTA!.

]]>
Technische Umsetzung von Layouts in Blazor https://basta.net/blog/technische-umsetzung-von-layouts-in-blazor/ Mon, 09 Dec 2024 20:55:29 +0000 https://basta.net/?p=106927 Layouts spielen eine zentrale Rolle für die Benutzererfahrung und Usability moderner Webanwendungen. Mit Blazor lassen sich responsive und wiederverwendbare Layouts einfach erstellen, die sowohl die Performance verbessern als auch die Benutzerführung optimieren. Dieser Artikel zeigt, wie Sie flexible Layoutoptionen in Blazor nutzen, um eine konsistente Struktur und eine bessere Zugänglichkeit für Ihre Web-App zu erreichen.

The post Technische Umsetzung von Layouts in Blazor appeared first on BASTA!.

]]>
In modernen Web-Apps spielen Layouts eine entscheidende Rolle für die Benutzererfahrung und -interaktion. Ein durchdachtes Layout sorgt nicht nur für eine ansprechende Darstellung, sondern beeinflusst auch die Usability, Performance und letztlich den Erfolg der Anwendung. Folgende Aspekte unterstreichen die Bedeutung von Layouts:

  1. Benutzerführung und Intuitivität: Ein gut strukturiertes Layout leitet Benutzer:innen intuitiv durch die App. Durch die gezielte Platzierung von Navigationselementen, Buttons und Inhalten können Benutzer:innen mühelos und ohne Verwirrung navigieren, was die Verweildauer und Zufriedenheit steigert.
  2. Responsivität und Zugänglichkeit: Web-Apps müssen auf verschiedenen Geräten und Bildschirmgrößen funktionieren. Ein responsives Layout, das sich dynamisch anpasst, stellt sicher, dass die App sowohl auf mobilen Geräten als auch auf großen Bildschirmen gut aussieht und einfach zu bedienen ist. Zudem trägt ein zugängliches Layout dazu bei, dass auch Nutzende mit Einschränkungen ein uneingeschränktes Erlebnis genießen können.
  3. Leistung und Ladezeiten: Effiziente Layoutstrukturen sind optimiert für schnelle Ladezeiten und Performance. Eine schlanke, gut strukturierte Layoutarchitektur reduziert den Ressourcenverbrauch und beschleunigt das Rendering – das sind wichtige Faktoren für die Benutzerzufriedenheit, insbesondere in mobilen Umgebungen.
  4. Konsistenz und Wiederverwendbarkeit: Ein konsistentes Layoutdesign fördert die Wiedererkennung und erleichtert die Entwicklung. In Blazor können Layoutkomponenten modular aufgebaut und wiederverwendet werden, was nicht nur die Entwicklungszeit verkürzt, sondern auch für ein einheitliches Erscheinungsbild in der gesamten Anwendung sorgt.
  5. Flexibilität und Skalierbarkeit: Die Anforderungen an eine Web-App ändern sich oft im Laufe der Zeit. Ein flexibles Layoutdesign erleichtert es, Änderungen oder Erweiterungen vorzunehmen, ohne die gesamte Struktur zu überarbeiten. Durch den modularen Aufbau in Blazor können Layouts leicht angepasst und skaliert werden, um neue Funktionen oder Inhalte zu integrieren.

In diesem Artikel geht es um die technische Umsetzung von Layouts in Web-Apps mit Hilfe des Blazor Frameworks.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Layoutoptionen

Die verschiedenen Layoutoptionen bieten Nutzer:innen eine flexible und effiziente Möglichkeit, die Struktur und Benutzerführung von Webapplikationen gezielt zu gestalten. Eine erste Übersicht ist in Tabelle 1 zu sehen. 

Layout-Option Beschreibung Nutzen für Anwender:innen
Master-Detail-Layout Hauptbereich (Master) zeigt eine Liste oder Navigation; Detailbereich zeigt Informationen basierend auf Auswahl Ermöglicht die schnelle Navigation und eine strukturierte Darstellung von Details; ideal für Datenmanagement
Sidebar-Layout Feste Seitenleiste für Navigationselemente oder Filter; dynamischer Hauptinhalt Bietet eine klare Orientierung und schnellen Zugriff auf häufig verwendete Funktionen und Ansichten
Tabbed Layout Inhalte sind in Registerkarten organisiert, die durch Klicken gewechselt werden können Übersichtliche und direkte Zugänglichkeit zu gleichwertigen Inhaltsbereichen, einfache Navigation
Grid-Layout Seite wird in ein Raster mit Zeilen und Spalten aufgeteilt; ideal für Karten oder Kacheln Erleichtert das visuelle Scannen und das schnelle Durchsuchen von Inhalten; ideal für Kataloge und Listen
Fullscreen-Layout Nutzt die gesamte Bildschirmfläche; häufig für visuell intensive Inhalte wie Dashboards oder Medienanzeige Bietet eine immersive Nutzererfahrung, bei der alle wichtigen Informationen auf einen Blick sichtbar sind
Responsive Layout Dynamisch anpassbares Layout mit Breakpoints für Desktop, Tablet und Mobilgerät Maximiert die Benutzerfreundlichkeit durch optimierte Darstellung auf unterschiedlichen Geräten.

Tabelle 1: Layoutoptionen in der Web-App

Layouts in Blazor

Layouts ermöglichen es, konsistente Strukturen für Seiten zu definieren und gemeinsame Elemente wie Navigation, Kopf- und Fußzeilen zentral zu verwalten. Ein Layout in Blazor ist eine spezielle Razor-Komponente, die als Vorlage für andere Komponenten dient. Diese Layoutkomponenten erben von der Klasse LayoutComponentBase und definieren eine Body-Eigenschaft, die einen Platzhalter für den Inhalt der untergeordneten Komponenten darstellt. Als erstes analysieren wir dazu den Quellcode einer Blazor-App (Web-App), die wir auf der Basis des gleichnamigen Templates generieren. Verantwortlich ist die Datei (Komponente) MainLayout.razor, die man im Ordner Components/Layout findet (Listing 1, Abb. 1).

@inherits LayoutComponentBase
<div class="page">
  <div class="sidebar">
    <NavMenu />
  </div>

  <main>
    <div class="top-row px-4">
      <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
    </div>

    <article class="content px-4">
      @Body
    </article>
  </main>
</div>

<div id="blazor-error-ui">
  An unhandled error has occurred.
  <a href="" class="reload">Reload</a>
  <a class="dismiss">🗙</a>
</div>


Abb. 1: Layout (Seitennavigation mit Menü) in einer Blazor-App (Template)

Sehen wir uns diese Komponente zunächst etwas genauer an:

  • Vererbung von LayoutComponentBase: Die Layoutkomponente erbt von der Basisklasse LayoutComponentBase, die in Blazor speziell für Layouts verwendet wird. Diese Basisklasse stellt die Body-Eigenschaft bereit, die als Platzhalter für den Inhalt der untergeordneten Komponente dient, die dieses Layout verwendet.
  • HTML-Grundstruktur: Das Haupt-<div> mit der CSS-Klasse “page” umfasst das Layout und bildet den äußeren Rahmen der Benutzeroberfläche.
  • Sidebar: Der Bereich <div class=”sidebar”> enthält die Komponente <NavMenu />, die typischerweise als Navigationsmenü fungiert. Die Sidebar bleibt innerhalb des Layouts fest verankert und bietet Nutzer:innen eine konsistente Navigation durch die Applikation.
  • Hauptinhalt (main): Innerhalb des <main>-Elements gibt es zwei zentrale Bereiche: Das Element mit der Klassenzuweisung “top-row” am Anfang des Hauptinhaltsbereichs enthält einen Link zur ASP.NET-Core-Dokumentation und wird durch eine CSS-Klasse (“px-4”) „gepolstert“, was den Inhalt innen um vier Einheiten Abstand versetzt. Dieser Bereich könnte für weitere Links oder Informationen genutzt werden. Der Artikelbereich mit der Klasse “content px-4” enthält die @Body-Direktive. Diese Direktive stellt den Bereich dar, in dem der spezifische Inhalt der untergeordneten Komponente gerendert wird, die das Layout verwendet. Hier werden also die Hauptinhalte der jeweiligen Seite angezeigt, die von der Sidebar unabhängig sind.
  • Fehleranzeige (blazor-error-ui): Am Ende des Layouts befindet sich ein <div> mit der ID “blazor-error-ui”, das nur sichtbar wird, wenn ein unerwarteter Fehler auftritt. Dieser Bereich enthält: 
    • eine Fehlermeldung („An unhandled error has occurred.“) 
    • einen Reload-Link, der die Seite neu lädt
    • ein Schließen-Symbol (🗙) zum Ausblenden der Fehlermeldung

Dieses Layout bietet eine klare Struktur und ermöglicht eine flexible Gestaltung, da verschiedene Seiten das gleiche Layout nutzen können. Die Navigation und die Fehlerverarbeitung werden über die gesamte Anwendung hinweg vereinheitlicht.

SIE WOLLEN MEHR INPUT ZUM THEMA WEB DEVELOPMENT?

Entdecken Sie den BASTA! Web Development Track

Viele Blazor-Entwickler:innen sind höchstwahrscheinlich über die Programmiersprache C# zur Webentwicklung mit Blazor gestoßen. Daher lohnt sich ein vertiefender Blick auf die Möglichkeiten des semantischen HTML, denn es bietet einen klaren Vorteil für die Strukturierung und Benutzerfreundlichkeit von Webanwendungen und wir nutzen diese HTML-Elemente in Blazor-Anwendungen. Webprofis können diesen Abschnitt getrost überspringen.

Exkurs: Semantisches HTML – Struktur und Bedeutung

In HTML5 ist die semantische Strukturierung ein wichtiger Aspekt, um die Benutzerfreundlichkeit, Zugänglichkeit und Übersichtlichkeit von Webseiten zu verbessern. Semantisches HTML bedeutet, dass Inhalte so strukturiert und mit Tags ausgezeichnet werden, dass ihre Funktion und Bedeutung im Kontext der Webseite klar erkennbar sind. Dies hilft nicht nur Entwickler:innen und Nutzer:innen, sondern auch Suchmaschinen und Screenreadern, den Inhalt korrekt zu interpretieren. Ein HTML-Dokument sollte nicht lediglich nacheinander mit Inhalten gefüllt werden, sondern eine klare Struktur und Hierarchie aufweisen. Mit den HTML-Tags lassen sich Webseiten logisch gliedern und erleichtern es Nutzer:innen, sich auf der Seite zurechtzufinden. Die wichtigsten HTML-Tags zur Strukturierung sind die folgenden:

  • <body>: Der <body>-Tag stellt den darstellbaren Inhaltsbereich des HTML-Dokuments dar. Alle Inhalte der Seite, die sichtbar sein sollen, werden innerhalb dieses Tags organisiert.
  • <nav>: Das <nav>-Element dient der Kennzeichnung von Navigationselementen einer Seite. Es enthält oft Links und Menüs zur Hauptnavigation der Seite. Da Seiten mehrere Navigationselemente enthalten können, sollte <nav> gezielt für die Hauptnavigation eingesetzt werden.
  • <section>: Mit <section> können Seiten in zusammenhängende inhaltliche Abschnitte aufgeteilt werden. Ein typisches Beispiel sind Abschnitte für „News“ oder „Services“. Verschachtelte Sections sind möglich und helfen, die Gliederung der Seite zu verfeinern.
  • <article>: Das <article>-Tag dient der Kennzeichnung in sich geschlossener Inhalte, die auch eigenständig bestehen könnten, wie Blogartikel oder Kommentare. Oft werden <section>- und <article>-Tags kombiniert, um die Seite hierarchisch zu gliedern.
  • <aside>: Das <aside>-Tag wird verwendet, um zusätzliche Informationen wie Seitenleisten oder ergänzende Inhalte anzuzeigen. Diese Inhalte sind oft mit dem Hauptinhalt verwandt, aber nicht zwingend erforderlich.
  • Überschriftenelemente (<h1> bis <h6>): HTML unterstützt die semantische Gliederung von Überschriften von <h1> bis <h6>. Innerhalb der Strukturtags wie <section>, <article>, <aside> und <nav> beginnt die Zählung der Überschriftenebenen neu.
  • <header>: Das <header>-Tag kennzeichnet den Kopfbereich einer Seite oder eines Abschnitts und kann Überschriften, Logos oder Navigationslinks enthalten.
  • <footer>: Im <footer>-Tag werden Informationen für den Fußbereich der Seite gesammelt. Typische Inhalte sind Urheberrechtshinweise und Kontaktinformationen.
  • <address>: Das <address>-Tag gibt Kontaktinformationen zum Autor des Inhalts an und wird oft im <footer> verwendet. In Browsern wird es meist kursiv dargestellt.
  • <main>: Mit <main> wird der zentrale Inhalt der Seite ausgezeichnet. Dieses Element sollte nur einmal pro Dokument vorkommen und ersetzt <div id=”main”>, um den Hauptinhalt der Seite semantisch zu kennzeichnen.

Die Verwendung dieser HTML5-Elemente zur Strukturierung verleiht Webseiten eine klare und bedeutungsgerechte Gliederung, was mehrere Vorteile mit sich bringt: Screenreader und andere Assistenztechnologien können die Struktur besser interpretieren, was sehbehinderten Nutzer:innen zugutekommt. Suchmaschinen verstehen durch semantische Tags die Bedeutung und Struktur des Inhalts besser, was die Indexierung und Auffindbarkeit der Seite verbessern kann. Klare Strukturen erleichtern die Wartung und Erweiterung des Codes, da die Bedeutung der einzelnen Bereiche sofort ersichtlich ist. Ein Beispiel für eine HTML5-Struktur mit semantischen Tags finden Sie in Listing 2 und Abb. 2.

<!DOCTYPE html>
<head>
  …
  <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
  <header>
    <h1>Die Überschrift</h1>
  </header>
  <nav>
    <a href="#">Link</a>
    <a href="#">Link</a>
    <a href="#">Link</a>
  </nav>
  <main>
    <div class="column side">
      <p>Der linke Bereich der Seite</p>
      <p>
        Lorem ipsum ….
      </p>
    </div>
    <div class="column middle">
      <p>Der mittlere Bereich der Seite</p>
      <p>
        Lorem ipsum ….
      </p>
    </div>
    <div class="column side">
      <p>Der rechte Bereich der Seite</p>
      <p>
        Lorem ipsum ….
      </p>
    </div>
  </main>
  <footer>
    Hier kommt zum Beispiel das Impressum.
  </footer>
</body>
</html>


Abb. 2: Typisches Layout einer Web-App, realisiert mit semantischem HTML

Wenn Sie diese Elemente in einem HTML5-Dokument verwenden, können Sie die inhaltliche Struktur des Dokumentes festlegen. Beachten Sie jedoch zwingend: Der Browser wird das eine oder andere Element bereits in irgendeiner Form angepasst darstellen, zum Beispiel werden Inhalte in <address> kursiv ausgegeben. Die eigentliche Festlegung des Layouts und des Designs erfolgt jedoch in CSS, in dem man dann die Tagelemente konkret anspricht und diese mit dem entsprechenden CSS-Styling und Design versieht. Kommen wir jetzt wieder zurück zu Blazor.

Nav-Komponente

Innerhalb der Layoutkomponente haben wir die Navigation (<Nav/>-Komponente) integriert, die im Blazor-Kontext des Templates als Sidebar oder Top-Navigationsleiste dient (Listing 3). Diese Navigationsleiste ermöglicht eine strukturierte und konsistente Navigation durch die Hauptbereiche der Anwendung.

Schauen wir uns den Aufbau und das Zusammenspiel der verschiedenen Teile der Navigation an. Der Navigationsbereich enthält mehrere Elemente, die zusammen eine responsive und funktionale Sidebar erzeugen:

<div class="top-row ps-3 navbar navbar-dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="proxy.php?url=">BlazorApp1</a>
  </div>
</div>

Dieser Abschnitt definiert den obersten Bereich der Navigation. Er enthält die “navbar-brand”-CSS-Klasse, die als Logo oder Markennamen dient (“BlazorApp1”). Das “navbar”– und “navbar-dark”-Klassenset bringt den Stil der Bootstrap-Navigationsleiste ein und sorgt für ein abgedunkeltes Farbschema. Mit folgendem Beispiel wird ein checkbox-Element definiert, das als Hamburgermenü fungiert:

<input type="checkbox" title="Navigation menu" class="navbar-toggler" />

Beim Anklicken, beispielsweise auf Mobilgeräten, wird das Menü ein- und ausgeblendet. Das title-Attribut verleiht dem Toggle-Button eine beschreibende Bezeichnung, die für Screenreader und eine universelle Zugänglichkeit wichtig ist. Kommen wir zum Nav-Container (scrollbarer Bereich):

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
  <nav class="flex-column">

Der Bereich nav-scrollable ist ein Container für die Navigation, der vertikales Scrollen unterstützt. Durch das onclick-Event im div wird das Menü ein- oder ausgeblendet, wenn Benutzer.innen auf den Bereich klicken. Das ermöglicht eine einfache Bedienung der Sidebar. Die einzelnen Navigationselemente werden durch <NavLink>-Komponenten definiert, die von Blazor bereitgestellt werden. Jedes <NavLink> ist mit einer Route der Anwendung verknüpft und wechselt den Zustand basierend auf dem aktuellen URL. Der Code:

<div class="nav-item px-3">
  <NavLink class="nav-link" href="proxy.php?url=" Match="NavLinkMatch.All">
    <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
  </NavLink>
</div>

Die Klasse “nav-item” sorgt für einheitliche Abstände, während die Klasse “px-3” für ein horizontal gepolstertes Layout sorgt. Die “nav-link”-Klasse ist eine Standardklasse für Links in Bootstrap und wird hier verwendet, um den Navigationselementen ein einheitliches Styling zu verleihen. Jedes <NavLink>-Element enthält ein span-Element mit einer Bootstrap-Icon-Klasse (“bi bi-house-door-fill-nav-menu”). Das ermöglicht die Darstellung eines Icons neben dem Text. Das aria-hidden=”true”-Attribut stellt sicher, dass das Icon für Screenreader ignoriert wird, da es nur eine dekorative Funktion hat. Kommen wir als letztes zu den Seitenverlinkungen (href-Attribute). Die einzelnen href-Attribute definieren die Routen in der Blazor-Anwendung, zum Beispiel: `/` (Startseite), `/counter` (Counter-Seite) oder `/weather` (Weather-Seite). Jedes dieser Elemente wird durch ein Icon und eine Beschriftung ergänzt, um die Navigation für Benutzer:innen intuitiv zu gestalten. Die Hauptvorteile dieses Vorgehens sind:

  • Responsivität: Die Kombination der Klassen “navbar-toggler” und “nav-scrollable” sorgt dafür, dass die Sidebar auf verschiedenen Geräten unterschiedlich dargestellt wird. Auf großen Bildschirmen bleibt die Sidebar sichtbar, während sie auf kleineren Bildschirmen durch das Hamburgermenü ein- und ausgeblendet werden kann.
  • Interaktive Navigation mit <NavLink/>-Komponente: Blazor stellt sicher, dass die <NavLink/>-Komponente automatisch den Zustand „aktiv“ annimmt, wenn Benutzer:innen sich auf der entsprechenden Seite befinden. Das bietet visuelles Feedback und vereinfacht die Orientierung innerhalb der Anwendung.
  • Trennung von Inhalt und Layout: Die Icons, Links und Navigationsstruktur sind klar in HTML5-CSS-Klassen organisiert, was die Benutzeroberfläche einfach anpassbar macht. CSS kann verwendet werden, um das Layout und Styling der Sidebar weiter zu individualisieren.

Zusammengefasst bietet dieses Layout eine gut strukturierte Navigation, die sich dynamisch anpasst, nutzerfreundlich ist und eine konsistente Navigation über die gesamte Blazor-App hinweg ermöglicht.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

CSS-Definition des Layouts

Das Styling der Layoutkomponente erfolgt durch eine zugeordnete CSS-Datei, in der die visuelle Gestaltung der Navigation und des allgemeinen Layouts definiert wird. Diese CSS-Datei enthält die Stile für die verschiedenen Bereiche der Layoutkomponente, wie die Sidebar, das Hamburgermenü, den Hauptinhalt und den Footer. In der App auf der Basis des allgemeinen Templates wird die Definition in der Datei MainLayout.razor.css (Listing 4) vorgenommen.

.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

.sidebar {
  background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
…
#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

Zunächst erfolgt die Definition des grundlegenden Layoutcontainers:

.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

Diese Klasse bildet den Hauptcontainer des Layouts und nutzt das Flexbox-Element aus CSS als Layoutmethode. Durch flex-direction: column werden die Elemente in .page vertikal angeordnet. Das position: relative stellt sicher, dass die Elemente innerhalb des Containers relativ zu .page positioniert werden können. Durch

main {
  flex: 1;
}

wird der Hauptinhalt so gestaltet, dass er den verbleibenden verfügbaren Platz im Container .page einnimmt. Dadurch passt sich der main-Bereich dynamisch an die Größe des Containers an und lässt Platz für andere fixierte oder flexible Elemente wie die Sidebar und den Header.

.sidebar {
  background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

Die Sidebar erhält durch einen linearen Farbverlauf (Gradient) eine attraktive Hintergrundfarbe, die von einem dunklen Blau (rgb(5, 39, 103)) oben in ein tiefes Violett (#3a0647) unten übergeht. Dieses visuelle Design hilft, die Sidebar als festen Bereich vom restlichen Inhalt abzuheben und eine ansprechende, moderne Optik zu erzeugen. Als letztes wird eine mögliche Fehlereinblendung der Blazor-App mit einem Layout versehen (Listing 5).

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}
  • #blazor-error-ui: Dieser Bereich wird als Fehleranzeige verwendet, die standardmäßig ausgeblendet ist (display: none). Wenn ein Fehler auftritt, wird #blazor-error-ui sichtbar gemacht, sodass Nutzer:innen auf das Problem hingewiesen werden.
  • Stil: Mittels background: lightyellow wird die hellgelbe Hintergrundfarbe definiert, die visuell eine Warnung oder einen Fehler symbolisiert; mit position: fixed platziert man das Element fest am unteren Rand des Bildschirms, unabhängig vom Seiteninhalt, sodass es stets sichtbar bleibt, wenn es eingeblendet ist; z-index: 1000 stellt sicher, dass die Fehleranzeige über anderen Inhalten liegt und mittels padding und box-shadow werden die Innenabstände und der Schatten der Fehleranzeige definiert. Es entsteht ein sauberes, abgesetztes Design, das ins Auge fällt und benutzerfreundlich bleibt.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Diese CSS-Definitionen (hier nur in Auszügen dargestellt und erläutert) legen die Grundstruktur und das Design der Blazor-Anwendung fest. Durch Flexbox und responsives Design wird ein flexibles Layout gewährleistet. Die Sidebar hebt sich durch ihren Farbverlauf ab, und die Fehleranzeige wird bei Bedarf hervorgehoben dargestellt. Studieren Sie die vollständige CSS-Datei in einer neuen Blazor-App, die sie auf einfache Art und Weise mit Hilfe des Assistenten in Visual Studio generieren lassen können.

Nutzung des Layouts in einer Komponente

In Blazor können Layouts auf verschiedene Arten auf Razor-Komponenten angewendet werden. Es bestehen die folgenden Optionen, um ein Layout für eine oder mehrere Komponenten festzulegen.

Nutzung eines Layouts durch die @layout-Direktive: Die einfachste Möglichkeit, einer Razor-Komponente ein Layout zuzuweisen, ist die Verwendung der @layout-Direktive direkt in der Komponente. Diese Methode ermöglicht eine individuelle Layoutzuweisung, die spezifisch für eine einzelne Komponente gilt. Ein einfaches Beispiel:

@page "/about"
@layout MainLayout
<h2>Über uns</h2>
<p>Willkommen auf der Über-uns-Seite.</p>

In diesem Beispiel wird der Komponente das Layout MainLayout zugewiesen, indem die @layout-Direktive verwendet wird. Der Inhalt der Razor-Komponente (hier: „Über uns“ und „Willkommen auf der Über-uns-Seite“) wird an der Stelle von @Body in MainLayout gerendert.

Nutzung eines Standard-Layouts für die gesamte Anwendung: Um ein Standardlayout für alle Razor-Komponenten festzulegen, können Sie das Layout in der <RouteView/>-Komponente der <Router/>-Komponente festlegen. Verwenden Sie den DefaultLayout-Parameter, um den Layouttyp festzulegen, üblicherweise in der Datei Routes.razor:

<Router AppAssembly="typeof(Program).Assembly">
  <Found Context="routeData">
    <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
    <FocusOnNavigate RouteData="routeData" Selector="h1" />
  </Found>
</Router>

In diesem Beispiel wird MainLayout als Standardlayout festgelegt. Dadurch wird MainLayout automatisch auf alle Razor-Komponenten angewendet, die keine spezifische @layout-Direktive haben.

Nutzung eines Layouts für mehrere Komponenten eines Ordners: Für Razor-Komponenten innerhalb eines bestimmten Ordners kann ein Layout durch eine _Imports.razor-Datei festgelegt werden. Diese Datei wird auf alle Razor-Komponenten im gleichen Ordner und in dessen Unterordnern angewendet, sodass das Layout automatisch übernommen wird. Ein weiteres Beispiel: In der Datei _Imports.razor notieren wir folgende Anweisung:

@layout MainLayout

In diesem Fall wird MainLayout als Layout für alle Razor-Komponenten innerhalb des gleichen Ordners wie die _Imports.razor-Datei und in den Unterordnern verwendet. Diese Methode ist besonders nützlich, wenn Sie unterschiedliche Layouts für verschiedene Teile Ihrer Anwendung benötigen, beispielsweise ein Adminlayout für alle Komponenten im Admin-Ordner und ein Standardlayout für andere Bereiche.

Fassen wir zusammen: Die @layout-Direktive dient der Zuweisung eines Layouts in einer spezifischen Komponente; das Standardlayout definiert das Layout für die gesamte Anwendung und mittels der _Imports.razor-Datei legen wir das Layout für mehrere Komponenten in einem bestimmten Ordner fest. Diese verschiedenen Methoden ermöglichen es Ihnen, Layouts flexibel und übersichtlich in einer Blazor-Anwendung anzuwenden und zu organisieren.

Layoutmodifikation – klassisches Menü

Wenn Sie das Layout einer Blazor-Anwendung anpassen möchten, dann müssen Sie die Layoutkomponente modifizieren und beispielsweise eine Menüstruktur hinzufügen. Hier wird der Code so angepasst, dass eine Navigationsleiste erstellt wird, die als Hauptmenü dient. Die Modifikation des Standardlayouts erfolgt durch die Definition von Header, Navigation und Inhaltsbereichen. Der Quellcode in Listing 6 beschreibt eine typische Menüstruktur.

@inherits LayoutComponentBase
Layout1
<div class="header-nav">
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">MyCompany</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
        <div class="navbar-nav">
          <NavLink class="nav-link active" href="/" Match="NavLinkMatch.All">Home</NavLink>
          <NavLink href="/weather" class="nav-link">Wetter</NavLink>
          <NavLink href="/counter" class="nav-link">Counter</NavLink>
        </div>
      </div>
    </div>
  </nav>
</div>
<div class="content px-4">
  @Body
</div>

Es folgt eine kompakte Erklärung der Komponenten und Modifikationen gegenüber der Ausgangssituation:

  • Vererbung von LayoutComponentBase: Durch @inherits LayoutComponentBase wird festgelegt, dass die Komponente als Layout verwendet werden kann. Die @Body-Direktive wird verwendet, um den Inhalt der untergeordneten Komponenten an der gewünschten Stelle im Layout einzufügen.
  • Header und Navigation: Der Header enthält die Hauptnavigation in Form einer <navbar/>-Komponente. Die Klasse “navbar navbar-expand-lg” sorgt dafür, dass die Navigationsleiste auf großen Bildschirmen horizontal dargestellt wird und auf kleineren Bildschirmen als zusammenklappbares (responsive) Menü erscheint. Die “bg-body-tertiary”-Klasse definiert den Hintergrundstil, und “container-fluid” sorgt dafür, dass die Inhalte der Navigation die volle Breite des Containers einnehmen.
  • Markenname (MyCompany): Das Element <a class=”navbar-brand” href=”#”>MyCompany</a>` definiert den Markennamen oder das Logo in der Navigationsleiste.
  • Hamburgermenü (für mobile Geräte): Der Button mit der Klasse “navbar-toggler” sorgt dafür, dass die Navigation auf kleineren Bildschirmen ein- und ausgeklappt werden kann.
  • Navigationselemente (NavLink): Die “navbar-nav”-Klasse gruppiert alle Navigationslinks in der Navbar. Die <NavLink/>-Komponenten erzeugen Links, die je nach Route aktiviert werden. NavLink mit href=”/” navigiert zur Startseite und hat Match=”NavLinkMatch.All”, sodass es als aktiv markiert wird, wenn sich Benutzer:innen auf der Startseite befinden. Weitere Navigationselemente, wie /weather für die Wetterseite und /counter für die Counter-Seite, werden hinzugefügt, um eine einfache Benutzerführung zu gewährleisten.
  • Inhaltsbereich (@Body): Der @Body-Platzhalter im <div class=”content px-4″>-Container definiert den Bereich, in dem die Inhalte der jeweiligen Seite gerendert werden. Die Klasse “px-4” fügt horizontale Innenabstände hinzu, um den Inhalt nicht direkt am Rand anzuzeigen und die Lesbarkeit zu verbessern.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Mit dieser Anpassung wird das Standardlayout um eine intuitive Menüstruktur erweitert (Abb. 3). Die Navigation ist klar strukturiert und passt sich automatisch an die Bildschirmgröße an, was das Layout flexibel und benutzerfreundlich macht.


Abb. 3: Layout einer Blazor-App mittels Menünavigation

Layoutmodifikation – Tabnavigation

Wenn Sie das Layout einer Blazor-Anwendung zu einer Tabnavigation anpassen möchten, dann müssen Sie das Standardlayout modifizieren und eine Struktur erstellen, die auf einer Navigation mit Tabs basiert. Eine solche Navigation zeigt die wichtigsten Seiten der Anwendung in Form von Registerkarten (Tabs) an, die den Benutzer:innen eine einfache und schnelle Navigation zwischen den Seiten ermöglichen. Das Quellcodebeispiel finden Sie in Listing 7 und das Ergebnis in Abbildung 4.

@inherits LayoutComponentBase
Layout2
<div class="header-nav">
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">MyCompany</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <ul class="nav nav-tabs">
        <li class="nav-item">
          <NavLink class="nav-link active" href="/" Match="NavLinkMatch.All">Home</NavLink>
        </li>
        <li class="nav-item">
          <NavLink href="/weather" class="nav-link">Wetter</NavLink>
        </li>
        <li class="nav-item">
          <NavLink href="/counter" class="nav-link">Counter</NavLink>
        </li>
      </ul>
    </div>
  </nav>
</div>
<div class="content px-4">
  @Body
</div>


Abb. 4: Layout einer Blazor-App mittels Tabnavigation

Die Anpassungen im Quellcode sind ähnlich, wie bei dem eben beschriebenen Beispiel. Um die Tabnavigation zu erreichen sind die entsprechenden Klassen aus Bootstrap für die Tabs zu nutzen.

Verschachtelte Layouts

In Blazor können Layouts ineinander geschachtelt werden, um eine hierarchische Struktur für die Benutzeroberfläche zu schaffen. Das bedeutet, dass ein Layout in einem anderen Layout eingebettet wird. Das ist besonders nützlich, wenn unterschiedliche Bereiche der Anwendung konsistente Elemente haben, aber einige Elemente eine spezifische Struktur benötigen. Dazu ein einfaches Beispiel: Stellen wir uns eine Anwendung vor, die ein Hauptlayout mit einer globalen Navigation und ein weiteres untergeordnetes Layout für einen bestimmten Bereich (z. B. Adminbereich) verwendet. Der Adminbereich hat eine zusätzliche Navigation und spezielle Formatierungen, die sich vom Hauptlayout abheben. Im Hauptlayout wird die globale Navigation und der allgemeine Inhaltsbereich definiert. Dieses Layout verwendet MainLayout als übergeordnetes Layout und fügt zusätzliche Strukturen, wie eine adminspezifische Navigation, hinzu. Nun kann eine Seite das Adminlayout als Layout verwenden. Durch diese Struktur wird eine übersichtliche und flexible Gestaltung möglich, die Bereiche mit spezifischen Anforderungen konsistent in das Hauptlayout integriert. Ein Beispiel finden Sie unter [1].

Fazit

Layouts spielen in Blazor-Anwendungen eine entscheidende Rolle, da sie die Grundlage für eine konsistente und benutzerfreundliche Benutzeroberfläche bilden. Sie ermöglichen eine klare Strukturierung der Seiteninhalte, fördern die Wiederverwendbarkeit und erleichtern die Wartung der Anwendung. Mit der Verwendung von Layouts in Blazor lassen sich Komponenten wie Header, Navigation und Footer zentral verwalten und für mehrere Seiten anwenden. Durch die verschiedenen Layoutoptionen wie Master-Detail, Sidebar, Tabnavigation und Fullscreen können Entwickler:innen das Layout flexibel an die Anforderungen der Anwendung und die Bedürfnisse der Nutzer:innen anpassen. Die Möglichkeit, Layouts ineinander zu verschachteln, bietet zusätzlichen Spielraum für komplexere Anwendungen, indem sie hierarchische Strukturen erlaubt. Zusammengefasst sind Layouts ein unverzichtbares Werkzeug für jede Blazor-Anwendung, da sie sowohl die technische Effizienz als auch die Benutzerfreundlichkeit erheblich steigern.

 

Web: https://larinet.com, https://www.tech-punkt.com

Links & Literatur

[1] https://learn.microsoft.com/de-de/aspnet/core/blazor/components/layouts?view=aspnetcore-8.0

The post Technische Umsetzung von Layouts in Blazor appeared first on BASTA!.

]]>
Der .NET-Upgrade-Assistent: Alles, was Sie zur Anwendungsmigration wissen müssen https://basta.net/blog/dotnet-upgrade-assistant-migration-guide/ Fri, 22 Nov 2024 14:16:44 +0000 https://basta.net/?p=106850 Der .NET-Upgrade-Assistent ist ein Tool, das von Microsoft entwickelt wurde, um Entwickler:innen die Migration von Anwendungen, die auf dem .NET Framework basieren, zu erleichtern. Mit dem Fokus auf Modernisierung und Zukunftssicherheit zielt das Tool darauf ab, den oft komplexen und zeitaufwändigen Prozess der Migration von .NET-Anwendungen zu vereinfachen und zu automatisieren.

The post Der .NET-Upgrade-Assistent: Alles, was Sie zur Anwendungsmigration wissen müssen appeared first on BASTA!.

]]>
Der .NET-Upgrade-Assistent führt uns durch eine Reihe von klar definierten Schritten, um bestehende Anwendungen schrittweise zu aktualisieren, wodurch das Risiko von Fehlern minimiert und die Entwicklungszeit verkürzt wird. Die Zielgruppe des .NET-Upgrade-Assistenten sind in erster Linie Entwickler:innen und Teams, die bestehende Anwendungen auf neuere und leistungsfähigere .NET-Versionen portieren möchten.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Unterstützte Projekttypen und Upgrade-Pfade

Der .NET-Upgrade-Assistent unterstützt eine Vielzahl von Projekttypen, die für die Migration auf moderne .NET-Versionen geeignet sind. Zu den unterstützten Projekten gehören [1]:

  • ASP.NET
  • Azure-Funktionen
  • Windows Presentation Foundation (WPF) und Windows Forms
  • Klassenbibliotheken und Konsolen-Apps
  • .NET Native UWP
  • Xamarin.Forms und .NET MAUI

Die unterstützten Upgrade-Pfade umfassen:

  • .NET Framework zu .NET
  • .NET Core zu .NET
  • UWP zu WinUI 3
  • Ältere .NET-Versionen auf die neueste Version
  • Azure Functions v1-v3 auf v4 (isoliert)
  • Xamarin.Forms zu .NET MAUI (Hinweis: für XAML-Dateitransformationen wird nur das Upgrade von Namespaces unterstützt)

Dabei ist aus Praxiserfahrungen festzustellen, dass diese Projekttypen und die zugehörigen Upgrade-Pfade mit einem unterschiedlichen Grad, einer verschiedenen Detaillierung und damit letztendlich auch mit wechselndem Erfolg unterstützt werden.

Installation

Die Installation des .NET-Upgrade-Assistenten kann auf zwei Arten erfolgen: als Visual-Studio-Erweiterung (Extension) oder als globales .NET-Tool. Die Extension finden Sie unter der Bezeichnung Upgrade Assistant. Der Assistent kann direkt aus Visual Studio über das Kontextmenü im Projektmappen-Explorer aufgerufen werden. Um es als globales .NET-Tool zu installieren sind die folgenden Schritte notwendig:

  • NET SDK überprüfen: Stellen Sie sicher, dass das .NET SDK auf Ihrem Computer installiert ist. Sie können das überprüfen, indem Sie in der Konsole den Befehl dotnet –version ausführen.
  • Upgrade Assistant installieren: Öffnen Sie eine Konsole (Command Prompt, PowerShell) und führen Sie den folgenden Befehl aus dotnet tool install -g upgrade-assistant.
  • Update: Da der .NET-Upgrade-Assistent als .NET-Tool installiert ist, kann er mittels dotnet tool update -g upgrade-assistant aktualisiert werden.
  • Migrieren: Um ein Projekt zu aktualisieren, können Sie den Upgrade-Assistenten wie folgt verwenden: upgrade-assistant upgrade <Projektdatei oder Lösung>.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Arbeitsweise und Tests

Wir demonstrieren das Handling mit dem .NET-Upgrade-Assistenten anhand einiger Projekte. Unser erstes Projekt ist eine ältere Windows-Forms-Applikation („Bibliothek“) auf der Basis des .NET Framework 4.7.2. Diese enthält lediglich ein Projekt und keine Abhängigkeiten von externen Bibliotheken. Die Benutzeroberfläche enthält nur wenige Komponenten, Herzstück ist ein DataGrid. Die Businesslogik ist im Code-Behind implementiert. Das Ziel ist eine Migration nach .NET 8, um die Applikation weiterentwickeln zu können. Starten wir mit einer ersten Analyse: Wir öffnen das Projekt in der aktuellen Version von Visual Studio und starten auf Ebene der Projektmappe (Kontextmenü im Projektmappen-Explorer) den .NET-Upgrade-Assistenten (Abb. 1).

Abb. 1: Start des .NET-Upgrade-Assistenten aus Visual Studio

Wir starten einen Report und müssen in den nachfolgenden Schritten das betreffende Projekt, das Ziel (hier .NET 8) und den Umfang der späteren Migration auswählen (Abb. 2).

Abb. 2: Konfigurieren der Migration im .NET-Upgrade-Assistenten

Das Ergebnis (Abb. 3) ist ein Report über die Einschätzung des Vorhabens und der notwendigen Schritte.

Abb. 3: Analyseergebnis der geplanten Migration

Die Hinweise stimmen uns optimistisch. Auf Projektebene starten wir danach den .NET-Upgrade-Assistenten neu und führen die konkrete Migration durch. Dabei haben wir u. a. die Wahl, ob wir die Migration innerhalb des Projekts durchführen möchten (in-place project upgrade) oder ob das migrierte Projekt als neues Projekt angelegt werden soll (side-by side project upgrade). Die eigentliche Migration des Projektes erfolgt ohne unser Zutun und konnte erfolgreich abgeschlossen werden. Die Windows-Forms-Applikation wurde nach .NET 8 portiert und kann auch gestartet und ausgeführt werden (Abb. 4).

Abb. 4: Der .NET-Upgrade-Assistent zeigt die erfolgreiche Migration

Das hat sehr gut funktioniert. Wie bereits dargestellt, handelt es sich um ein Projekt mit einem begrenzten Funktionsumfang und wenig Komplexität.

Unser zweiter Test ist schon anspruchsvoller. Es geht um eine Xamarin.Forms-Applikation für Android und iOS. Diese soll nach .NET MAUI migriert werden. Wir laden auch dieses Projekt in Visual Studio. Als Erstes werden wir aufgefordert, die fehlenden Komponenten für die Entwicklung von Xamarin.Forms-Projekte in Visual Studio nachzuladen. Diese wurden schon mit den Updates von Visual Studio entfernt, da auf dem Entwicklungsrechner in letzter Zeit nur noch .NET-MAUI-Projekte entwickelt wurden. Das Projekt weist auch Abhängigkeiten zu einigen externen Bibliotheken auf. Die Analyse der bevorstehenden Migration ergibt ein deutlich gemischteres Bild. Es werden Fehler und Schwierigkeiten bei der Migration erwartet und der Gesamtaufwand wird deutlich größer (24 Story Points) eingeschätzt (Abb. 5).

Abb. 5: Analyse des Xamarin-Projekts durch den .NET-Upgrade-Assistenten

Im nächsten Schritt starten wir die automatische Migration der Xamarin.Forms-App. Es muss jedes Xamarin.Forms-Projekt eigenständig über den .NET-Upgrade-Assistenten migriert werden. Die Migration des plattformneutralen Projektes konnte ohne Fehlermeldungen abgeschlossen werden. Die Migration des Android-Projekts dauerte einige Minuten und auch hier werden keine schwerwiegenden Fehler gemeldet. Als Letztes wird versucht, das iOS-Projekt zu migrieren. Wir bekommen Hinweise, dass ein Plug-in in iOS (zum Einblenden der Tastatur ist es in .NET MAUI nicht mehr erforderlich) nicht in .NET MAUI unterstützt wird. Ansonsten sieht das Ergebnis des Migrationsreports gut aus. Die App lässt sich dennoch nicht unmittelbar fehlerfrei kompilieren. Es werden Mehrdeutigkeiten von Klassen gemeldet. Das rührt daher, dass .NET MAUI auch neue Klassen einführt, die es in Xamarin nicht gab. Hier steht einige manuelle Nacharbeit auf der Agenda. Die Migration von Xamarin auf .NET MAUI ist deutlich komplexer als ein Update der .NET-Version. Dennoch liefert der .NET-Upgrade-Assistent wichtige Hinweise für eine Migration und nimmt auch bereits einige Arbeitsschritte vor.

Unser letztes Experiment versucht, eine Migration einer App für die UWP, bestehend aus mehreren Teilprojekten und der Nutzung von externen Bibliotheken. Starten wir auch hier mit einer Analyse der geplanten Migration: Das Ziel der Migration ist es, die App von der UWP (WinUI 2.*) nach WinUI 3 zu bekommen. Es deuten sich Probleme bei der Nutzung von Drittanbieterbibliotheken an. Die Analyse dauert mehrere Minuten und das Ergebnis enthält Hinweise zu erwarteten Aufgaben der Migration und möglichen Probleme. Hier wird beispielsweise bemängelt, dass eine externe Bibliothek für Interaktion mit dem Cloudspeicherdienst Dropbox nicht mit den neuen .NET-Versionen kompatibel ist. Wir wissen jedoch aus der Praxis, dass diese Bibliothek sowohl mit der UWP als auch mit WinUI 3 funktioniert. Gleiches gilt auch für andere gemeldete voraussichtliche Probleme. Der geschätzte riesige Migrationsaufwand (> 1 600 Story Points), primär durch das mögliche Neuschreiben der Bibliotheken, hat sich in der Praxis nicht so herausgestellt. Die App konnte mit einem deutlich geringeren Aufwand von der UWP nach WinUI 3 migriert werden. Das bedeutet, dass die Schätzung des .NET-Upgrade-Assistenten nur eine grobe Einschätzung des Aufwandes ist, sie kann auch komplett falsch sein. Wertvoller sind die Hinweise zu den konkreten Migrationsschritten.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Stärken, Schwächen und Einschätzung

Der .NET-Upgrade-Assistent wird in der Entwicklercommunity als ein nützliches Werkzeug zur Modernisierung von .NET-Anwendungen angesehen, insbesondere für Projekte, die auf älteren .NET-Framework- oder .NET-Core-Versionen basieren. Das Tool ist wertvoll, weil es den Migrationsprozess durch Automatisierung und eine geführte Schritt-für-Schritt-Anleitung erheblich vereinfacht. Das hilft, typische Fehler zu vermeiden und den Prozess effizienter zu gestalten, insbesondere bei weniger komplexen Projekten. Man kann folgende Stärken ausmachen:

  • Automatisierung: Das Tool automatisiert viele Aufgaben, die sonst manuell erledigt werden müssten, wie z. B. die Aktualisierung von Projektdateien und die Verwaltung von Abhängigkeiten. Das reduziert den Arbeitsaufwand und minimiert das Risiko von Fehlern.
  • Vielfältige Unterstützung: Der Assistent unterstützt eine breite Palette von Projekttypen, darunter ASP.NET, WPF, Windows Forms und Xamarin.Forms und bietet auch Migrationspfade zu .NET MAUI und Azure Functions.
  • Geführter Prozess: Entwickler:innen wird ein strukturierter Migrationspfad geboten, der sie durch jeden Schritt des Prozesses führt. Das Tool hebt potenzielle Probleme hervor und bietet Lösungen an, was besonders für weniger erfahrene Entwickler:innen hilfreich ist.

Das Tool ist primär eine Hilfestellung, d. h., es hat Schwächen und Einschränkungen:

  • Komplexe Projekte: Bei sehr großen und komplexen Projekten kann das Tool an seine Grenzen stoßen und erfordert meist manuelle Nacharbeiten, da es nicht immer alle Probleme erkennt oder die optimalen Lösungen vorschlägt.
  • Manuelle Eingriffe: Trotz der Automatisierung bleibt bei der Migration noch immer viel manuelle Arbeit erforderlich, insbesondere wenn es Änderungen an den Architekturentscheidungen gibt oder veraltete Bibliotheken nicht automatisch aktualisiert werden können.

Das Tool wird bisher kontinuierlich verbessert und unterstützt inzwischen auch neuere Technologien wie .NET MAUI und Azure Functions. Diese Erweiterungen und die Verbesserung der Fehlerbehandlung tragen dazu bei, die Qualität der automatischen Upgrades weiter zu erhöhen.

Migrationsplanung

Von einfachen Anpassungsmaßnahmen (Upgrade-Pfaden) abgesehen, beispielsweise eine Umstellung von einer älteren auf eine aktuelle .NET-Version, benötigt man eine gute Planung der Durchführung einer Migration. Wir beschreiben nachfolgend drei typische Migrationspfade im .NET-Umfeld. Die Migrationsplanung für .NET-Anwendungen umfasst typischerweise die Schritte (grober Ablauf) gemäß Tabelle 1.

Schritt/Phase (Meilenstein) Teilschritte
Vorbereitungsphase
  • Bestimmen Sie, warum die Migration notwendig ist. Das könnte die Nutzung neuer .NET-Features, plattformübergreifende Unterstützung, verbesserte Performance oder die Vorbereitung auf zukünftige Entwicklungen sein.
  • Überprüfen Sie die bestehende Anwendung auf Kompatibilität mit der Zielplattform. Tools wie der .NET-Upgrade-Assistent helfen, nicht unterstützte APIs und Bibliotheken zu identifizieren. Erstellen Sie eine Liste aller verwendeten NuGet-Pakete und Drittanbieterbibliotheken. Stellen Sie sicher, dass diese in der Zielplattform verfügbar sind oder alternative Pakete verwenden. 
  • Überprüfen Sie, ob die Anwendung auf spezifische Windows APIs oder Windows-Dienste angewiesen ist, die möglicherweise nicht in allen .NET-Versionen unterstützt werden.
Planungsphase
  • Identifizieren Sie unabhängige Module oder Komponenten, die separat migriert werden können. Das erleichtert die schrittweise Migration und minimiert das Risiko.
  • Wählen Sie ein weniger kritisches Modul oder eine Komponente für eine Pilotmigration aus, um Erfahrungen zu sammeln und potenzielle Herausforderungen frühzeitig zu erkennen.
  • Bestimmen Sie den Ressourcenbedarf. Erstellen Sie einen realistischen Zeitplan für die Migration, der Pufferzeiten für unvorhergesehene Probleme beinhaltet.
Durchführung der Migration
  • Nutzen Sie den .NET-Upgrade-Assistenten, um die Projektdateien in das neue Format zu überführen und die Struktur auf die Zielplattform anzupassen.
  • Ersetzen Sie veraltete oder nicht unterstützte APIs durch moderne Alternativen, die in der Zielplattform verfügbar sind. Migrieren Sie die Anwendung Schritt für Schritt.
  • Führen Sie nach jeder Phase Tests durch, um sicherzustellen, dass die Anwendung weiterhin korrekt funktioniert. 
  • Beheben Sie während des Migrationsprozesses auftretende Probleme und passen Sie den Code entsprechend an.
Test- und Optimierungsphase
  • Stellen Sie sicher, dass alle Funktionen der Anwendung nach der Migration wie erwartet arbeiten. Das umfasst auch spezifische Tests für neu implementierte Features.
  • Testen Sie die Anwendung auf Performanceprobleme und optimieren Sie den Code, um die Vorteile der neuen Plattform voll auszuschöpfen.
  • Nutzen Sie die Gelegenheit, den Code zu modernisieren und zu optimieren, indem Sie neue Features und Best Practices der Zielplattform implementieren.
Abschluss und Bereitstellung
  • Dokumentieren Sie den gesamten Migrationsprozess, um zukünftige Wartungsarbeiten zu erleichtern und Wissenstransfer innerhalb des Teams zu ermöglichen.
  • Bereiten Sie die Anwendung für die Bereitstellung vor und stellen Sie sicher, dass Monitoringtools eingerichtet sind, um die Anwendung in der Produktionsumgebung zu überwachen und auf Probleme reagieren zu können.
Nachbereitung
  • Sammeln Sie nach der Bereitstellung Feedback von Nutzer:innen und Ihrem Team, um weitere Verbesserungen und Optimierungen vorzunehmen.
  • Planen Sie regelmäßige Updates und Wartungszyklen ein, um die Anwendung auf dem neuesten Stand zu halten und neue Features der Plattform zu nutzen.

Tabelle 1: Schrittfolge bei der Migration von .NET-basierten Anwendungen

Die Migration einer .NET-Anwendung ist ein anspruchsvoller Prozess, der eine sorgfältige Planung und Durchführung erfordert. Mit einer strukturierten Herangehensweise und der Nutzung des .NET-Upgrade-Assistenten kann der Übergang jedoch risikoarm gestaltet werden. 

 

Migration von Xamarin.Forms nach .NET MAUI

Xamarin.Forms ist eine bewährte Plattform zur Erstellung plattformübergreifender mobiler Anwendungen mit einer gemeinsamen Codebasis für Android, iOS und UWP. Mit der Einführung von .NET MAUI (Multi-Platform-App-UI) hat Microsoft eine Weiterentwicklung und Erweiterung von Xamarin.Forms bereitgestellt, die zusätzlich die Entwicklung für macOS und Windows unterstützt und eine konsolidierte Entwicklungsplattform innerhalb von .NET darstellt. Sollen bestehende Xamarin.Forms-Apps weiter angepasst und entwickelt werden, sind diese zu .NET MAUI zu migrieren. Der typische Migrationspfad sieht wie folgt aus (Meilensteine):

  • Schritt 1 – Aktualisieren: Aktualisieren Sie das Xamarin.Forms-Projekt auf die neueste Version, um sicherzustellen, dass alle Abhängigkeiten aktuell sind.
  • Schritt 2 – Konvertieren: Konvertieren Sie das Projekt in ein .NET-MAUI-Projekt, was die Anpassung der Projektdateien (.csproj) erfordert.
  • Schritt 3 – Prüfen: Überprüfen und aktualisieren Sie die XAML-Dateien und den Code auf Kompatibilität mit .NET MAUI. Das schließt auch die Migration von benutzerdefinierten Renderern und Abhängigkeiten ein.
  • Schritt 4 – Tests: Führen Sie umfassende Tests durch, um sicherzustellen, dass die Anwendung auf allen Zielplattformen ordnungsgemäß funktioniert.
  • Schritt 5 – Anpassungen: Durchführen von notwendigen Anpassungen, beispielsweise Bibliotheken, die unter neueren .NET-MAUI-Versionen nicht mehr unterstützt werden.

Neben der Möglichkeit das Projekt weiter entwickeln zu können, ergeben sich weitere Vorteile, beispielsweise eine vereinfachte Projektstruktur, eine bessere Entwicklungsunterstützung, erweiterte Plattformunterstützung (einschließlich Desktopplattformen) und eine verbesserte Performance und API-Konsistenz innerhalb des .NET-Ökosystems.

Migration von WPF zu WinUI 3

Windows Presentation Foundation (WPF) ist ein bewährtes Framework zur Entwicklung von Desktopanwendungen unter Windows. Es bietet eine flexible und leistungsstarke Plattform für die Gestaltung von Benutzeroberflächen. WinUI 3 hingegen ist das neueste UI-Framework von Microsoft, das moderne Windows-10- und Windows-11-Anwendungen mit einer modernen Benutzeroberflächentechnologie ermöglicht. Die Migration von WPF zu WinUI 3 kann eine sinnvolle Option sein, wenn Sie eine modernere, zukunftssichere Oberfläche für Ihre Anwendung entwickeln möchten. Ein typischer Migrationspfad kann wie folgt aussehen:

  • Schritt 1 – Bewertung der Anwendung: Beginnen Sie mit einer gründlichen Analyse der bestehenden WPF-Anwendung, um zu verstehen, welche Teile problemlos migriert werden können und welche möglicherweise neu entwickelt werden müssen. Überprüfen Sie, welche externen Bibliotheken und Abhängigkeiten verwendet werden und ob diese in WinUI 3 unterstützt werden oder ersetzt werden müssen.
  • Schritt 2 – Erstellen eines neuen WinUI-3-Projekts: Erstellen Sie ein neues WinUI-3-Projekt in Visual Studio. Dies stellt die Grundlage für die Migration Ihrer bestehenden WPF-Komponenten dar. Die Geschäftslogik (Backend-Code) aus der WPF-Anwendung kann in den meisten Fällen direkt übernommen werden, da WinUI 3 auf derselben .NET-Plattform basiert. Der Fokus liegt daher auf der Migration der Benutzeroberfläche.
  • Schritt 3 – Migration der Benutzeroberfläche: WPF und WinUI 3 verwenden beide XAML zur Definition der Benutzeroberfläche, aber es gibt Unterschiede in den verfügbaren Steuerelementen und den unterstützten Features. Die XAML-Dateien müssen angepasst werden, um mit den WinUI-3-Komponenten kompatibel zu sein. Einige WPF-spezifische Funktionen wie bestimmte Steuerelemente oder Datenbindungskonzepte müssen durch die entsprechenden WinUI-3-Features ersetzt werden. Hier kann es notwendig sein, benutzerdefinierte Steuerelemente oder Workarounds zu implementieren.
  • Schritt 4 – Testen und Optimieren: Testen Sie die migrierte Anwendung umfassend, um sicherzustellen, dass alle UI-Komponenten wie erwartet funktionieren. Achten Sie dabei besonders auf die Performance und das Verhalten der Benutzeroberfläche. Nehmen Sie nach Bedarf Optimierungen vor, insbesondere in Bezug auf die Nutzung neuerer WinUI-3-Features, die in WPF nicht verfügbar waren.
  • Schritt 5 – Bereitstellung: Bereiten Sie die migrierte Anwendung für die Bereitstellung vor. WinUI 3 ermöglicht es, die Anwendung sowohl über den Microsoft Store als auch als klassische Desktopanwendung zu verteilen.

Welche Vorteile bietet eine Migration zu WinUI 3? WinUI 3 unterstützt das Fluent-Design-System, das moderne und ansprechende Benutzeroberflächen ermöglicht. Ebenso ist WinUI 3 die Basis für zukünftige Windows-Entwicklungen, was die Anwendung langfristig wartbar und kompatibel mit neuen Windows-Versionen macht. Es gibt eine bessere Unterstützung und Integration mit den neuesten Windows APIs, was die Nutzung neuerer Betriebssystemfeatures erleichtert. Bei größeren Projekten kann man jedoch von einem umfangreicheren Anpassungsbedarf ausgehen. Die Migration der Benutzeroberfläche kann erheblichen Anpassungsaufwand erfordern, insbesondere bei komplexen WPF-Anwendungen. Einige WPF-Features haben keine direkten Entsprechungen in WinUI 3, was möglicherweise Neuentwicklungen erforderlich macht. Zusammengefasst kann man sagen: Die Migration von WPF zu WinUI 3 ist ein strategischer Schritt, der insbesondere dann sinnvoll ist, wenn eine langfristige Modernisierung und Integration mit den neuesten Windows-Technologien angestrebt wird.

Anmerkung/Hinweis: Microsoft hat erkannt, dass WPF weiterhin eine große Bedeutung für bestehende Projekte (und vielleicht auch noch für neue Projekte) hat. Es sind daher auch Anpassungen und Aktualisierungen geplant. Die Roadmap [2] zu WPF sieht dazu beispielsweise wie folgt aus: „Im letzten Jahr haben wir uns bemüht, die Testinfrastruktur zu verbessern, Community-PRs zusammenzuführen, um seit langem bestehende Probleme zu beheben, neue Steuerelemente hinzuzufügen (OpenFolderDialog) und neue Funktionen zu ermöglichen (Hardwarebeschleunigung für RDP-Verbindungen). Die Bemühungen, unsere Tests als Open Source bereitzustellen und den Testprozess durch CI/CD-Pipelines zu automatisieren, Komponententests hinzuzufügen, persistente Probleme zu beheben und neuere Steuerelemente hinzuzufügen, führten dazu, dass WPF auf .NET 8 integriert wurde“. Ebenso werden die folgenden Features, beispielsweise mit .NET in Aussicht gestellt:

  • Integration von Win-11-Features wie Andocklayout, abgerundete Ecken für Steuerelemente und neueres Farbschema
  • Nullability Annotations zur Verbesserung der Codequalität
  • Verbesserungen der Barrierefreiheit für WPF-Steuerelemente
  • Optimierung von WPF für verbesserte Unterstützung auf allen Geräten

Daraus kann man schlussfolgern, dass WPF kurzfristig wohl nicht abgekündigt wird und daher eine Migration nach WinUI 3 aus heutiger Sicht nicht zwingend ist. Wichtiger ist es jedoch bestehende WPF-Anwendungen auf der Basis des .NET Frameworks (bis Version 4.8*) nach .NET zu portieren – womit wir direkt bei der Beschreibung des nächsten Migrationspfads sind.

Migration von .NET Framework zu .NET

Das .NET Framework ist eine ältere, aber weit verbreitete Plattform für die Entwicklung von Windows-Anwendungen. Es wurde bekanntermaßen durch .NET (früher .NET Core) abgelöst. Sollen bestehende Applikationen dauerhaft weiterentwickelt werden, muss man diese nach .NET migrieren. Die Schritte des typischen Migrationspfades sind:

  • Schritt 1 – Analyse: Nutzen Sie den .NET-Upgrade-Assistenten, um die Kompatibilität Ihrer Anwendung zu prüfen und potenzielle Probleme zu identifizieren.
  • Schritt 2 – Aktualisieren: Aktualisieren Sie NuGet-Pakete und Abhängigkeiten, um die Kompatibilität mit .NET sicherzustellen.
  • Schritt 3 – Konvertieren: Konvertieren Sie das Projekt in ein SDK-Format und passen Sie die Projektdateien entsprechend an.
  • Schritt 4 – Migrieren: Migrieren Sie schrittweise den Code, indem Sie veraltete APIs ersetzen und plattformübergreifende Features nutzen.
  • Schritt 5 – Testen: Führen Sie umfassende Tests durch, um sicherzustellen, dass die Anwendung unter .NET korrekt funktioniert.

Durch die Migration – welche nicht immer einfach möglich ist – erreichen wir folgende Vorteile: eine plattformübergreifende Unterstützung (Windows, Linux, macOS), eine verbesserte Performance und Ressourcennutzung, den Zugang zu den neuesten .NET-Features und -Verbesserungen und eine langfristige Wartbarkeit der Applikation. Einen Überblick zu den aktuellen .NET-Versionen und deren Vorversionen gibt Tabelle 2. .NET 9 wird voraussichtlich im November 2024 erscheinen und .NET 10 ist für 2025 geplant.

Version Veröffentlichungs-datum Letzte Patch-Version Patch-Datum Release-Typ Support-Ende
.NET 8 14. November 2023 8.0.8 13. August 2024 LTS 10. November 2026
.NET 7 8. November 2022 7.0.20 28. Mai 2024 STS 14. Mai 2024
.NET 6 8. November 2021 6.0.33 13. August 2024 LTS 12. November 2024
.NET 5 10. November 2020 5.0.17 10. Mai 2022 STS 10. Mai 2022

Tabelle 2: .NET-Versionen (LTS = Long Term Support, STS = Standard Term Support)

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Fazit und Ausblick

Insgesamt bietet der NET-Upgrade-Assistent eine solide Grundlage für die Modernisierung von .NET-Anwendungen, besonders für kleinere bis mittelgroße Projekte. Bei größeren Projekten kann es ein Hilfsmittel sein, um den Anpassungsbedarf der Migration abzuschätzen. Hier muss i. d. R. eine umfassende Planung je nach Ausgangspunkt und Ziel des Upgrades durchgeführt werden. Schnelle und unkomplizierte Hilfe kann es für das eine oder andere .NET-Projekt bereitstellen, wenn man dieses beispielsweise im Zuge des laufenden Entwicklungsprojektes auf die aktuelle .NET-Version bringen möchte. Die Migration per Mausklick ist auch im .NET-Umfeld nur in Einzelfällen machbar.

Web: https://larinet.com, https://www.tech-punkt.com.

Links & Literatur

[1] https://learn.microsoft.com/de-de/dotnet/core/porting/upgrade-assistant-overview?WT.mc_id=dotnet-35129-website

[2] https://github.com/dotnet/wpf/blob/main/roadmap.md

The post Der .NET-Upgrade-Assistent: Alles, was Sie zur Anwendungsmigration wissen müssen appeared first on BASTA!.

]]>
Die Zukunft des Software Developments: KI in der Praxis https://basta.net/blog/ki-softwareentwicklung-tools/ Tue, 24 Sep 2024 07:17:50 +0000 https://basta.net/?p=93459 Die rasante Entwicklung der künstlichen Intelligenz (KI) verändert bereits heute die Art und Weise, wie Software entwickelt wird. In unserer exklusiven Eröffnungskeynote haben sich drei Experten – Neno Loje, Christian Weyer und Jörg Neumann – genau diesem Thema gewidmet. In dieser Keynote zeigen sie, welche Tools und Methoden die Programmierung durch und mit künstlicher Intelligenz revolutionieren.

The post Die Zukunft des Software Developments: KI in der Praxis appeared first on BASTA!.

]]>
Was erwartet dich in der Keynote?

  1. Von Sprache zu Software: GitHub Copilot Workspaces
    Was bedeutet es, wenn Software durch Sprache entsteht? Mit “GitHub Copilot Workspaces” geben unsere Speaker einen Ausblick auf die nächste Generation der KI-gestützten Entwicklerwerkzeuge und wie sie deinen Entwicklungsprozess optimieren können.
  2. Smarter coden mit KI: Claude-Dev in VS Code
    Coding ist nur ein Teil der täglichen Arbeit eines Entwicklers. Mit Claude-Dev, vollständig in Visual Studio Code integriert, macht das Programmieren einfach mehr Spaß – und du sparst wertvolle Zeit bei repetitiven Aufgaben.
  3. Azure AI: Das Schweizer Taschenmesser für KI-Entwickler
    Solide KI-Lösungen zu entwickeln erfordert mehr als nur ein Tool. Azure AI bietet ein umfassendes Set an Technologien, das alle Aspekte der KI-Entwicklung abdeckt – von der Modellauswahl bis zum Scripting und darüber hinaus.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Warum du diese Keynote nicht verpassen solltest

Diese Keynote ist ein Muss für jeden, der sich für die Zukunft der Softwareentwicklung interessiert. Die gezeigten Tools und Techniken eröffnen neue Möglichkeiten, wie Entwickler ihre Arbeit effizienter gestalten können. Egal, ob du bereits mit KI arbeitest oder gerade erst einsteigst – diese Keynote liefert dir wertvolle Insights und praktische Tipps für deinen Alltag als Software-Entwickler.

 

Jetzt die gesamte Keynote ansehen und mehr erfahren!

The post Die Zukunft des Software Developments: KI in der Praxis appeared first on BASTA!.

]]>
Microsoft Copilot for Security https://basta.net/blog/microsoft-copilot-for-security/ Wed, 28 Aug 2024 08:22:27 +0000 https://basta.net/?p=93395 Microsoft Copilot for Security ist eine KI-gestützte Lösung, die die Effizienz und Genauigkeit von Sicherheitsteams verbessert. Für Analyst:innen bietet es eine assistierende Copilot-Erfahrung, die bei End-to-End-Sicherheitsszenarien unterstützt und sich nahtlos in bestehende Microsoft-Sicherheitsprodukte integrieren lässt.

The post Microsoft Copilot for Security appeared first on BASTA!.

]]>
Der Copilot nutzt die neuesten Fortschritte in der AI-Technologie, um Sicherheitsanalysen zu automatisieren und zu beschleunigen, was letztlich zu verbesserten Sicherheitsergebnissen führt. Zusammengefasst ist es ein leistungsstarkes Werkzeug, das Sicherheitsteams dabei unterstützt, ihre Arbeit effizienter und effektiver zu gestalten.

 

Was ändert sich?

Die Sicherheitsbranche ist ein sich ständig weiterentwickelndes Feld, das andauernd nach neuen und effektiveren Methoden sucht, um die Sicherheit von Systemen und Daten zu gewährleisten. Als Microsoft am 1. April 2024 Microsoft Copilot for Security für den Markt freigab, hielt die künstliche Intelligenz Einzug in die Sicherheitsbranche. Der Einfluss ist signifikant – hier ein paar der wichtigsten Auswirkungen:

 

  • Produktivitätssteigerung: Studien zeigen, dass Sicherheitsexperten bei allen Aufgaben bis zu 22 Prozent schneller und neue Sicherheitsmitarbeiter bis zu 44 Prozent genauer sind.
  • Effizienzsteigerung: Microsoft Copilot for Security hilft Sicherheitsteams, ihre Effizienz zu steigern, indem es ihnen ermöglicht, innerhalb von Minuten statt Stunden oder Tagen auf Vorfälle zu reagieren.
  • Lern- und Karrierewachstum: Copilot unterstützt das Lernen und die berufliche Entwicklung von Sicherheitsteams. Es hilft Nachwuchsanalyst:innen beim Aufbau von Wissen und Fähigkeiten und spart gleichzeitig den leitenden Mitarbeitenden wertvolle Zeit.
  • Benutzerakzeptanz: Mehr als 93 Prozent der Benutzer:innen wollten Copilot erneut verwenden.
  • AI als Force Multiplier: Künstliche Intelligenz wird schnell zu einem Force Multiplier, der bedeutende Möglichkeiten für Sicherheitsteams bietet, die Produktivität zu steigern, Zeit zu sparen, Ressourcen aufzurüsten und mehr.

 

Insgesamt trägt Microsoft Copilot for Security dazu bei, die Sicherheitsbranche zu transformieren, indem es die Effizienz und Genauigkeit von Sicherheitsteams verbessert und gleichzeitig das Lernen und die berufliche Entwicklung fördert.

 

Wie kann ich AI Awareness schaffen?

Diese Frage ist leider nicht so einfach zu beantworten – dennoch gibt es ein paar Leitsätze, die hier einen Ansatz zeigen können, wie sich AI-Awareness schaffen lässt:

 

  1. Bildung und Schulung: Schulungen für Mitarbeiter:innen über die Grundlagen der KI und ihre Anwendung in der Sicherheitsbranche stellen ein essenzielles Fundament dar. Dabei werden sowohl theoretisches Wissen als auch praktische Anwendungen vermittelt.
  2. Workshops und Seminare: Ein wertvolles Mittel gegen Ängste oder Unsicherheit bietet die Durchführung von Workshops und Seminaren. Das trägt zusätzlich dazu bei, Akzeptanz aufzubauen und das Vertrauen in die Technologie zu stärken.
  3. Ressourcen zur Verfügung stellen: Wer sich in die Cloud begibt, steht oft vor der Herausforderung, wie man Know-how aufbauen kann. Ein wesentlicher Punkt hierbei ist die Bereitstellung von Ressourcen. Das ist bereits in der Grundarchitektur des Enterprise-Scale Framework verankert und findet sich in den Sandboxing Landing Zones wieder.
  4. Kontinuierliche Unterstützung: Es ist ebenso wichtig, dass Unternehmen kontinuierliche Unterstützung bieten, um sicherzustellen, dass Mitarbeiter:innen bei der Nutzung von KI in der Sicherheitsbranche erfolgreich sind.
  5. Förderung einer Kultur der Akzeptanz: Unternehmen sollten eine Kultur fördern, die die Akzeptanz und Nutzung von KI fördert.

 

Alles in allem muss grundlegende Akzeptanz und eine Hands-on-Experience geschaffen werden, um im weiteren Schritt die AI auch in interne Systeme einfließen zu lassen. Dieser Schritt ist essenziell und einer der wichtigsten auf der AI Journey.

 

Copilot for Security Standalone vs. Embedded?

In der Standalone Experience von Copilot for Security wird eine eigenständige Anwendung genutzt – Benutzer:innen greifen direkt auf die Copilot-Plattform zu, um Sicherheitsanalysen durchzuführen. Die Embedded-Version von Copilot for Security ist in den Microsoft-Produkten integriert. Das bedeutet, dass die Funktionen von Copilot direkt in folgenden Produkte genutzt werden können:

 

  • Microsoft Defender XDR
  • Microsoft Purview
  • Microsoft Sentinel
  • Microsoft Entra
  • Microsoft InTune
  • Microsoft Defender for Cloud

 

Diese Integration ermöglicht es Benutzer:innen, die Vorteile von Copilot zu nutzen, ohne die gewohnte Arbeitsumgebung verlassen zu müssen.

 

Wie funktioniert Copilot for Security?

Copilot for Security nutzt das fortschrittliche GPT4-Modell von OpenAI in Kombination mit einem speziellen Sicherheitsmodell, das von Microsoft entwickelt wurde. Dieses Modell wird durch die umfassende Sicherheitsexpertise und globale Bedrohungsintelligenz von Microsoft Security angetrieben. Durch die Integration von weiteren Microsoft Services und Third-party Services werden die Effektivität und Effizienz von Sicherheitsexperten erhöht.

Funktionen wie die Skriptanalyse ermöglichen es Kunden beispielsweise, Hunderte von Codezeilen zu analysieren und diese in natürlicher Sprache in Minuten zu interpretieren. Der Prozess ist grundsätzlich sehr einfach ausgelegt und basiert auf einigen Charakteristika (Abb. 1).

 

Die Grafik zeigt, wie Microsoft Copilot for Security einen Prompt analysiert und eine Antwort erstellt.

Abb. 1: Der Prozess für Copilot for Security [1]

 

  1. Einen Prompt in die Prompt Bar eingeben. Das ist die Interaktion des Mitarbeitenden mit dem Copilot.
  2. Der Copilot for Security sendet diese Information an das Backend. Hier wird auch die initiale Datenverarbeitung durchgeführt, die anschließend einen Plan aufgrund der verfügbaren Möglichkeiten (Skills) erstellt.
  3. Sobald ein Plan definiert und erstellt ist, führt Copilot diesen Plan aus, um den erforderlichen Datenkontext zum Beantworten des Prompts zu erhalten.
  4. Während der Ausführung des Plans analysiert Copilot alle Daten und Muster, um intelligente Erkenntnisse zu liefern.
  5. Copilot kombiniert alle Daten und Kontexte und nutzt die Leistungsfähigkeit seines erweiterten LLM (Large Language Model), um eine Antwort in verständlicher Sprache zu verfassen.
  6. Bevor die Antwort an Benutzer:innen zurückgesendet werden kann, formatiert und überprüft Copilot die Antwort im Rahmen des Engagements von Microsoft für verantwortungsvolle AI. Dies beruht auf folgenden Prinzipien:
    • Fairness
    • Zuverlässigkeit und Sicherheit
    • Datenschutz und -Sicherheit
    • Inklusion
    • Transparenz
    • Verantwortlichkeit
  7. Zuletzt wird die verifizierte Antwort an die Benutzer:innen zurückgesendet, die damit weiterarbeiten können.

 

Datenschutz und -sicherheit

Im sechsten Punkt wurde das Thema verantwortungsvolle AI und das Engagement von Microsoft kurz angeschnitten. Ich möchte hier auf den Punkt Datenschutz und -sicherheit etwas genauer eingehen.

Ein wesentlicher und wichtiger Aspekt hierbei ist „Deine Daten sind deine Daten, die durch umfassende Compliance- und Sicherheitskontrollen gesichert sind“. Ihre Daten werden auch nicht für das Trainieren von AI-Modellen verwendet. Um die Daten ausschließlich der Organisation zur Verfügung zu stellen, werden folgende Maßnahmen angewendet:

 

  • Verschlüsselung: Daten werden sowohl im Ruhezustand als auch während der Übertragung verschlüsselt, um unbefugten Zugriff zu verhindern.
  • Zugriffskontrollen: Nur autorisierte Personen haben Zugriff auf sensible Informationen. Das wird durch rollenbasierte Zugriffskontrollen und die Zuweisung von Berechtigungen gewährleistet.
  • Datenfreigabeeinstellungen: Die Freigabe von Kundendaten ist standardmäßig aktiviert, kann jedoch von den Copilot-Besitzenden während der ersten Nutzung und jederzeit danach angepasst werden.
  • Regelmäßige Überwachung und Protokollierung: Systemgenerierte Protokolle werden kontinuierlich erstellt, um die Systemaktivität zu überwachen und sicherzustellen, dass die Systeme wie erwartet funktionieren.
  • Einhaltung von Datenschutzbestimmungen: Microsoft Copilot for Security erfüllt die bestehenden Datenschutz-, Sicherheits- und Complianceverpflichtungen, einschließlich der Datenschutz-Grundverordnung (DSGVO) und der EU-Datenrichtlinie.

 

Analyst:innen können keine Daten und Informationen einsehen, auf die sie keinen Zugriff haben. Wenn beispielsweise ein Securityanalyst Informationen von Microsoft Sentinel benötigt, muss er auch die notwendigen Berechtigungen dafür haben, um die Daten zu erhalten.

Hier eine Auflistung der Microsoft-Hauptdatenquellen (Skills), die an Copilot for Security angebunden bzw. aktiviert werden können:

 

  • Microsoft Defender XDR
  • Microsoft Sentinel
  • Microsoft InTune
  • Microsoft Defender Threat Intelligence
  • Microsoft Entra
  • Microsoft Purview
  • Microsoft Defender External Attack Surface Management
  • Microsoft Defender for Cloud

 

Des Weiteren kann Copilot for Security um Third-party-Plug-ins und Custom-Integrationen erweitert werden.

 

Schlussfolgerung

Zusammengefasst handelt es sich bei Copilot for Security um eine Revolution im Securitybereich, die unter Berücksichtigung und Sicherstellung von umfangreichen Compliance- und Sicherheitsmaßnahmen jedem zur Verfügung steht. Diese Funktionalität basiert auf dem aktuellen Entwicklungsstand der künstlichen Intelligenz und wird in den hoch skalierbaren Cloud-Datacenters von Microsoft betrieben.

Aus Sicht von Konsumenten wird sich die Erkennung von Angriffen erheblich verbessern und auch die Incident Response beschleunigen, was heutzutage ein absolutes Muss darstellt.

 

Links & Literatur

[1] learn.microsoft.com

The post Microsoft Copilot for Security appeared first on BASTA!.

]]>
Effizientes Unit-Testen von Blazor-Komponenten mit bUnit https://basta.net/blog/effizientes-unit-testen-blazor-komponenten-bunit/ Mon, 29 Jul 2024 08:27:13 +0000 https://basta.net/?p=93357 Unit-Tests für Blazor-Komponenten sind ein entscheidender Schritt, um stabile und effiziente Webanwendungen zu entwickeln. In diesem Artikel zeigen wir, wie Sie mit bUnit das Testen von Blazor-Komponenten vereinfachen und optimieren können. Sie erfahren, wie Sie Dependency Injection und Mocking nutzen, CSS-Selektoren anwenden und JavaScript-Interaktionen simulieren können. Mit bUnit wird das Testen von Blazor-Komponenten durch das Razor-Format intuitiver und schneller.

The post Effizientes Unit-Testen von Blazor-Komponenten mit bUnit appeared first on BASTA!.

]]>

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Introduction

bUnit ist eine Bibliothek, die das Testen von Blazor-Komponenten vereinfacht. Dabei stellt bUnit Klassen und Methoden bereit, die das Interagieren von Blazor-Komponenten (oft „component under test“, kurz „cut“ genannt) ermöglicht und vereinfacht. Dabei ist bUnit, im Gegensatz zu xUnit, nUnit oder MSTest kein eigener Test-Runner, der Tests ausführt, sondern sitzt auf diesen auf und erweitert sie um die Möglichkeit, Blazor-Komponenten zu testen.

 

„Hallo Welt“

Wer schon einmal mit Blazor gearbeitet hat, kennt das klassische „Hallo Welt“-Beispiel, in dem ein Counter hochgezählt wird. Wir erweitern dieses kleine Beispiel (Listing 1) um zwei weitere Sachen:

 

  1. Unsere Komponente soll den initialen Wert als Parameter übergeben bekommen.
  2. Das Inkrementieren erfolgt über einen Service, der per Dependency Injection in die Komponente injiziert wird.

 

Wie sieht nun ein Test für diese Komponente aus (Listing 2)?

Listing 1

@page "/counter"
@inject IIncrementService IncrementService

<h1>Counter</h1>
<p>Current count: @InitialCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
[Parameter]
public int InitialCount { get; set; }

private void IncrementCount()
{
InitialCount = IncrementService.Increment(InitialCount);
}
}

 

 

 

Listing 2

@using Bunit
@inherits TestContext

@code {
[Fact]
public void IncrementCounterWhenButtonClicked()
{
// Wir haben, wie im produktiven System, einen "DI" Container, den wir befüllen können
// Dies ermöglich uns, den Service zu mocken und zu injizieren
// Services.AddScoped<IIncrementService, FakeIncrementService>();
// Dank des Razor-Formats können wir die Komponente direkt erzeugen und Parameter einfach zuweisen
var cut = Render<Counter>(@<Counter InitialCount="3">)

// Wir nutzen CSS-Selektoren und "Klicken" auf den Button
cut.Find("button").Click();

// Wieder benutzen wir CSS-Selektoren und prüfen ob der Text stimmt
cut.Find("p").MarkupMatches("<p>Current count: 4</p>");
}

private class FakeIncrementService : IIncrementService
{
public int Increment(int count) => count + 1;
}
}

 

Was besonders auffällt, ist, dass der Testcode sehr nah am eigentlichen Blazor-Code liegt. Das liegt daran, dass bUnit-Tests im Razor-Format unterstützt. Die Dokumentation zeigt auf, wie das möglich ist [1]. Natürlich ist es möglich, auch klassische Tests zu schreiben, jedoch ist der Razor-Ansatz sehr angenehm und macht das Testen von Blazor-Komponenten sehr eingängig. Die Unterschiede werden ebenfalls in [2] aufgezeigt.

Entwickler:innen, die sich schon mit Ende-zu-Ende-Tests auseinandergesetzt haben, wird auffallen, dass die Selektion von Elementen sehr ähnlich ist (via CSS-Selektoren). Das ist kein Zufall, denn bUnit verwendet AngleSharp [3], um die Komponenten zu analysieren und zu selektieren.

Im gegebenen Test wird ein FakeIncrementService erstellt und in den DI-Container injiziert. Das ermöglicht es, den Service zu mocken und somit die Businesslogik der Komponente zu testen. Das ist ein sehr einfaches Beispiel, zeigt aber sehr gut, wie einfach bUnit das Testen von Blazor-Komponenten macht. Durch den Razor-Ansatz sind das Erstellen und Zeichnen der Counter-Komponente wie im produktiven Code möglich. Das Übergeben der InitialCount-Parameter geschieht direkt im HTML-Code im Test.

Danach wird via Find ein Button gesucht. Im gegebenen Beispiel gibt es nur einen, welcher zurückgegeben wird – anschließend wird dank der Click Methode das onclick Event ausgelöst.

 

Der Unterschied zu Ende-zu-Ende-Tests, oder: Was bUnit nicht ist

Ende-zu-Ende-Tests sind Tests, welche die gesamte Applikation testen. Dabei wird die Applikation gestartet und die Tests interagieren mit der Applikation, wie Benutzer:innen es tun würde. bUnit hingegen zielt darauf ab, eine einzelne Komponente (oder auch mehrere) zu testen. Dabei wird die Komponente isoliert getestet und nicht die gesamte Applikation. Das bringt einige Vorteile mit sich:

 

  • Schneller: Da nur eine Komponente getestet wird, sind die Tests schneller als Ende-zu-Ende-Tests. Es muss auch viel weniger aufgebaut werden, da nur die Komponente und nicht die gesamte Applikation gestartet wird.
  • Stabiler: Da die Komponente isoliert getestet wird, hat sie weniger Abhängigkeiten zur Umwelt und ist somit stabiler.

 

Wichtig zu verstehen ist, dass bUnit keinen Ersatz für Ende-zu-Ende-Tests darstellt, sondern eine Ergänzung. Zum Beispiel laufen Tests im Gegensatz zu Ende-zu-Ende-Tests nicht im Browser. Das birgt auch einige Nachteile:

 

  • JavaScript-Interaktion: JavaScript wird nicht ausgeführt. Das bedeutet, dass JavaScript-Interaktionen nicht getestet werden können (aber bUnit bietet die Möglichkeit, JavaScript zu emulieren, später dazu mehr).
  • Styling: Da die Komponenten nicht im Browser gerendert werden und CSS-Klassen nicht „angewendet“ werden, können zum Beispiel keine Browser-spezifischen Styles getestet werden.
  • Navigation: Da die Komponenten nicht im Browser gerendert werden (und damit kein Router involviert ist), können nicht einfach Links angeklickt und zu anderen Seiten navigiert werden.

 

Wenn Ihnen diese Punkte wichtig sind, sind Testing-Frameworks wie Playwright oder Cypress die bessere Wahl.

 

Was sollte mit bUnit getestet werden?

Es ist wichtig, zu verstehen, dass bUnit kein Browser oder komplettes End-to-End-Test-Framework ist. Es ist darauf ausgelegt, einzelne Komponenten und deren Businesslogik zu testen. Hier eine sehr vereinfachte Komponente: Sie hat ein Anchor-Element, das auf eine andere interne Seite verweist: <a href=”/bestellung/1″>Zur Bestellung</a>. Ein Test könnte dann so aussehen wie in Listing 3.

 

Listing 3

@using Bunit
@inherits TestContext

@code {
[Fact]
public void ShouldNavigateToUrl()
{
var cut = Render(@<MyComponent />);
cut.Find("a").Click();

// Den NavigationManager aus dem Container holen
var navigationManager = Services.GetRequiredService<NavigationManager>();
navigationManager.Uri.Should().Be("/bestellung/1");
}
}

 

Wir klicken auf den Link und prüfen, ob der URL stimmt. Obwohl der Test zuerst sinnvoll erscheint, ist er es nicht, aus zwei Gründen. Zuerst ist es wesentlich, zu verstehen, dass Click nicht wirklich auf ein HTML-Element klickt, sondern das @onclick Event ausführt. Im gegebenen Beispiel gibt es aber gar kein @onclick Event. Das bedeutet, dass der Test immer fehlschlagen wird, da der Link nicht auf das @onclick Event reagiert. Der zweite Grund ist, dass der Test nur 3rd-Party-Code testet. Unit-Tests, die das Hauptanwendungsgebiet für bUnit sind, sollten immer nur die eigene Businesslogik testen. Das Klicken auf einen Link ist aber nicht Bestandteil der eigenen Businesslogik, sondern das Resultat aus der Kombination von Blazor und dem Browser.

 

Stubbing und Faking von Komponenten

Genau deshalb bringt bUnit auch die Fähigkeit mit, Komponenten komplett zu ersetzen. Das bedeutet, dass wir beliebige Komponenten durch eigene Komponenten ersetzen können oder einfach durch leeres Markup (auch Shallow Rendering genannt). Das macht immer dann Sinn, wenn eine Komponente viele Abhängigkeiten hat, die nicht relevant für den Test sind. Doch was ist Faking beziehungsweise Stubbing? Faking ist das Ersetzen von Komponenten durch eigene Komponenten, die wir kontrollieren können. Stubbing ist das Ersetzen von Komponenten durch leeres Markup. Ein Beispiel, in dem ein Button einer 3rd-Party-Bibliothek verwendet wird, finden Sie in Listing 4.

 

Listing 4

@page "/counter"
<h1>Counter</h1>
<p>Current count: @InitialCount</p>

<ThirdPartyButton Click="Increment">Click me</ThirdPartyButton>

@code {
[Parameter]
public int InitialCount { get; set; }

private void Increment()
{
InitialCount++;
}
}

 

Wie sieht nun ein Test dafür aus? Wie bewegen wir den ThirdPartyButton dazu, dass er das Increment Event auslöst? Das ist gar nicht so einfach, da wir nicht wissen, wie der ThirdPartyButton implementiert ist. Ein Weg besteht darin, herauszufinden, dass der ThirdPartyButton intern auch nur ein button-Element ist und wir dieses Element direkt ansprechen können (Listing 5).

 

Listing 5

@inherits TestContext

@code {
[Fact]
public void IncrementCounterWhenButtonClicked()
{
var cut = Render(@<Counter InitialCount="3" />);

cut.Find("button").Click();

cut.Find("p").MarkupMatches("<p>Current count: 4</p>");
}
}

 

Schön, der Test wird grün, es verstreicht ein wenig Zeit und nun nutzt der ThirdPartyButton ein div-Element. Resultat: Der Test wird rot. Das ist ein Problem, weil wir nicht die Implementation der Komponente testen wollen, sondern nur die Businesslogik. Hier kommt das Faking ins Spiel (Listing 6). Dafür bietet bUnit die Möglichkeit, Komponenten zu ersetzen. Das bedeutet, dass wir den ThirdPartyButton durch eine eigene Komponente ersetzen, die wir kontrollieren und mittels der wir auch einfach das Click Event auslösen können.

 

Listing 6

@using Bunit
@inherits TestContext

@code {
[Fact]
public void IncrementCounterWhenButtonClicked()
{
// Dies ersetzt alle Vorkommen von "ThirdPartyButton" durch "FakeThirdPartyButton"
ComponentFactories.Add<FakeThirdPartyButton, ThirdPartyButton>();
var cut = Render(@<Counter InitialCount="3" />);

// Wir lösen das Click Event aus
cut.FindComponent<FakeThirdPartyButton>().Instance.ClickButton();

cut.Find("p").MarkupMatches("<p>Current count: 4</p>");
}

private class FakeThirdPartyButton : ComponentBase
{
[Parameter]
public EventCallback Click { get; set; }

public Task ClickButton() => Click.InvokeAsync();
}
}

 

Das ist ein sehr einfaches Beispiel, zeigt aber sehr gut, wie mächtig das Ersetzen von Komponenten sein kann. In der offiziellen Dokumentation finden sich mehr Details über Stubbing und Faking von Komponenten [4]. bUnit geht sogar ein Schritt weiter und bietet extra Pakete an, die automatisch Stubs generieren können. Mehr dazu kann auf der offiziellen Website gefunden werden [5].

Natürlich kann das Stubbing auch dafür genutzt werden, um das Set-up von Komponenten zu vereinfachen, zum Beispiel eine Komponente, die gewisse Dienste injiziert bekommt, um zu funktionieren. Wenn diese Komponente für den Test nicht wichtig ist, ergibt es Sinn, sie zu ersetzen, um die Stabilität des Tests zu erhöhen und Abhängigkeiten zu reduzieren.

 

 

JavaScript-Interaktion

Auch wenn Stubbing oder Faking die Möglichkeit bietet, gewisse Sachen zu vereinfachen, kann es sein, dass die eigene Integration mit JavaScript getestet werden muss. bUnit führt zwar JavaScript nicht direkt aus, bringt aber eine eigene Laufzeit mit, welche es einfach macht, zu prüfen, ob JavaScript aufgerufen wurde (oder nicht). Dieses Verhalten ist automatisch angeschaltet. Dabei wird zwischen strict und loose unterschieden. strict bedeutet, dass, wenn bUnit einen JavaScript-Aufruf entdeckt, welcher nicht erwartet wurde, der Test fehlschlägt. loose hingegen ignoriert solche Aufrufe. Der Standardwert ist strict – im Falle von Werten, die von der Interoperabilitätsschicht von Blazor zurückgegeben werden, gibt loose den Standardwert des Datentyps zurück (zum Beispiel 0 für int). Eine ausführliche Dokumentation kann unter [6] gefunden werden.

 

Wo geht die Reise hin?

bUnit ist seit fünf Jahren versehen mit der Version 1, sprich seit fünf Jahren ist bUnit mehr oder weniger API-stabil und unterstützt .NET Core 3.1 bis .NET 9. Das ist unabhängig davon, dass manche von diesen Versionen schon nicht mehr offiziell von Microsoft selbst unterstützt werden. Mit so einer langen Laufzeit für eine Version gibt es natürlich Sachen, die eventuell gar nicht mehr so gebraucht werden oder die man heute anders machen würde und genau das ist die Idee für die nächste Version, die sich gerade in der Vorabphase befindet.

Das generelle Thema für Version 2 sind Aufräumarbeiten, ein verbessertes API und einige neue Funktionalitäten und Bugfixes, die größere Änderungen erforderten. Generell wurde viel alter Ballast abgeräumt. Darunter zählen zum Beispiel, dass nur noch .NET 8 und kommende Versionen unterstützt werden.

Viele Abstraktionen (Klassen wie Interfaces) waren für den Fall gedacht, dass es neben Blazor für das Web auch noch andere Modi gibt, die nicht HTML-basiert sind (zum Beispiel Blazor Mobile Bindings [7], die vor ein paar Jahren als Experiment angekündigt wurden). Diese Abstraktionen werden entfernt bzw. zusammengeführt, um ein einheitlicheres Bild zu geben.

 

Vereinheitlichung des API

Das neue Release soll ein einfacheres API bieten – zum Beispiel kannte Version 1 der Bibliothek Funktionen wie Render, SetParametersAndRender und RenderComponent. Diese Funktionen werden in der neuen Version zu einer einzigen Funktion zusammengefasst: Render. Des Weiteren wurden viele Funktionen und Klassen auf konsistente Weise umbenannt und die meisten Typen starten mit Bunit als Präfix. Zum Beispiel wird aus TestContext dann BunitContext oder aus FakeNavigationManager wird BunitNavigationManager. Es soll einfacher werden, neue Dinge zu entdecken und zu verwenden.

 

Rauf und runter im Komponentenbaum

In Version 1 war es ohne weiteres möglich, Kindkomponenten der aktuellen Komponente zu finden (FindComponent), sprich es gab eine Möglichkeit, hinunterzugehen. In Version 2 soll es auch möglich sein, von einer Kindkomponente zur Elternkomponente zu gehen. Technisch gesehen war es in Version 1 so, dass jede Komponente das generierte Markup von sich (cut.Markup) und allen Kindelementen hatte. Dabei war die Komponente selbst immer das Root-Element in dem Baum. Das führt dazu, dass Kindelemente natürlich auch nur sich selbst als Root-Element und damit keine Beziehung mehr zum Elternelement haben. Das führte zu Fehlern, die sehr unverständlich für User:innen waren, zum Beispiel beim Zusammenspiel zweier Komponenten, wobei die Elternkomponente ein form-Element ist, das aus dem Kindelement submittet wird (Listing 7).

 

Listing 7

<form @onsubmit="SubmitForm">
<ChildComponent />
</form>

@code {
private void SubmitForm() { ... }
}

Hier die Kindskomponente: <button type=”submit”>Abschicken</button>. Im Browser würde das Drücken auf Abschicken das Formular senden. Wie der Test hier aussieht, zeigt Listing 8.

 

Listing 8

var cut = Render(@<ParentComponent />);
// Wir suchen die "ChildComponent"
var childComponent = cut.FindComponent<ChildComponent>();

// Ausführen des Submit Events
childComponent.Find("button").Submit();

// Prüfen, ob der Submit Event ausgelöst wurde

 

 

Der Test scheint logisch und korrekt zu sein, aber er wird fehlschlagen. Der Grund ist, dass die childComponent nur das button-Element kennt. Dieses Hindernis wird mit Version 2 überarbeitet und funktioniert dann ohne Probleme. Insgesamt versucht bUnit nicht, sich in Version 2 neu zu erfinden, sondern viele Sachen zu vereinfachen, alte Sachen wegzulassen und den Weg zu ebnen, um in Zukunft einfacher neue Features hinzuzufügen.

 

Weitere Informationen

bUnit ist eine sehr mächtige Bibliothek, die das Testen von Blazor-Komponenten sehr einfach macht. Die offizielle Website bietet eine sehr gute Dokumentation und viele Beispiele, die das Verständnis erleichtern. Die Website ist unter [8] zu finden. Wenn man sich für die Entwicklung von bUnit interessiert, kann man gerne auf der offiziellen GitHub-Seite vorbeischauen [9]. Natürlich sind Bug-Reports, Featureverbesserung und Fragen immer willkommen.

 

Links & Literatur

[1] https://bunit.dev/docs/getting-started/create-test-project.html?tabs=xunit

[2] https://bunit.dev/docs/getting-started/writing-tests.html?tabs=xunit#write-tests-in-cs-or-razor-files

[3] https://anglesharp.github.io

[4] https://bunit.dev/docs/test-doubles/index.html

[5] https://bunit.dev/docs/extensions/bunit-generators.html

[6| https://bunit.dev/docs/test-doubles/emulating-ijsruntime.html

[7] https://learn.microsoft.com/en-us/mobile-blazor-bindings/

[8] https://bunit.dev

[9] https://github.com/bUnit-dev/bUnit

The post Effizientes Unit-Testen von Blazor-Komponenten mit bUnit appeared first on BASTA!.

]]>
Kolumne: Enterprise Angular https://basta.net/blog/neu-in-angular-18-zoneless-mode-event-replay-microsoft-entwickler/ Wed, 12 Jun 2024 09:11:04 +0000 https://basta.net/?p=93084 Im Mai 2024 hat das Angular-Team die Version 18 seines Frameworks veröffentlicht. Es bietet erstmals, wenn auch nur experimentell, eine offizielle Möglichkeit zum Arbeiten ohne zone.js. Außerdem gibt es einige wirkliche nette Abrundungen. In diesem Artikel gehe ich auf die Neuerungen ein. Die verwendeten Beispiele finden sich unter [1].

The post Kolumne: Enterprise Angular appeared first on BASTA!.

]]>
Seit seinen ersten Tagen nutzt Angular die Bibliothek Zone.js, um herauszufinden, wann die Change Detection einzelne Komponenten auf Änderungen prüfen muss. Die Idee dahinter ist einfach: In einer JavaScript-Anwendung können nur Event Handler gebundene Daten ändern. Also gilt es, herauszufinden, wann ein Event Handler ausgeführt wurde. Dazu klinkt sich zone.js in alle Browserobjekte ein: HTMLInputElement, Promise und XmlHttpRequest sind nur ein paar Beispiele. Diese Vorgehensweise, die die dynamische Natur von JavaScript nutzt, um bestehende Objekte im Nachgang zu ändern, nennt sich „Monkey Patching“.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Zoneless

Auch wenn der Ansatz im Regelfall gut funktioniert, führt er doch immer wieder zu Problemen. Beispielsweise lassen sich Bugs in diesem Bereich nur schwer diagnostizieren, und da nicht jeder Event Handler zwingend gebundene Daten ändert, läuft die Change Detection mitunter zu häufig. Entwickelt man wiederverwendbare Web Components, die Implementierungsdetails wie die Nutzung von Angular verstecken, muss der Konsument diese trotzdem mit einer bestimmten zone.js-Version einsetzen.

Ab Version 18 unterstützt das Framework nun auch einen Datenbindungsmodus, der ohne zone.js auskommt. Er ist vorerst experimentell, damit das Angular-Team zur neuen Vorgehensweise Feedback sammeln kann. Um diesen Modus zu aktivieren, ist die Funktion provideExperimentalZonelessChangeDetection beim Bootstrapping der Anwendung zu nutzen:

 

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection(),
    […]
  ]
}

Da zone.js die Change Detection nicht mehr triggert, braucht Angular andere Auslöser. Dabei handelt es sich um jene, die auch im Datenbindungsmodus OnPush zum Einsatz kommen:

 

  • Ein mit der async Pipe gebundenes Observable veröffentlicht einen neuen Wert.
  • Ein gebundenes Signal veröffentlicht einen neuen Wert.
  • Die Objektreferenz eines Inputs ändert sich.
  • Ein UI Event mit gebundenem Event Handler tritt auf (z. B. click).
  • Die Anwendung triggert die Change Detection manuell.

 

Das bedeutet, dass jene, die in der Vergangenheit bereits konsequent auf OnPush gesetzt haben, nun relativ problemlos auf Zoneless wechseln können. In Fällen, in denen das nicht einfach möglich ist, kann die Anwendung ohne Bedenken zone.js-basiert bleiben. Das Angular-Team geht davon aus, dass nicht jede bestehende Anwendung auf Zoneless umgestellt wird und unterstützt deswegen zone.js weiterhin.

Für neue Anwendungen bietet sich jedoch das Arbeiten ohne zone.js an, sobald dieser neue Modus nicht mehr experimentell ist. Die geplanten Signal Components werden künftig Zoneless-Anwendungen einfacher machen, da sie durch den konsequenten Einsatz von Signals die Voraussetzungen dafür automatisch erfüllen.

Nach einer Umstellung auf Zoneless lässt sich auch der Verweis auf zone.js aus der angular.json entfernen. Durch den Wegfall dieser Bibliothek werden die Bundles im Production Mode um ca. 11 KB kleiner.

 

Coalescing Zone

Für neue Anwendungen nutzt das Angular CLI nach wie vor zone.js. Neu ist, dass es nun Code generiert, der standardmäßig Event Coalescing aktiviert:

 

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    […]
  ]
};

Event Coalescing bedeutet, dass zone.js für unmittelbar aufeinanderfolgende Events nur ein einziges Mal tätig wird. Als Beispiel nennt das Angular-Team mehrere Click Handler, die aufgrund von Event Bubbling hintereinander angestoßen werden.

 

Neuerungen bei Router Redirects

Möchte ein Guard eine Umleitung auf eine andere Route veranlassen, liefert er den UrlTree dieser Route zurück. Im Gegensatz zur Methode navigate erlaubt dieser Ansatz jedoch nicht die Angabe von Optionen zur detaillierten Steuerung des Routerverhaltens. Beispielsweise kann die Anwendung nicht festlegen, ob der aktuelle Eintrag in der Browser-History zu überschreiben ist oder ob Parameter, die nicht in dem URL aufscheinen sollen, zu übergeben sind.

Um diese Möglichkeit zu nutzen, kann ein Guard nun auch ein RedirectCommand zurückliefern. Dieses RedirectCommand nimmt neben dem UrlTree ein Objekt vom Typ NavigationBehaviorOptions entgegen, welches das gewünschte Verhalten steuert (Listing 1).

 

Listing 1

export function isAuth(destination: ActivatedRouteSnapshot) {
  const router = inject(Router);
  const auth = inject(AuthService);

  if (auth.isAuth()) {
    return true;
  }

  const afterLoginRedirect = destination.url.join('/');
  const urlTree = router.parseUrl('/login');

  return new RedirectCommand(urlTree, {
    skipLocationChange: true,
    state: {
      needsLogin: true,
      afterLoginRedirect: afterLoginRedirect
    } as RedirectToLoginState,
  });
}

export const routes: Routes = [
  {
    path: '',
    redirectTo: 'products',
    pathMatch: 'full'
  },
  {
    path: 'products',
    component: ProductListComponent,
  },
  {
    path: 'login',
    component: LoginComponent,
  },
  {
    path: 'products/:id'
    component: ProductDetailComponent,
    canActivate: [isAuth]
  },
  {
    path: 'error',
    component: ErrorComponent
  }
];

 

Die Eigenschaft skipLocationChange gibt an, dass der Routenwechsel nicht in der Browser-History aufscheinen soll, und beim angegeben State handelt es sich um Werte, die an die zu aktivierende Komponente zu übergeben sind, jedoch nicht in dem URL aufscheinen sollen. Weitere Eigenschaften, die NavigationBehaviorOptions bietet, finden sich unter [2].

Der Vollständigkeit halber zeigt Listing 2 die adressierte Komponente, die die übergebenen Daten ausliest.

 

Listing 2

@Component({ … })
export class LoginComponent {
  router = inject(Router);
  auth = inject(AuthService);

  state: RedirectToLoginState | undefined;

  constructor() {
    const nav = this.router.getCurrentNavigation();

    if (nav?.extras.state) {
      this.state = nav?.extras.state as RedirectToLoginState;
    }
  }

  logout() {
    this.auth.logout();
  }

  login() {
    this.auth.login('John');
    if (this.state?.afterLoginRedirect) {
      this.router.navigateByUrl(this.state?.afterLoginRedirect);
    }
  }

}


Der Einsatz eines RedirectCommands wird nun auch durch das optionale Feature withNavigationErrorHandler unterstützt. Dieses Feature, das das Festlegen eines Handlers zur Behandlung von Routerfehlern erlaubt, kann nun damit das Verhalten des Routers steuern (Listing 3).

 

Listing 3

export function handleNavError(error: NavigationError) {
  console.log('error', error);

  const router = inject(Router);
  const urlTree = router.parseUrl('/error')
  return new RedirectCommand(urlTree, {
    state: {
      error
    }
  })
}

export const appConfig: ApplicationConfig = {
  providers: [
    […]
    provideRouter(
      routes,
      withComponentInputBinding(),
      withViewTransitions(),
      withNavigationErrorHandler(handleNavError),
    ),
  ]
};


 

Eine weitere Neuerung in Sachen Redirects betrifft die Eigenschaft redirectTo in der Routerkonfiguration. Bis jetzt konnte man auf den Namen einer anderen Route verweisen. Nun nimmt diese Eigenschaft auch eine Funktion auf, die sich programmatisch um die Weiterleitung kümmert (Listing 4).

 

Listing 4

export const routes: Routes = [
  {
    path: '',
    redirectTo: () => {
      const router = inject(Router);
      // return 'products' // Alternative
      return router.parseUrl('/products');
    },
    pathMatch: 'full'
  },
  […],
];

 

Der Rückgabewert dieser Funktion ist entweder ein UrlTree oder ein String mit dem Pfad der gewünschten Route.

 

Standardinhalte für Content Projection

Das Element ng-content, das Angular als Ziel für die Content Projection nutzt, kann nun einen Standardinhalt aufweisen. Diesen Inhalt zeigt Angular an, wenn der Aufrufer keinen anderen Inhalt übergibt:

 

<div class="pl-10 mb-20">
  <ng-content>
    <b>Book today to get 5% discount!</b>
  </ng-content>
</div>

 

Standardinhalte für Content Projection

Das AbstractControl, das u. a. als Basisklasse für FormControl und FormGroup fungiert, weist nun eine Eigenschaft events auf. Dieses Observable informiert über zahlreiche Zustandsänderungen (Listing 5).

 

Listing 5

export class ProductDetailComponent implements OnChanges {

  […]

  formControl = new FormControl<number>(1);

  […]

  constructor() {
    this.formControl.events.subscribe(e => {
      console.log('e', e);
    });
  }

  […]

}

 

Die einzelnen Events veröffentlicht es als Objekte vom Typ ControlEvent. Bei ControlEvent handelt es sich um eine abstrakte Basisklasse mit den folgenden Ausprägungen:

 

  • FormResetEvent
  • FormSubmittedEvent
  • PristineChangeEvent
  • StatusChangeEvent
  • TouchedChangeEvent
  • ValueChangeEvent

 

Event Replay für SSR

Durch den Einsatz von Server-side Rendering bekommen Aufrufer:innen rascher die angeforderte Seite angezeigt. Danach beginnt der Browser mit dem Laden der JavaScript Bundles, die die Seite erst interaktiv machen. Dieser Vorgang wird auch Hydration genannt. Abbildung 1 veranschaulicht das: FMP steht für „First Meaningful Paint“ und TTI für „Time to Interactive“.

 

Abb. 1: Uncanny Valley bei SSR

 

Besondere Beachtung erfordert die Zeitspanne zwischen FMP und TTI, auch bekannt als „Uncanny Valley“. Benutzer:innen bekommen hier die Seite bereits angezeigt, das JavaScript, das auf Benutzerinteraktionen reagiert, ist jedoch noch nicht geladen. Klicks und andere Interaktionen laufen also ins Leere.

Um das zu verhindern, bietet Angular nun Event Replay, das die Interaktionen im Uncanny Valley aufzeichnet und danach wiedergibt. Möglich wird das durch ein minimales Skript, das der Browser initial gemeinsam mit der vorgerenderten Seite lädt.

Event Replay kommt als optionales Feature für provideClientHydration und wird mit der Funktion withEventReplay eingebunden (Listing 6).

 

Listing 6

export const appConfig: ApplicationConfig = {
  providers: [
    […],
    provideClientHydration(
      withEventReplay()
    )
  ]
};


 

Die Implementierung von Event Replay hat sich bei Google schon länger bewährt. Sie stammt aus dem Google-internen Framework Wiz [3], das für seine Fähigkeiten im Bereich SSR und Hydration bekannt ist und deswegen für performancekritische öffentliche Lösungen zum Einsatz kommt.

Auf der diesjährigen ng-conf hat das Angular-Team bekannt gegeben, dass künftig die Angular- und Wiz-Teams verstärkt zusammenarbeiten werden. In einem ersten Schritt nutzt das Wiz-Team die aus Angular stammenden Signals während Angular die erprobten Möglichkeiten zu Event Replay vom Wiz-Team übernommen hat.

 

Automatischer TransferState für HTTP-Anfragen

Beim Einsatz von SSR cacht der HttpClient schon länger die Ergebnisse serverseitig durchgeführter HTTP-Anfragen, sodass die Anfragen im Browser nicht nochmal ausgeführt werden müssen. Dazu kommt ein Interceptor zum Einsatz, der sich auf das TransferState API stützt. Dieses API bettet die gecachten Daten im Markup der vorgerenderten Seite ein und im Browser greift u. a. der HttpClient darauf zu.

Nun berücksichtigt diese Implementierung auch, dass serverseitig andere URLs als im Browser zum Einsatz kommen können. Dazu lässt sich ein Objekt konfigurieren, das interne, serverseitige URLs auf die URLs für den Einsatz im Browser abbildet. Listing 7 veranschaulicht die Konfiguration dieses Objekts mit einem Beispiel, das aus dem Change-Log [4] übernommen wurde.

 

Listing 7

// in app.server.config.ts
{
  provide: HTTP_TRANSFER_CACHE_ORIGIN_MAP,
  useValue: {
    'http://internal-domain:80': 'https://external-domain:443'
  }
}

// Alternative usage with dynamic values
// (depending on stage or prod environments)
{
  provide: HTTP_TRANSFER_CACHE_ORIGIN_MAP,
  useFactory: () => {
    const config = inject(ConfigService);
    return {
      [config.internalOrigin]: [config.externalOrigin],
    };
  }
}

 

Außerdem platziert der HttpClient nun die Resultate serverseitiger HTTP-Anfragen, die einen Authorization- oder Proxy-Authorization-Header enthalten, nicht mehr automatisch im TransferState. Wer solche Resultate auch künftig cachen möchte, nutzt die neue Eigenschaft includeRequestsWithAuthHeaders:

 

withHttpTransferCache({
  includeRequestsWithAuthHeaders: true,
})

 

DevTools und Hydration

Die Angular DevTools zeigen nun auf Wunsch an, welche Komponenten bereits hydriert wurden. Dazu ist rechts unten die Option Show hydration overlays zu aktivieren (Abb. 2).

 

Abb. 2: Die DevTools zeigen nun an, welche Komponenten bereits hydriert wurden

 

Diese Option versieht alle hydrierten Komponenten mit einem blau-transparenten Overlay und zeigt rechts oben zusätzlich ein Wassertropfenicon an.

 

Migration auf den neuen ApplicationBuilder

Der neue ApplicationBuilder wurde bereits mit Angular 17 eingeführt und automatisch für neue Angular-Anwendungen eingerichtet. Da er auf modernen Technologien wie esbuild basiert, ist er um einiges schneller als der ursprüngliche, webpack-basierte Builder, den Angular nach wie vor unterstützt. Bei ersten Tests konnte ich eine Beschleunigung um den Faktor 3 bis 4 feststellen. Außerdem kommt der ApplicationBuilder mit einer komfortablen Unterstützung für SSR.

Beim Update auf Angular 18 schlägt das CLI nun vor, auch bestehende Anwendungen auf den ApplicationBuilder umzustellen (Abb. 3).

 

Abb. 3: Das Update auf Version 18 bietet die Migration auf den neuen ApplicationBuilder an

 

Da das CLI-Team viel Aufwand in Featureparität zwischen der klassischen und der neuen Implementierung investiert hat, sollte diese Umstellung in den meisten Fällen gut funktionieren und die Build-Zeiten drastisch beschleunigen. Auf jeden Fall sollten aber nach der Umstellung die Anwendung sowie der Build auf Funktionstüchtigkeit geprüft werden.

 

Weitere Neuerungen

Neben den bereits beschriebenen Neuerungen gibt es noch einige kleinere Updates und Verbesserungen:

 

  • @defer funktioniert auch in npm Packages.
  • Ein neues Token HOST_TAG_NAME zeigt auf den Tagnamen der aktuellen Komponente.
  • Angular I18N spielt nun mit Hydration zusammen.
  • Die Module HttpClientModule, HttpClientXsrfModule und HttpClientJsonpModule, sowie das HttpClientTestingModule sind nun deprecated. Als Ersatz kommen die entsprechenden Standalone APIs wie provideHttpClient zum Einsatz. Ein Schematic kümmert sich automatisch um diese Umstellung beim Update auf Angular 18.
  • Für neue Projekte richtet nun das CLI einen public-Ordner anstatt eines assets-Ordners ein. Damit möchte sich das Angular-Team an eine allgemeine Gepflogenheit in der Welt der Webentwicklung annähern.
  • Für Zoneless-Anwendungen wandelt das CLI async und await nicht mehr in Promises um. Beim Einsatz von zone.js ist das notwendig, da sich Promises monkey-patchen lassen.
  • Der ApplicationBuilder cacht nun Zwischenergebnisse. Damit lassen sich die folgenden Builds drastisch beschleunigen.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Zusammenfassung

Nach mehreren Releases mit vielen neuen Features steht bei Angular 18 vor allem das Abrunden von Ecken im Vordergrund. Es gibt neue Möglichkeiten für Router Redirects, Standardwerte für Content Projection, Event Replay und Verbesserungen bei der Nutzung des TransferState für HTTP-Anfragen. Daneben wurden zahlreiche Bugs behandelt und die Performance des neuen ApplicationBuilder durch Caching verbessert. Außerdem gibt es einen Ausblick auf Zoneless Angular.

 

Links & Literatur

[1] https://github.com/manfredsteyer/hero-shop.git

[2] https://angular.dev/api/router/NavigationBehaviorOptions

[3] https://blog.angular.dev/angular-and-wiz-are-better-together-91e633d8cd5a

[4] https://github.com/angular/angular/pull/55274

The post Kolumne: Enterprise Angular appeared first on BASTA!.

]]>
Neuerungen in Visual Studio https://basta.net/blog/visual-studio-2022-updates-funktionen-zeit-sparen-produktivitaet-steigern/ Fri, 24 May 2024 07:14:12 +0000 https://basta.net/?p=93006 Microsofts Entscheidung, Visual Studio im offenen Verfahren weiterzuentwickeln, hat für Entwickler:innen vielerlei Konsequenzen – regelmäßige Updates bringen verschiedenste Neuerungen, die mitunter hunderte Personenstunden einsparen. Hier eine kleine Übersicht der aktuellen Verbesserungen, mit denen man in Redmond das Entwicklungsleben zu verschönern sucht.

The post Neuerungen in Visual Studio appeared first on BASTA!.

]]>
Zum Geleit sei angemerkt, dass es sich hierbei nicht um die erste Liste von Änderungen handelt. Die Neuerungen bis inklusive Visual Studio 2022 17.8.P1 haben wir in der Vergangenheit bereits besprochen; online findet sich der Artikel unter [1].

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Als Arbeitsumgebung kommt wie immer eine Windows-10-Zweischirmworkstation zum Einsatz; die hier benutzte Version von Visual Studio Community 2022 identifizierte sich als 17.10.0.P2.

Zu guter Letzt sei noch darauf hingewiesen, dass Visual Studio selbstständig über Neuerungen informiert. Wer im Benutzerinterface die Option Hilfe | Neuigkeiten anklickt, findet eine kompakte Liste neuer Funktionen, die Microsoft als besonders wichtig erachtet (Abb. 1).

Abb. 1: Visual Studio zeigte sich durchaus auskunftsfreudig

Wie immer gilt übrigens, dass die produktive Nutzung von Preview-Releases von Microsoft nicht gerne gesehen wird. Im Unternehmen des Autors ist sie allerdings seit Jahren gelebte Praxis; außerdem ist der Visual Studio Installer immer bereit, eine normale und eine Vorschau-Variante des Produkts nebeneinander auf dem gleichen Terminal zu installieren.

AI-Unterstützung mit und ohne Abonnement

Eins der beeindruckendsten Features von Visual Studio 2022 war die wesentliche Verbesserung von IntelliSense: Insbesondere auf Systemen mit sehr hoher Einkernleistung und schneller Internetverbindung ist die IDE stellenweise zum Finden von geradezu genialen Auto-Completition-Vorschlägen befähigt.

Aufgrund des Kaufs von GitHub hat Microsoft seit einiger Zeit Zugriff auf ein gigantisches Archiv von Quellcode, der seit einiger Zeit zur Realisierung einer als GitHub Copilot bezeichneten Funktion herangezogen wird. Angemerkt sei, dass es sich dabei um ein kostenpflichtiges Feature handelt – Abbildung 2 zeigt die unter [2] bereitstehende Preisliste.

Abb. 2: Microsoft lässt sich die Nutzung von AI-Funktionen in Visual Studio vergolden

Wer sich ein diesbezügliches Abonnement leistet und die Umbenennen-Refactoring-Funktion heranzieht, findet bei installierter GitHub-Copilot-Chat-Erweiterung eine zusätzliche Schaltfläche vor. Deren Anklicken nutzt dann den AI-Assistenten zur Generierung eines vernünftig erscheinenden Namens für das neue Element.

Eine ähnliche Komfortfunktion findet sich auch im Git-Commit-Fenster. Die insbesondere im universitären Umfeld oft für Ärger sorgenden Commits generiert ein mit einer Copilot-Subskription ausgestattetes VS ebenfalls automatisch.

Abb. 3: Visual Studio generiert auf Wunsch automatische Messages

Abb. 4: Die automatisch generierte Commit-Message

Allgemein gilt dabei übrigens, dass das Aufscheinen des mit zwei Sternen dekorierten Bleistiftsymbols auf das Verfügbarsein von AI-Funktionen hinweist: Microsoft dürfte Copilot in immer mehr Bereiche erweitern, um Entwickler:innen das Erzeugen von unnötigen oder nur als Dokumentation dienenden Strings zu ersparen und so Lebenszeit zurückzugeben.

Hervorzuheben ist außerdem, dass die Funktion „Explain Commit“ von GitHub Copilot auch nachträglich verwendet werden kann. Visual Studio analysiert in diesem Fall die im Versionskontrollsystem befindlichen Informationen und generiert einen menschenlesbaren Bericht über die (wahrscheinlich) durchgeführten Änderungen an der Codebasis.

Angemerkt sei, dass man im Hause Microsoft langfristig noch weitere Funktionen für Copilot avisiert. Unter [3] findet sich beispielsweise eine Passage, in der Microsoft die automatische Analyse von Unit-Test-Fehlschlägen verspricht (Abb. 5)

Abb. 5: Ein hochgestecktes Versprechen – fraglich, ob Microsoft es auch umsetzen kann

Intelligente Suche nach Code

Ein alter Kalauer besagt, dass die Unübersichtlichkeit einer Codebasis kubisch oder sogar exponentiell mit ihrer Größe wächst. Auf jeden Fall gilt, dass vernünftige Such- und Navigationsverfahren eins der besten Verkaufsargumente für integrierte Entwicklungsumgebungen darstellen.

Neben der durch Drücken von STRG + SHIFT + F aktivierbaren stupiden Textsuche stellt Microsoft seit einiger Zeit auch einen alternativen Suchassistenten zur Verfügung, der bei der Bewertung der gefundenen Ergebnisse Informationen über die jeweiligen Codedateien miteinbezieht. Diese auch als Codesuche bezeichnete Funktion lässt sich durch Drücken von STRG +T aktivieren, und erscheint ebenfalls in einem Pop-up-Fenster. Neu ist hier die Möglichkeit, die Textfunktion durch Drücken des Knopfes zu aktivieren (Abb. 6).

Abb. 6: Die Codesuche bekommt in Visual Studio 17.9 ein Sonderregime eingeschrieben

Sollte diese Funktion auf Ihrer Installation noch nicht zur Verfügung stehen, so lässt sich in den Einstellungen unter Tools | Options | Environment | Preview Features | Plain text search in die All-in-One Search aktivieren. Zu beachten ist außerdem, dass die Aktivierung des Sonderregimes zu einer wesentlichen Vertiefung der Ergebnisse führt – die Codesuche kann in diesem Fall beispielsweise auch Kommentare nach Treffern durchforsten.

Erweiterungen im Text-Rendering zwecks höherer Übersichtlichkeit

Farbliches Hervorheben ist einer der schnellsten Wege, um textuelle Informationen für das menschliche Gehirn besser erfassbar zu machen (das dürfte spätestens seit Tim van Beverens Klassiker „Runter kommen sie immer“ im allgemeinen Wissensschatz der GUI-Designer und Elektroniker verankert sein).

Seit Visual Studio 2019 pflegt die Community dabei einen Feature-Request, der eine eher kleine Erweiterung der Textdarstellung von Visual Studio zum Ziel hat. Spezifisch wünscht man sich die Möglichkeit, Kommentare und ähnliche Elemente nicht nur in einer anderen Schriftart oder Farbe, sondern auch kursiv, fett und/oder unterstrichen darstellen zu lassen.

In den Einstellungen findet sich unter Tools | Options | Environment | Fonts and Colors nun das in Abbildung 7 gezeigte Einstellungsfenster: Wer die Checkbox Kursiv (bzw. in der englischen Version Italic) aktiviert, bekommt den jeweiligen Text fortan kursiv angezeigt.

 

Abb. 7: Kleine Erweiterungen erhöhen die Übersichtlichkeit

Eine weitere Komfortfunktion ermöglicht das direkte Anzeigen von Bildvorschauen. Wo Sie den Cursor über ein Codeelement legen, das ein Bild anzeigt, erscheint das in Abbildung 8 gezeigte Pop-up. Im Interesse der Übersichtlichkeit beschneidet Microsoft die Breite und Höhe des Thumbnails dabei auf 500 Pixel; zum Zeitpunkt der Erstellung dieses Artikels gibt es noch keine Informationen darüber, welche Arten von Bilddaten der in Visual Studio integrierte Parser auswerten kann.

Abb. 8: Der Vorschauassistent informiert über den Bildinhalt

Verbesserte Erweiterungshandhabung in Visual Studio

Start-ups, die ihren Nutzer:innen kein Visual-Studio-Code-Plug-in anbieten, haben bei der Akquise von „other people’s money“ im Allgemeinen ihre liebe Not. Visual Studio bietet seit längerer Zeit einen Erweiterungsmanager an, der in der Vergangenheit allerdings vergleichsweise staubig wirkte und von der immensen Macht der Funktion ablenkte.

Der in 17.8 erstmals eingeführte neue Extension-Manager ist nun final und erleichtert die Verwaltung von Visual-Studio-Erweiterungen an mehrerlei Stelle. Abbildung 9 zeigt dabei einen Überblick des neuen Designs.

Abb. 9: Der Erweiterungsmanager präsentiert sich neuerdings sehr aufgeräumt

VisualStudio.Extensibility

Neben dem neuen Design ist auch das Filtersteuerelement zu beachten. Klickt man das Trichtersymbol an, so reagiert Visual Studio mit der Einblendung von Comboboxen unter dem Eingabefeld. Diese ermöglichen danach das Einschränken der angezeigten Erweiterungen, um nur bestimmte Expansions zuzulassen – die Abbildung entstand durch Selektion auf Steuerelemente auf Basis der Silverlight-Technologie.

Im Hintergrund führte Microsoft Retoolings im Bereich der Erweiterungsinfrastruktur durch. Man mag es nach dem Debakel um den Home Screen des Pocket PC kaum glauben, allerdings führte Visual Studio Erweiterungen bisher im eigentlichen Prozess der integrierten Entwicklungsumgebung aus.

Mit dem unter [4] im Detail dokumentierten Paket VisualStudio.Extensibility versucht Microsoft nun einen radikalen Neuanfang. Es handelt sich dabei um einen neuen Weg zur Programmierung von Plug-ins für Visual Studio, die dank einer Client-Server-Architektur außerhalb des VS-Prozesses leben. Neben der Möglichkeit zur beliebigen Parallelisierung dürfte das naturgemäß auch mit einer Steigerung der Stabilität einhergehen; Erweiterungen sollten nicht mehr in der Lage seien, Visual Studio als Ganzes zum Absturz zu bringen.

In Version 17.9 erfährt dieses neue Interface mehrere Nutzwertsteigerungen: Erstens ist es ab sofort erlaubt, auf Basis der neuen Technologie erzeugten Erweiterungen zwecks Verbreitung in den Visual Studio Marketplace hochzuladen.

Eine zweite Ergänzung, die logisch aus dem externen Hosting der Erweiterungsprozesse folgt, ist ihre vereinfachte Installation. Der bisher notwendige Neustart von Visual Studio entfällt bei Nutzung von auf VisualStudio.Extensibility basierenden Erweiterungen, weil diese einfach ihren externen Prozess starten und Verbindung mit dem von der integrierten Entwicklungsumgebung zur Verfügung gestellten Interface aufnehmen.

 

Zu guter Letzt gibt es einige Erweiterungen im Bereich der für Plug-ins auslösbaren Aktionen. Abbildung 10 informiert beispielsweise darüber, dass es fortan durch nach folgendem Schema aufgebauten Code erlaubt ist, eine Rekompilation eines in der Solution befindlichen Projekts zu befehligen:

var result = await this.Extensibility.Workspaces().QueryProjectsAsync(

  project => project.Where(p => p.Name == projectName),

  cancellationToken);

await result.First().BuildAsync(cancellationToken);

Abb. 10: Erweiterungen dürfen nun Projektereignisse auslösen

Handhabung des Speicherbedarfs und Performanceprobleme des Emulators

Apropos Visual Studio Installer: Wer auf einer Workstation mit wenig Speicherplatz entwickelt, ist gut beraten, die Checkbox empfohlene Komponenten beim Update hinzufügen zu deaktivieren (Abb. 11).

Microsoft nimmt sich in Visual Studio  7.10 bzw. im dazugehörigen Visual Studio Installer das Recht heraus, Workstations automatisch mit in Redmond als empfehlenswert empfundenen Komponenten auszustatten – dass diese mitunter viel Speicherplatz verbrauchen, sei angemerkt.

Abb. 11: Wer Speicher sparen möchte, sollte diese Option berücksichtigen

Ein weiteres dauerndes Ärgernis betrifft die Hardwarebeschleunigung für den Android-Emulator: Ist sie deaktiviert, so artet das virtualisierte Testen von Applikationen in nervtötende Warterei aus. Visual Studio ist in aktuellen Versionen zur Erkennung von Performanceproblemen befähigt, und führt Entwickler:innen so zum Ziel.

Erleichterte Verwaltung der Optionen

Neben dem Streamlining im Erweiterungsmanager möchte Microsoft Entwickler:innen auch dabei helfen, die (immer mächtiger werdende) Flut an Settings in der Visual-Studio-Arbeitsoberfläche angenehmer zu verwalten. So ist ein Redesign des beispielsweise in Abbildung 12 gezeigten Einstellungsdialogs avisiert – als Inspirationsquelle dürfte, wie in der Vergangenheit so oft, Visual Studio Code gedient haben.

Vor der praktischen Besprechung dieses Features sei allerdings angemerkt, dass es sich dabei derzeit um eine frühe Preview handelt. Für Sie als Entwickler:in wirkt sich das unter anderem insofern aus, als Microsoft in der Dokumentation an mehrerlei Stelle darauf hinweist, dass der Gutteil der Features derzeit nur in altem Dialog zur Verfügung steht. Wer verzweifelt nach einer Einstellung sucht und diese nicht findet, ist gut beraten, der älteren Variante eine zweite Chance zu geben.

Zum Zeitpunkt der Drucklegung lässt sich die Variante des zu verwendenden Options-Dialogs unter Extras | Optionen Experience auswählen – Abbildung 12 zeigt die etwas holprige Übersetzung aus dem Hause Microsoft.

Abb. 12: An dieser Stelle ist etwas Nachbesserung vernünftig

Wer die aktualisierte Variante des Einstellungsdialogs zu verwenden gedenkt, sollte im ersten Schritt die Option Vorschau auswählen. Die integrierte Entwicklungsumgebung reagiert darauf mit der Einblendung eines Nachfragedialogs, der um die Autorisierung eines Neustarts bittet – aufgrund der doch erheblichen Änderungen ist Microsoft derzeit nicht in der Lage, die Umstellung in einer laufenden Instanz von Visual Studio durchzuführen.

Im nächsten Schritt lässt sich der Options-Dialog öffnen, was zum Aufscheinen des in Abbildung 13 gezeigten Fensters führt.

Abb. 13: Visual Studio Code sendet Grüße an die Vollversion

Die oben links eingeblendete Textbox erlaubt dann das Eingeben von Strings, die Visual Studio zur Einschränkung der angezeigten Optionsvielfalt heranzieht.

Interessant ist allerdings auch, dass Visual Studio bei Cursorberührung ein Zahnrad mit zusätzlichen Einstellungen zur jeweiligen Option anbietet. Wer dieses anklickt, bekommt ein Menü mit zwei Optionen eingeblendet: Neben einem bequemen Weg zum Wiederherstellen der von Microsoft vorgegebenen Standardeinstellungen ist es hier auch möglich, Feedback an Microsoft zu schicken. Besonders wünscht man sich in Redmond dabei Hinweise dazu, wie sich die Beschreibung der jeweiligen Funktion entwickler- bzw. benutzerfreundlicher gestalten lassen würde.

Vereinheitlichungen und QOL-Verbesserungen für GUI-Entwickler

Im Umfeld des Autors gibt es einige Beratungsunternehmen, die den Umstieg auf Visual Studio 2022 verweigert haben – Ursache dafür ist die schlechtere Integration des Windows-Forms-Designers in die neue Variante der IDE. Ursache dafür ist, dass Visual Studio 2022 im Interesse der besseren Performance als 64-Bit-Applikation vorliegt und dementsprechend naturgemäß mit dem 32-Bit-Forms-Designer nur wenig anzufangen weiß.

Microsoft begegnet diesem Problem seit einiger Zeit mit einem externen Windows-Forms-Designer, der sein nützliches Tun abseits des Hauptprozesses bewerkstelligt. In der aktuellen Version nahm Microsoft Verbesserungen vor, um dem laut Dokumentation nach wie vor weit von Featureparität entfernten Editor eine höhere Performance zu ermöglichen.

Neuerung Nummer zwei betrifft eine als „Live Property Explorer“ bezeichnete Funktion: Dieses bei der Arbeit mit gewöhnlichen XAML-Dateien liebgewonnene Feature steht ab sofort auch dann zur Verfügung, wenn das vorliegende Programm auf Basis des neuen MAUI-GUI-Stacks entsteht.

Während dem Debuggen einer MAUI-Applikation lässt sich das Werkzeug durch Drücken von Debug | Windows | Live Property Explorer aktivieren. Es verhält sich dann im Allgemeinen so, wie man es als Entwickler:in von der gewöhnlichen Version der IDE erwarten würde.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Auf Jagd nach Featureparität für ARM

Dass Aktualisierungen von Visual Studio seit jeher mit einem wahren Sperrfeuer von Updates der zugrunde liegenden Komponenten einhergehen, wird erwartet – die Installation von Visual Studio setzt nun beispielsweise das .NET Framework in Version 4.7 voraus. Besonders interessant ist in diesem Zusammenhang aber die (an sich seit einiger Zeit zur Verfügung stehenden) Variante der integrierten Entwicklungsumgebung für Workstations, die auf ARM-Prozessoren basieren.

Bisher waren Nutzer:innen dieser neuartigen Variante des Produkts von der Verwendung der als SQL Server Developer Tools (SSDT) bezeichneten Komponente für die SQL-Datenbankanalyse ausgeschlossen – ein Problem, das Microsoft nun behoben hat. In der Dokumentation weisen die Redmonder allerdings darauf hin, derzeit noch keine vollständige Featureparität erreicht zu haben – einige fortgeschrittene Features stehen also nach wie vor nur auf X64-Stationen zur Verfügung.

Allgemeine Verbesserungen der Lebensqualität für C++ und C#

Wer viele Codedateien gleichzeitig geöffnet hält, lernt irgendwann das Scrollen durch die Tab-Liste. Microsoft verbessert diesen Teil des Systems insofern, als es nun auf Wunsch auch möglich ist, mehrere Zeilen mit Tab-Headern auf dem Bildschirm zu halten.

Nutzer:innen der Spiele-Engine Unreal bekommen eine Erweiterung von IntelliSense serviert: Die Codevervollständigung interagiert nun auch mit per Unreal-Header-Tool generierten Dateien, was die Reichhaltigkeit der angezeigten Auto-Completion-Vorschläge erhöht.

Mindestens ebenso hilfreich ist die Möglichkeit, die von Visual Studio als Standard verwendete CMake-Version durch eine hauseigene Variante zu ersetzen. Hierzu findet sich unter Tools | Options | CMake | General ein Einstellungsfenster, in dem sich der von der Toolchain verwendete Pfad zur CMake-EXE austauschen oder anpassen lässt.

Besondere Erwähnung verdient auch die Fähigkeit der IDE, Structs dynamisch (also ohne Rekompilation) in Form eines Speicherlayouts anzuzeigen (Abb. 14).

Abb. 14: Visual Studio zeigt die Struktur des Speicherlayouts an

Aufseiten der C#-Entwicklung gibt es Snippetsupport für Razor: Dabei handelt es sich um ein an die in Palm OS implementierten Shortcuts erinnerndes Feature, das das Einpflegen von hauseigenen Codestücken optimiert.

Außerdem steht mit Blazor Scaffolding ein vergleichsweise umfangreicher Codegenerator zur Verfügung. Er erzeugt auf Data Binding basierende Views automatisiert, was bei Einarbeitung und Umstieg Zeit spart.

Integration ins Microsoft-Ökosystem

Im Rahmen der Ankündigung von Visual Studio 17.10 Preview 1 betonte Microsoft mehrfach die neuen Möglichkeiten des Teams SDK: Wir haben es in unserem oben erwähnten Artikel [1] im Detail beschrieben, weshalb an dieser Stelle nur ein knapper Verweis ausreichen muss.

Interessant ist, dass Microsoft die Hoffnung auf das Verkaufen der kostenpflichtigen Versionen Visual Studio Professional und Visual Studio Enterprise auch nach dem durchschlagenden Erfolg der kostenfreien Community-Variante nicht komplett aufgegeben hat.

Der beste Beleg für diese Annahme ist das unter [5] bereitstehende und in Abbildung 15 gezeigte Portal. Besitzer:innen kostenpflichtiger Varianten bekommen von Microsoft dort einen kompakten Überblick über alles, was sie an Side Benefits im Rahmen ihres Abonnements angeboten bekommen.

Abb. 15: Der Microsoft-Weihnachtsmann bringt Geschenke

Fazit

Microsofts Entscheidung, mit Visual Studio und Visual Studio Code interne Konkurrenz zu erzeugen, trägt für die Entwicklerschaft reiche Dividende. Die Preview-Versionen von Visual Studio greifen Entwickler:innen an mehrerlei Stelle unter die Arme, ohne dabei lästig zu werden. Die Empfehlung des Autors für das neueste Produkt aus Redmond sei deshalb jederzeit gegeben.

 

Links & Literatur

[1] https://entwickler.de/reader/reading/windows-developer/4.2023/65722e67d1045fe264c26ad9?searchterm=visual%20studio

[2] https://github.com/features/copilot?utm_source=vscom&utm_medium=hero&utm_campaign=cta-get#pricing

[3] https://learn.microsoft.com/en-us/visualstudio/productinfo/vs-roadmap

[4] https://learn.microsoft.com/de-de/visualstudio/extensibility/visualstudio.extensibility/visualstudio-extensibility?view=vs-2022

[5] https://my.visualstudio.com

The post Neuerungen in Visual Studio appeared first on BASTA!.

]]>
.NET 9 Preview: Neuerungen und Änderungen in der Version 9.0 https://basta.net/blog/neuerungen-in-dotnet-9/ Mon, 22 Apr 2024 08:15:46 +0000 https://basta.net/?p=92934 Microsoft hat die Vorschaureihe zu .NET 9.0 gestartet und wie jedes Jahr berichtet der DOTNET-DOKTOR über die Entwicklung

The post .NET 9 Preview: Neuerungen und Änderungen in der Version 9.0 appeared first on BASTA!.

]]>
.NET 9.0 soll im November 2024 als Nachfolger des im November 2023 als stabile Version veröffentlichen .NET 8.0 erscheinen. Als ungerade Versionsnummer wird .NET 9.0 wieder nur Standard Term Support (STS) für 18 Monate erhalten, also nur bis Mai 2026, während der Support für .NET 8.0 noch bis November 2026 läuft.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

.NET 9.0 Preview 1 ist am 13. Februar 2024 erschienen und am 12. März 2024 folgte Preview 2. Die Vorschauversionen stehen zum Download unter [1] bereit.

An der Grundstruktur (drei Runtimes, ein SDK) hat sich nichts geändert (Abb. 1) und auch die unterstützten Plattformen sind gleichgeblieben. Laut Abbildung 1 sind weiterhin die Sprachversionen C# 12.0, F# 8.0 und Visual Basic .NET 16.9 enthalten – neue Sprachfeatures gibt es demnach bisher noch nicht.

 

.NET-9.0-Downloads

Abb. 1: .NET-9.0-Downloads

 

Neuerungen im .NET 9.0 SDK

Das Befehlszeilenwerkzeug dotnet test konnte bisher automatisierte Tests für ein Projekt mit mehreren .NET-Versionen nacheinander ausführen (z. B. <TargetFrameworks>net8.0;net9.0</TargetFrameworks> in der Projektdatei des Testprojekts). Eine neue Funktion in .NET 9.0 Preview 2 ist, dass diese Ausführung für verschiedene .NET-Versionen nun parallel erfolgt (Abb. 2). Außerdem nutzt dotnet test den Terminal Logger, der in .NET 8.0 eingeführt wurde und eine übersichtlichere Darstellung bietet. Das hat insbesondere positive Auswirkungen auf die Anzeige der Testergebnisse sowohl während als auch nach der Testausführung (Abb. 3). Bei Bedarf kann die Parallelisierung verhindert werden, indem man die MSBuild-Eigenschaft TestTfmsInParallel auf false setzt.

 

Parallele Testausführung für mehrere .NET-Versionen

Abb. 2: Parallele Testausführung für mehrere .NET-Versionen

 

Übersichtlichere Testergebnisse

Abb. 3: Übersichtlichere Testergebnisse

 

Nutzer:innen von .NET-SDK-basierten Tools haben nun die Möglichkeit, mit der Option —allow-roll-forward ein Werkzeug auf einer neueren .NET-Hauptversion laufen zu lassen, falls die .NET-Laufzeitumgebung, für die das Werkzeug eigentlich kompiliert wurde, nicht auf dem lokalen System verfügbar ist. Diese neue Option kann sowohl bei der Installation mit dotnet tool install als auch beim Ausführen mit dotnet tool run verwendet werden. Es muss jedoch darauf hingewiesen werden, dass aufgrund von Breaking Changes zwischen .NET-Hauptversionen keine Garantie besteht, dass das Werkzeug ordnungsgemäß auf einer neueren .NET-Laufzeitumgebung funktioniert.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Neuerungen in der .NET-Klassenbibliothek

In der Basisklassenbibliothek wurden neue LINQ-Operatoren CountBy() und Index() hinzugefügt. CountBy() vermeidet den bisher notwendigen Einsatz von GroupBy() in Fällen, in denen nach Häufigkeit gruppiert werden soll (Listing 1). Index() liefert für ein IEnumerable<T> ein Tupel mit dem Wert und dem laufenden Index (0 bis n) zurück (Listing 2). Abbildung 4 zeigt die Ausgabe, bei der die Zusatzbibliothek SpectreConsole für die Balkenausgabe zum Einsatz kommt [2].

 

Listing 1: Ermitteln der häufigsten Vorname mit CountBy()

#region ---------------- Demodaten erstellen
  int count = 10000;
  CUI.H3($"Generiere {count} Demodaten...");
  Randomizer.Seed = new Random(42); // für gleichbleibende Testdaten!
  var personFaker = new AutoFaker<Person>("de")
    .RuleFor(fake => fake.ID, fake => fake.Random.Int(0))
    .RuleFor(fake => fake.Givenname, fake => fake.Name.FirstName())
    .RuleFor(fake => fake.Surname, fake => fake.Name.LastName())
    .RuleFor(fake => fake.Birthday, fake => fake.Date.Past(100).Date);
  List<Person> personSet = new();
  for (int i = 0; i < count; i++)
  {
    var p = personFaker.Generate();
    personSet.Add(p);
  }
  Console.WriteLine("Anzahl Personen: " + personSet.Count);
#endregion

#region ---------------- Auswertung
  CUI.H3("Auswertung:");
  // bisherige Implementierung mit GroupBy() + Select()
  var groups1 = personSet
    .GroupBy(info => info.Givenname) // Gruppieren nach GivenName
    .Select(group => new // Zählen
    {
      Key = group.Key,
      Value = group.Count()
    });

  Console.WriteLine("10 häufigste Vornamen im Datenbestand:");
  var groups1top10 = groups1.OrderByDescending(x => x.Value).Take(10);
  // Ausgabe mit SpectreConsole
  AnsiConsole.Write(new BarChart()
    .Width(60)
    .AddItems(groups1top10, (item) => new BarChartItem(
      item.Key, item.Value, Color.Yellow)));

  // Neue Implementierung mit CountBy()
  var groups2 = personSet
    .CountBy(x => x.Givenname); // Gruppieren nach Häufigkeit des GivenName

  Console.WriteLine("10 häufigste Vornamen im Datenbestand:");
  var groups2top10 = groups2.OrderByDescending(x => x.Value).Take(10);
  // Ausgabe mit SpectreConsole
  AnsiConsole.Write(new BarChart()
    .Width(60)
    .AddItems(groups2top10, (item) => new BarChartItem(
      item.Key, item.Value, Color.Yellow)));

#endregion

 

Ausgabe der Ermittlung der häufigsten Vornamen mit CountBy()

Abb. 4: Ausgabe von Listing 1

 

Listing 2: Einsatz der neuen Methode Index()

Console.WriteLine("10 häufigste Vornamen im Datenbestand:");
// bisher schon möglich:
foreach ((int index, KeyValuePair<string, int> group) in groups2top10.Select((wort, index) => (index, wort)))
{
  CUI.LI($"Platz {index + 1}: Vorname {group.Key} kommt {group.Value} vor.");
}

Console.WriteLine("10 häufigste Vornamen im Datenbestand:");
// neu ab .NET 9.0:
foreach ((int index, KeyValuePair<string, int> group) in groups2top10.Index())
{
  CUI.LI($"Platz {index + 1}: Vorname {group.Key} kommt {group.Value} vor.");
}

 

Die PriorityQueue-Klasse, die in .NET 6.0 eingeführt wurde, verfügt nun über eine Remove()-Methode, mit der Elemente entfernt werden können, auch wenn sie nicht an der Reihe sind. Remove() erweitert als Parameter das zu entfernende Element. Remove() liefert drei Werte zurück: Als Rückgabewert einen Boolean-Wert, der anzeigt, ob das Element vorhanden war und entfernt werden konnte. Im zweiten und dritten Methodenparameter kommt via out das entfernte Element sowie seine Priorität zurück.

 

Listing 3: Einsatz der Methode Remove in der Klasse PriorityQueue

public class FCL_Collections
{
  public void Run()
  {
    CUI.H2(nameof(FCL_Collections));

    var q = new PriorityQueue<string, int>();
    q.Enqueue("www.dotnet-doktor.de", 20);
    q.Enqueue("www.dotnet8.de", 2);
    q.Enqueue("www.IT-Visions.de", 10);
    q.Enqueue("www.dotnet-lexikon.de", 30);
    q.Enqueue("www.dotnet9.de", 1);

    Console.WriteLine(q.Count);

    CUI.H3("Entferne vorhandenes Element");
    bool b1 = q.Remove("www.dotnet8.de", out string e1, out int priority1);
    if (b1) Console.WriteLine($"Element {e1} mit Priorität {priority1} wurde entfernt!");
    else Console.WriteLine("Element nicht gefunden");

    CUI.H3("Versuch, nicht vorhandenes Element zu entfernen");
    bool b2 = q.Remove("www.dotnet7.de", out string e2, out int priority2);
    if (b2) Console.WriteLine($"Element {e2} mit Priorität {priority2} wurde entfernt!");
    else Console.WriteLine("Element nicht gefunden");

    CUI.H3($"Alle Elemente {q.Count} auflisten"); // 4
    var count = q.Count;
    for (int i = 0; i < count; i++)
    {
      var current = q.Dequeue();
      Console.WriteLine(i + ": " + current);
    }
    Console.WriteLine($"Verbliebene Elemente: {q.Count}"); // 0
  }
}

 

Mit .NET 9.0 ist es nun auch wieder möglich, zur Laufzeit erzeugte Assemblies im Dateisystem zu persistieren, indem die Methode Save() in der Klasse AssemblyBuilder verwendet wird (Listing 4). Diese Funktion war bereits im klassischen .NET Framework verfügbar (siehe Dokumentation zu Save() unter [3]). Im modernen .NET konnten zur Laufzeit erzeugte Assemblies bisher nur im RAM gehalten werden.

 

Listing 4: Speichern einer dynamisch erzeugten Assembly im Dateisystem

public void CreateAndSaveAssembly()
{
  CUI.H2(nameof(CreateAndSaveAssembly));

  string assemblyPath = Path.Combine(System.AppContext.BaseDirectory, "Math.dll");

  AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(new AssemblyName("Math"), typeof(object).Assembly);
  TypeBuilder tb = ab.DefineDynamicModule("MathModule").DefineType("MathUtil", TypeAttributes.Public | TypeAttributes.Class);

  MethodBuilder mb = tb.DefineMethod("Sum", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]);
  ILGenerator il = mb.GetILGenerator();
  il.Emit(OpCodes.Ldarg_0);
  il.Emit(OpCodes.Ldarg_1);
  il.Emit(OpCodes.Add);
  il.Emit(OpCodes.Ret);

  tb.CreateType();
  Console.WriteLine("Speichere Assembly unter: " + assemblyPath);
  ab.Save(assemblyPath); // Speichern ins Dateisystem oder einen Stream
}

 

 

Die .NET-Debugger in Visual Studio und Visual Studio Code zeigen nun die Inhalte von Dictionary-Klassen während des Debuggings deutlich übersichtlicher an (Abb. 5).

 

Verbesserte Debugger-Ansicht von Dictionary-Klassen in .NET 9.0

Abb. 5: Verbesserte Debugger-Ansicht von Dictionary-Klassen in .NET 9.0 [4]

 

Microsoft liefert ab .NET 9.0 eine Implementierung des KECCAK Message Authentication Code (KMAC) des US-amerikanischen National Institute of Standards and Technology (NIST) für kryptographische Funktionen [5].

 

Verbesserungen in System.Text.Json 9.0

Die Version 9.0 der JSON-Bibliothek System.Text.Json ermöglicht es, die Einrückung in der JSON-Zeichenkette über die JsonSerializerOptions anzupassen. Hierfür wurden zwei neue Einstellungen eingeführt: IndentCharacter und IndentSize. Listing 5 zeigt ein Beispiel. Als IndentCharacter werden aber nur ein Leerzeichen ‘ ‘ oder ein Tabulator ‘\t’ unterstützt.

Durch die Verwendung von JsonSerializerOptions.Web (Listing 5) können Entwickler:innen nun auf schnelle Weise dieselben Einstellungen für die JSON-Serialisierung und -Deserialisierung wählen, die standardmäßig im ASP.NET-Core-Web-API verwendet werden.

 

Listing 5: Setzen von IndentCharacter und IndentSize für die JSON-Serialisierung mit System.Text.Json

internal class FCL9_JSON
{
  public void Run()
  {
    CUI.H2(nameof(FCL9_JSON));

    CUI.H3("IndentCharacter und IndentSize")

    var consultant = new Consultant() { ID = 42, FullName = "Holger Schwichtenberg", Salutation = "Dr.", PersonalWebsite = "www.dotnet-doktor.de" };

    var options = new JsonSerializerOptions
    {
      WriteIndented = true,
      IndentCharacter = ' ',
      IndentSize = 2,
    };
    var json1 = JsonSerializer.Serialize(consultant, options);

    Console.WriteLine(json1);
    //liefert:
    //{
    //  "Languages": [],
    //  "PersonalWebsite": "www.dotnet-doktor.de",
    //  "ID": 42,
    //  "FullName": "Holger Schwichtenberg",
    //  "Salutation": "Dr.",
    //  "Address": null
    //}

    CUI.H3("JsonSerializerOptions.Web");
    var json2 = JsonSerializer.Serialize(consultant, JsonSerializerOptions.Web);
    Console.WriteLine(json2);
    //liefert:
    //{"languages":[],"personalWebsite":"www.dotnet-doktor.de","id":42,"fullName":"Holger Schwichtenberg","salutation":"Dr.","address":null}
  }
}

 

Verbesserungen in Entity Framework Core 9.0

Der Object-relational Mapper Entity Framework Core hat in Preview 1 und 2 bereits eine Reihe kleinerer Verbesserungen implementiert. Verbesserung in der Modellerstellung sind:

 

  • Bei Schlüssel- und Indexspalten können nun für den Microsoft SQL Server Angaben zum Füllfaktor gemacht werden. Der Füllfaktor bestimmt den Prozentsatz des Platzes auf eine Seite in der Datenbankdatei, der mit Daten gefüllt werden soll, wobei der Rest auf jeder Seite als freier Platz für zukünftiges Wachstum reserviert wird. Der Füllfaktor kann nun in Entity Framework Core 9.0 via HasFillFactor() für einfache und zusammengesetzte Schlüsselspalten und Indexe gesetzt werden (Listing 6).
  • Bei der Erstellung von Autowerten mit Sequenzen kann nun das Caching via UseCache() und UseNoCache() konfiguriert werden, z. B.
modelBuilder.HasSequence<int>("NameDerSequence")
  .HasMin(10).HasMax(255000)
  .IsCyclic()
  .StartsAt(11).IncrementsBy(2)
  .UseCache(3);
  • Die Umwandlung einer normalen Tabelle in eine temporäre Tabelle ist nun mit einer stark verkürzten Befehlsfolge in den Schemamigrationen möglich. Siehe dazu [6].

 

Listing 6: Einsatz von HasFillFactor()

modelBuilder.Entity<User>()
  .HasKey(e => e.Id)
  .HasFillFactor(80);

modelBuilder.Entity<User>()
  .HasAlternateKey(e => new { e.Region, e.Ssn })
  .HasFillFactor(80);

modelBuilder.Entity<User>()
  .HasIndex(e => new { e.Name })
  .HasFillFactor(80);

modelBuilder.Entity<User>()
  .HasIndex(e => new { e.Region, e.Tag })
  .HasFillFactor(80);

 

Verbesserung bei der Übersetzung von LINQ in SQL sind:

 

  • Bei der Übersetzung von LINQ zu SQL fasst Entity Framework Core 9.0 nun die SQL-Befehle von verschachtelten LINQ-Abfragen zusammen (Listing 6, 7 und 8).
  • Entwicklerinnen und Entwickler können nun mit den .NET-Funktionen EF.Parameter() und EF.Constant() erzwingen, dass bei der Übersetzung von LINQ-Abfragen nach SQL ein Wert in der SQL-Abfrage als Parameter bzw. als Konstante übergeben wird. Bisher lag die Entscheidung für die Parametrisierung allein beim ORM. Nun ist sie beeinflussbar, um die Nutzung des Query-Cache des Datenbankmanagementsystems zu optimieren. EF.Constant() ist auch als ein Nachtrag zu .NET 8.0 verfügbar, der in Version 8.0.2 erschienen ist. Das ist ungewöhnlich, da Microsoft behauptet, beim modernen .NET dem Semantic Versioning zu folgen, welches bei neuen Funktionen eine Änderung der Versionsnummer an der zweiten Stelle vorsieht.
  • Bei der Übersetzung von Abfragen, die sich auf JSON-Spalten beziehen, werden nun bei OPENJSON…WITH nur noch die Teile der JSON-Zeichenkette ausgewertet, die für die Bedingung oder das Resultset notwendig sind.
  • Falls das Zieldatenbanksystem ein Microsoft SQL Server der neusten Version 2022 ist, verwendet Entity Framework Core 9.0 nun die neu eingeführten T-SQL-Funktionen LEAST() und GREATEST() bei der Übersetzung von Min() und Math.Max().
  • Die Methode ExecuteUpdate() bietet nun eine verkürzte Syntax in Verbindung mit komplexen Typen:
var newAddress = new Address("www.IT-Visions.de", 45257, "Essen");
await context.Stores
  .Where(e => e.Region == "Germany")
  .ExecuteUpdateAsync(s => s.SetProperty(b => b.ShippingAddress, newAddress));
  • Analog zur bereits in Entity Framework Core 2.0 eingeführten Materialisierungsmethode ToHashSet() bietet der ORM nun ein asynchrones Pendant: ToHashSetAsync().

 

Weitere Verbesserungen in Entity Framework Core 9.0 sind:

 

  • Die Entity-Framework-Core-Kommandozeilenwerkzeuge sollen nun weniger häufig eine Neukompilierung des Projekts erfordern.
  • Die Erstellung von Konventionen für die Modellerstellung hat Microsoft verschlankt, hierfür gibt es ein umfangreiches Beispiel in der Dokumentation [7].
  • In der „What’s new“-Dokumentation findet man darüber hinaus einen Hinweis auf eine neue Überladung der Methode Parse() in der Klasse HierarchyId, die in Entity Framework Core 8.0 eingeführt wurde. Eine HierarchyId-Instanz soll man nicht nur wie bisher aus einer Zeichenkette (z. B. /4/1/3/1/2/), sondern auch typsicherer auf Basis einer anderen HierarchyId erstellen können, z. B.:
var daisy = await context.Halflings.SingleAsync(e => e.Name == "Daisy");
var child1 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1), "Toast");
var child2 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 2), "Wills");
var child1b = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1, 5), "Toast");
  • Allerdings funktioniert das in Entity Framework Core 9.0 Preview 2 noch nicht, sondern kommt dann voraussichtlich in Preview 3.

 

Listing 7: Verschachtelte LINQ-Abfragen

var dotnetPosts = context
  .Posts
  .Where(p => p.Title.Contains(".NET"));

var results = dotnetPosts
  .Where(p => p.Id > 2)
  .Select(p => new { Post = p, TotalCount = dotnetPosts.Count() })
  .Skip(2).Take(10)
  .ToArray();

 

 

Listing 8: Übersetzung der LINQ-Abfragen in Listing 1 in zwei SQL-Abfragen in Entity Framework Core 8.0

SELECT COUNT(*)
FROM [Posts] AS [p]
WHERE [p].[Title] LIKE N'%.NET%'

SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] LIKE N'%.NET%' AND [p].[Id] > 2
ORDER BY (SELECT 1)
OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY

 

 

Listing 9: Übersetzung der LINQ-Abfragen in Listing 1 in eine einzige SQL-Abfrage in Entity Framework Core 9.0

SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata], (
  SELECT COUNT(*)
  FROM [Posts] AS [p0]
  WHERE [p0].[Title] LIKE N'%.NET%')
FROM [Posts] AS [p]
WHERE [p].[Title] LIKE N'%.NET%' AND [p].[Id] > 2
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY

 

Einige der Neuerungen in Entity Framework Core 9.0 Preview 1 und 2 stammen nicht von Microsoft-Entwickler:innen, sondern aus der Community, so zum Beispiel die Verbesserungen für Sequenzen, HasFillFactor(), ToHashSetAsync() und die weniger häufigen Kompilierungsvorgänge bei den Kommandozeilenwerkzeugen.

 

Verbesserungen für Blazor 9.0

In Webanwendung auf Basis von Blazor ist es jetzt möglich, Dependency Injection auch über den Konstruktor in einer Code-Behind-Datei in der Razor Component durchzuführen. Wie Listing 10 zeigt, können auch die in C# 12.0 eingeführten Primärkonstruktoren dafür verwendet werden.

 

Listing 10: Konstruktorinjektion in einer Code-Behind-Datei einer Blazor-Komponente

public partial class Counter(NavigationManager navigationManager, IJSRuntime js)
{
  private void NavigateHome() => navigationManager.NavigateTo("/");
  private string GetBlazorType
  {
    get
    {
      if (js.GetType().Name.Contains("Remote")) return "Blazor Server";
      if (js.GetType().Name.Contains("WebAssembly")) return "Blazor WebAssembly";
      return "Blazor Hybrid " + js.GetType().Name;
    }
  }
}

 

 

Bisher war es in Razor Components nur möglich, Dienste entweder durch @inject im Razor-Template oder durch die Annotation [Inject] auf einer Eigenschaft im Code zu injizieren.

Beim Interactive Server-side Rendering (alias Blazor Server) ist nun standardmäßig die Komprimierung in BlazorPack-Daten in der WebSockets-Verbindung aktiviert. Entwickler:innen können das jedoch bei Bedarf deaktivieren, indem sie DisableWebSocketCompression = true im Parameterobjekt von AddInteractiveServerRenderMode() setzen:

 

app.MapRazorComponents<App>()

  .AddInteractiveServerRenderMode(o => o.DisableWebSocketCompression = true);


Microsoft setzt nun standardmäßig die Content Security Policy (CSP) auf frame-ancestor: ‘self’. Das kann jedoch durch die Verwendung von ContentSecurityFrameAncestorsPolicy geändert werden:

 

app.MapRazorComponents<App>()
  .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy="'none'");

 

Weitere Neuerungen in ASP.NET Core 9.0a

In ASP.NET Core 9.0 SignalR ist Polymorphism für die Parameter von Operationen in ASP.NET-Core-SignalR-Hubs möglich, indem man die in System.Text.Json-Version 7.0 eingeführten Annotationen [JsonPolymorphicAttribute] und [JsonDerivedTypeAttribute] verwendet, die zu Metadaten (einem Type Discriminator) in JSON führen (z. B. “$type” : “PersonWithAge”). Listing 11 zeigt ein Einsatzbeispiel.

 

Listing 11: Polymorphisms bei ASP.NET Core SignalR

using System.Text.Json.Serialization;

namespace Blazor9;

[JsonPolymorphic]
[JsonDerivedType(typeof(PersonWithAge), nameof(PersonWithAge))]
[JsonDerivedType(typeof(PersonWithBirtday), nameof(PersonWithBirtday))]
public class PersonDTO
{
  public string? Name { get; set; }
}

public class PersonWithAge : PersonDTO
{
  public int Age { get; set; }
}

public class PersonWithBirtday : PersonDTO
{
  public DateTime Birthday { get; set; }
}

public class SignalRHub
{
  public int Search(PersonDTO person)
  {
    if (person is PersonWithAge)
    {
      // Suche Person mit Name und Alter
      // ...
    }
    else if (person is PersonWithBirtday)
    {
      // Suche Person mit Name und Geburtsdatum
      // ...
    }
    else
    {
      // Suche Person nur mit Name
      // ...
    }
    return 0; // ID = 0 -> nicht gefunden
  }
}


 

Bei der Verwendung von OAuth und OIDC ist es nun deutlich prägnanter, die Parameter anzupassen:

 

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
  {
    options.AdditionalAuthorizationParameters.Add("prompt", "login");
    options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});

Fast nichts Neues bei den GUIs

Bei .NET MAUI wurden bisher zahlreiche Fehlerbehebungen und kleinere Verbesserungen, wie beispielsweise die Unterstützung von animierten GIFs auf iOS, eingeführt. Die entsprechenden Release-Notes finden sich unter [8] und [9].

Informationen zu Verbesserungen bei Windows Forms oder der Windows Presentation Foundation (WPF) sind bisher nicht in den Release-Notes enthalten. Immerhin gab es jedoch im Februar ein Bekenntnis von Microsoft, weiterhin am Design für Windows Forms in Visual Studio zu arbeiten, wie unter [10] erklärt wird.

 

Geänderte Informationskanäle von Microsoft in der Kritik

In den letzten Jahren gab es zu jeder Vorschauversion des modernen .NET mehrere Blogeinträge im Microsoft .NET Blog [11]. Das galt bis einschließlich November 2023, dem Erscheinungstermin von .NET 8.0.

Zu den Previews von .NET 9.0 gibt es leider keine Blogeinträge mehr. Das ist Teil einer seit diesem Jahr veränderten Strategie bezüglich des .NET Blogs. Das .NET Blog soll sich laut einer Ankündigung von .NET Program Manager Rich Lander auf Beiträge über stabile .NET-Versionen konzentrieren [12].

Die Preview-Versionen will man jetzt stattdessen über einen neuen Diskussionsbereich auf GitHub anzukündigen [13]. Die einzelnen Neuerungen sind dann auch nicht dort, sondern in den verlinkten Release-Notes beschrieben, außerdem in „What’s New“-Dokumenten auf Microsoft Learn [14].

Allerdings sind die Neuerungen über mehrere Release-Notes-Dokumente verstreut und werden darin meist nur sehr knapp erwähnt. In den „What’s New“-Dokumenten zu .NET 9.0 [15], ASP.NET Core 9.0 [16], Entity Framework Core 9.0 [17] und .NET MAUI [18] sind die Neuerungen ausführlicher beschrieben. Allerdings fehlt in diesen Dokumenten die Trennung zwischen den verschiedenen Vorschauversionen und es finden sich in den Dokumenten (wie oben am Beispiel HierarchyId.Parse() gezeigt) auch Hinweise auf Features, die noch gar nicht in den aktuellen Preview-Versionen enthalten sind.

Microsofts neue Informationskanäle erschweren die Arbeiten derjenigen Entwicklerinnen und Entwickler, die am Puls der Zeit bleiben wollen und damit auch die Erstellung von journalistischen Fachberichten wie diesem. Die Kommentare auf GitHub [19] zeigen, dass die neue Strategie einigen Kundinnen und Kunden nicht gefällt: „confusing“, „badly done“ und „before it was even out was so much better“ heißt es in den Kommentaren. Ketzerische Reaktionen wie „so, is .net dead?“ gibt es auch. Mitarbeitende von Microsoft haben inzwischen mit Ankündigungen reagiert, die Darstellung zu überdenken. Jon Douglas schreibt: „I will make notes to ask our product and documentation teams to provide clear diffs“ [20].

Der einzige Blogbeitrag, der zu .NET 9.0 erschienen ist, trägt den Titel „Our Vision for .NET 9“ [21]. Ziele für .NET 9.0 sind demnach Verbesserungen für Cloud- und KI-Anwendungen, was angesichts der Microsoft-Unternehmensstrategie nicht überraschend ist. Welche Features genau dazu gehören, bleibt offen. Es bleibt bei schwammigen Aussagen wie „Entwickler werden großartige Bibliotheken und Dokumentationen finden, um mit OpenAI- und OSS-Modellen (gehostet und lokal) zu arbeiten. Wir werden weiterhin an der Zusammenarbeit an Semantic Kernel, OpenAI und Azure SDK arbeiten, um sicherzustellen, dass .NET-Entwickler beim Erstellen intelligenter Anwendungen eine erstklassige Erfahrung haben“.

Was schon zuvor angekündigt wurde, sind die Erweiterungen der Einsatzgebiete des Ahead-of-Timer-Compilers. Er funktioniert in .NET 7.0 und .NET 8.0 für Konsolenanwendungen, Minimal WebAPIs, gRPC-Dienste und Worker Services –, aber auch dort mit großen Einschränkungen (z. B. ist Entity Framework Core nicht nutzbar). Der Blogeintrag lässt offen, was konkret der AOT-Compiler in .NET 9.0 mehr können wird.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Roadmaps und Backlogs

Auch die Liste der geplanten Features ist, anders als bei .NET 5.0 bis .NET 8.0, noch nicht sehr ausführlich. Am meisten hat derzeit das ASP.NET-Core-Team veröffentlicht (Abb. 6). Dazu gehören:

 

  • die bisher fehlende OAuth-Authentifizierung mit der Microsoft Identity Platform in der Projektvorlage Blazor Web App
  • eine einfachere Möglichkeit, den aktuellen Render-Modus bei Blazor zu ermitteln (hier muss man bisher die Typnamen der injizierten Instanzen von NavigationManager oder IJSRuntime abfragen)
  • Übergabe des Zustandes nach dem Pre-Rendering an den Client
  • Verbesserung der Multi-Threading-Unterstützung in Blazor WebAssembly
  • eine verbesserte Startzeit von Blazor-WebAssembly-Anwendungen, die im Browsercache liegen

 

Pläne für ASP.NET Core 9.0 und Blazor 9.0

Abb. 6: Pläne für ASP.NET Core 9.0 und Blazor 9.0 [22]

 

Auch das C#-Team gibt unter [23] wie immer frühe Einblicke. Wenig zu lesen gibt es hingegen bei .NET MAUI [24]. Nichts Aktuelles findet sich bei Windows Forms (Roadmap ist zwei Jahre alt, siehe [25]) und WPF (drei Jahre alte Roadmap, siehe [26]).

Das Entity Framework Core-Entwicklungsteam schreibt Stand 17. März 2024 nur „Coming soon“ [27]. Eine Linkliste auf verschiedene Roadmaps und Backlogs finden Sie unter [28].

 

Dr. Holger Schwichtenberg – alias der „DOTNET-DOKTOR“ – ist Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1 500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland. Seit 1998 ist er ununterbrochen Sprecher auf sämtlichen BASTA!-Konferenzen. Von Microsoft wird er seit 20 Jahren als MVP ausgezeichnet.

Mail: [email protected]

Twitter: @dotnetdoktor

Web: www.dotnet-doktor.de

 

Links und Literatur

[1] https://dotnet.microsoft.com/en-us/download/dotnet/9.0

[2] https://spectreconsole.net

[3] https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilder.save?view=netframework-4.8.1

[4] https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-8.0

[5] https://csrc.nist.gov/pubs/sp/800/185/final

[6] https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew

[7] https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#make-existing-model-building-conventions-more-extensible

[8] https://github.com/dotnet/maui/releases/tag/9.0.100-preview.1.9973

[9] https://github.com/dotnet/maui/releases/tag/9.0.0-preview.2.10293

[10] https://devblogs.microsoft.com/dotnet/winforms-designer-64-bit-path-forward/

[11] https://devblogs.microsoft.com/dotnet/

[12] https://github.com/dotnet/core/discussions/9131

[13] https://github.com/dotnet/core/discussions

[14] https://learn.microsoft.com/en-us/dotnet/whats-new/

[15] https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview

[16] https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-8.0

[17] https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew

[18] https://learn.microsoft.com/en-us/dotnet/maui/whats-new/dotnet-9?view=net-maui-8.0

[19] https://github.com/dotnet/core/discussions/9217

[20] https://github.com/dotnet/core/discussions/9217#discussioncomment-8765352

[21] https://devblogs.microsoft.com/dotnet/our-vision-for-dotnet-9/

[22] https://github.com/dotnet/aspnetcore/issues/51834

[23] https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md

[24] https://github.com/dotnet/maui/wiki/Roadmap

[25] https://github.com/dotnet/winforms/blob/main/docs/roadmap.md

[26] https://github.com/dotnet/wpf/blob/main/roadmap.md

[27] https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/plan

[28] https://github.com/dotnet/core/blob/main/roadmap.md

The post .NET 9 Preview: Neuerungen und Änderungen in der Version 9.0 appeared first on BASTA!.

]]>
Warum Agile Retrospektiven oft scheitern: Lösungen für mehr Erfolg https://basta.net/blog/agile-retrospektiven-verbessern/ Mon, 18 Mar 2024 07:30:40 +0000 https://basta.net/?p=92886 Regelmäßige Retrospektiven sind ein fester Bestandteil agiler Arbeitsweisen. Doch oft verpuffen diese Meetings im Nichts, ohne dass echte Verbesserungen erzielt werden. In diesem Artikel erfahren Sie, warum dies passiert und wie Sie Ihre Retrospektiven effektiver gestalten können.

The post Warum Agile Retrospektiven oft scheitern: Lösungen für mehr Erfolg appeared first on BASTA!.

]]>
Wenn Retrospektiven verpuffen

Konzentriert findet Inspect and Adapt oft im Rahmen von Retrospektiven statt. Teammitglieder kommen alle paar Wochen für teils mehrere Stunden zusammen. Es wird besprochen, es wird vorgeschlagen, es wird vereinbart. Und dann?

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Gemeinsam an Verbesserung arbeiten ist ein hohes Gut, das der Wertschöpfung dient. Bessere Prozesse, bessere Tools und Psychological Safety zahlen alle auf die Unternehmensziele ein – wenn sie denn erreicht werden. Der wichtigste Grundsatz beim agilen Arbeiten ist Inspect and Adapt. Um diesem Prinzip der kontinuierlichen Verbesserung gerecht zu werden, muss die Arbeit transparent sein und es muss eine offene Kommunikation herrschen. Inspect and Adapt erstreckt sich auf alle Bereiche – ganz gleich, ob es sich dabei um Prozesse, Qualität, Quantität oder Kommunikation handelt.

Man müsste mal … In Konjunktiven gefangen ist es schwierig, die Umsetzungsebene zu erreichen und konkret Antipatterns in Projekten anzugehen. Retrospektiven (auch Retros genannt), richtig angewendet, sind gerade im agilen Umfeld das Mittel der Wahl, um besser zu werden. Wenn ich als Coach neu zu Teams dazukomme, schaue ich gerne, ob es eine Art Dokumentation zu stattgefundenen Retrospektiven gibt. Wenn ja, dann schaue ich nach Action Points, die festgehalten wurden und was damit passiert ist. In manchen Fällen wird den Action Points nachgegangen und eine schrittweise Verbesserung findet statt oder lässt sich vermuten. Jedoch sehe ich ebenfalls seitenweise offene Action Points – Vorhaben, die gemeinsam festgehalten, jedoch nicht umgesetzt wurden. Wie kann das sein? Es gab doch die besten Intentionen.

Ursachen einer Retro-Verpuffung

Was sind die Ursachen für so eine fehlende Umsetzung? Unsichtbare Dokumentation. Was für einen Stellenwert hat eine Zusammenfassung für das Team? Liegt sie z. B. in einem Confluence-Bereich, der von den Teammitgliedern eher selten bis gar nicht frequentiert wird? Natürlich gilt auch bei einer Retro nur so viel wie nötig und so wenig wie möglich zu dokumentieren, denn die Produktion von Dokumenten macht schließlich nicht agiler.

Tools sind hier auch weniger die Lösung als Mittel zum Zweck. Nach dem Kanban-Prinzip „Da anfangen, wo man ist“ schaue ich gerne, was Teams bisher wirklich nutzen. Manchmal muss erst Ordnung reingebracht werden, aber meistens gibt es frequentierte Bereiche, die sich auch für Retro-Dokus eignen. Manchmal reicht es, einfach die Verbesserungsvorhaben direkt im Backlog zu platzieren und einzuplanen.

Untergang im Alltag

Allzu hoher Produktionsdruck oder konstantes Firefighting lassen den Fokus von Verbesserung abschweifen bzw. den notwendigen Fokus erst gar nicht zu. Wenn nur hinterhergerannt wird, ist es ausgeschlossen, die Strecke zu gestalten. Es wird lediglich reagiert und die gegebenen Umstände im schlimmsten Fall als normal angesehen. Die Ursachen können vielfältig sein. Eine volatile Systemlandschaft, die mit Mühe und Not am Leben erhalten wird, eine Technical-Debt-Kakophonie, chronische Unterbesetzung, fehlende Priorisierung, unklare Entwicklungsprozesse und vieles mehr.

Jedes noch so wohlgemeinte Vorhaben kann nur schwer oder gar nicht stattfinden, wenn es schlicht und einfach keine Zeit gibt, um Impediments abzubauen. Also einfach Zeit schaffen – schön wäre es! Die ersten zarten Schritte von Transparenz und einer Arbeitsplanung sind sicherlich ein guter Weg, um mit der Realität umzugehen.

Fehlende Nachhaltigkeit

Wie wird mit Action Points umgegangen? Manchmal fehlt einfach, dass im Team daran erinnert wird, dass Verbesserungsmaßnahmen stattfinden sollen. Teams, die noch auf dem Weg der Reife sind, benötigen mehr Unterstützung und müssen noch Erfahrungen machen, die dann erst in die Arbeitsweisen übergehen können. So finde ich mich als Coach dann häufiger in der Rolle, Fragen zu stellen und dadurch Lösungen anzustoßen. Gerne biete ich aber auch „Good Practices“ an, z. B. die Priorisierung von Retro-Vorhaben im Backlog. Was für das eine Team funktioniert, muss jedoch keine Allgemeingültigkeit haben.

Die Ownership von Action Points zahlt ebenfalls auf Nachhaltigkeit ein. Während das Pull-Prinzip und Cross-Funktionalität hohe Güter sind, kann es trotzdem sinnvoll sein, Ownership für solche Vorhaben frühzeitig explizit zu machen. Der vermeintliche Wert von Retro Action Points kann für manche Teammitglieder im ersten Moment durchaus unklar sein, dann ist der Griff zu produktiven Themen im Backlog naheliegender.

Unkonkrete Vorhaben sind also schwer greifbar. Gibt es jetzt eine Aufgabe, die sich jemand nimmt, oder gibt es in der Wahrnehmung nur eine vage Aussage zu einem Missstand? Timeboxing in Retrospektiven helfen mit der Strukturierung, jedoch helfen sie wenig, wenn am Ende nichts Greifbares rauskommt. Explizite Formulierungen und sogar Akzeptanzkriterien schaffen hier Klarheit und forcieren Bewegung.

 

Einflussmöglichkeiten

Eine gute Übung für Teams ist es, sich folgende Frage zu stellen: „Was liegt in unserer Macht, das wir ändern können?“ Im Team und auf Teamebene ist es in der Regel einfacher, kleine Schritte in Richtung Verbesserung zu gehen. Das Maß an Einfluss auf andere Ebenen schwindet in der Regel auf dem Weg nach oben bzw. nach außen. „Circles of Influence“ ist ein Konzept, das durch Visualisierung hilft, Bereiche zu erkennen, die mehr Aufmerksamkeit benötigen. Im Grunde geht es um eine Dreiteilung. In der äußeren Hülle, dem „Circle of Concern“ oder „Kreis der Bedenken“, befinden sich Inhalte, auf die kein Einfluss genommen werden kann. Der „Circle of Influence“ oder „Einflussbereich“ ist der Bereich, auf dem bedingt etwas bewegt werden kann. Auf den „Circle of Control“ oder „Kreis der Kontrolle“ kann unmittelbarer Einfluss genommen werden.

Sicherlich gibt es immer wieder mal bedenkenschaffende Einflussfaktoren, wie z. B. einen scheußlichen Produktschnitt, der Antipattern per Design etabliert. Trotzdem ist es sicherlich sinnvoll, Transparenz für Impediments zu schaffen, die nur anderswo gelöst werden können. Teamentwicklung und Organisationsentwicklung gehen zwar gerne Hand in Hand, jedoch in unterschiedlichen Geschwindigkeiten. Darauf zu warten und zu vertrauen, dass etwas passiert, ist nicht zielführend.

Das Format passt nicht zu den Teilnehmer:innen

Manche Teams blühen auf, wenn sie einfallsreiche Retros durchführen und gemeinsam feststellen, welcher Wind in den Segeln sie vorantreibt und welche Anker sie zurückhalten. Andere Teams möchten nur Real Talk. Gezieltes Adressieren von Umständen mit klaren, schnellen Vorhaben ohne großes Check-in und anregenden Metaphern. Beides hat seine Berechtigung und ist in der richtigen Kombination zielführend. Wenn sich jedoch nur ein wie auch immer geratener Hammer im Werkzeugkoffer befindet, besteht die große Gefahr, mit den Retros eher zu traktieren, als etwas voranzubringen.

Gewöhnungseffekte

Beim Gewöhnungseffekt geht es um emotionale Erschöpfung als Reaktion auf frustrierende Umstände oder Themen. Bei dauerhaft ungelösten Problemen ohne wahrnehmbare Verbesserungen kann ein Resignationseffekt eintreten. Betroffene möchten sich einfach nicht mehr mit bestimmten Themen beschäftigen. Solche Umstände können dann als normal empfunden werden. Neue Teammitglieder lernen diese Dysfunktion als normal und unumstößlich kennen, was leider einen nachhaltenden Einfluss auf die Unternehmenskultur hat.

Wenn also Retros verpuffen, dann sinkt ihr Stellenwert und zahlt sogar auf das Gefühl ein, dass es zu viele sinnlose Meetings gibt (ein Retro-Dauerbrenner). Schon bildet sich ungewollt eine explosive Atmosphäre. Die Verpuffung möchte man in der Regel nicht herbeiführen, also wie vorbeugen? Letztendlich gibt es viele Faktoren, die auf den Erfolg von Retrospektiven einzahlen. Was schützt nun vor Verpuffung? Kurzgesagt: Realismus und Nachhaltigkeit. Ich habe ja bereits einige Ansätze genannt, aber wie so oft im Agilen kommt es auf die Situation an. Kleine erreichbare Stellschrauben sollten nicht übersehen werden bei dem Wunsch, am ganz großen Rad zu drehen.

SIE LIEBEN AGILE?

Entdecken Sie die BASTA! Tracks

Jahresrückblick

Zum Jahresende mache ich gerne eine „Geist-von-Weihnachten-Retrospektive“, in der wir auf die Retros des ablaufenden Jahres zurückblicken und schauen, welche Vorhaben wir festgehalten haben und ob bzw. wie wir sie umgesetzt haben. Das schafft ein Bewusstsein dafür, wie viel bewegt wurde und erinnert an Verbesserungen, die mittlerweile schon als selbstverständlich angenommen werden. Es wird gemeinsam bekräftigt, weiter dem Verbesserungsprinzip zu folgen und entsprechend den Erkenntnissen des Jahresrückblicks ggf. am Vorgehen nachjustiert.

Schaffung einer positiven Atmosphäre

Gerade aus einer Situation heraus, in der hoher Arbeitsdruck besteht, kann es für Teammitglieder schwierig sein, den Kontextwechsel hinzukriegen. Unter dem Damoklesschwert der Ticketflut oder kognitiv tief in der Umsetzung einer User Story zu sein, kann das Empfinden hinterlassen, durch die Retro aus etwas Wichtigerem gerissen zu werden. Es geht hierbei nicht um tatsächlich unternehmenskritische Ereignisse. Klar kann es auch mal Wichtigeres als eine Retrospektive geben. Unternehmenskritisch wird jedoch schon mal etwas weit gesteckt und bildet dann eine dauerhafte Dysfunktion, die am besten in einer Retro zu behandeln ist.

Wie eine positive Atmosphäre geschaffen werden kann, hängt von den Beteiligten ab. Das Team zu fragen, gleichzeitig aber auch Vorschläge mitzubringen ist ein guter Ansatz. Ein Eisbrecher zum Start kann Wunder bewirken und den Kontextwechsel ermöglichen. Beispiele gibt es viele: Von einer Frage wie „Was ist dein Lieblingstier und weshalb?“ bis hin zu Spielen wie „Wahrheit oder Lüge“, bei der jeder eine oder mehrere Anekdoten erzählt und das Team erraten muss, ob sie stimmen.

Ob es gelingt, diese positive Atmosphäre zu schaffen, hängt davon ab, wieviel Zeit zur Verfügung steht, was bezweckt wird und wer die Teilnehmer:innen sind oder wie fortgeschritten ein Team ist. Wie bereits geschrieben, können manche Teams sofort in die Themenfindung springen. Bei anderen geht es darum, dass jede:r mal den Raum findet, sich zu artikulieren, vom Vorherigen abzuschalten, Spaß zu haben oder Offenheit herbeizuführen. Das ist bei jedem Team letztendlich abhängig von den involvierten Charakteren. Zum Beispiel kann ein Scrum Master mit geringen People Skills (möglicherweise befördert im Rahmen einer „Hauptsache-intern-besetzt-Philosophie“) häufig daneben liegen.

Verbesserung braucht Zeit

Verbesserung braucht Zeit – das würde jede:r unterschreiben. Weshalb also ist es so schwierig, Zeit zu schaffen? Der große Wurf wird nicht sofort gelingen. Eindeutige Priorisierung kann helfen. Der Product Owner erfüllt die Gatekeeper-Funktion und stoppt ungeplante Aufgaben von außen bzw. bringt sie in eine Priorisierungsreihenfolge. Die Zeitfresser müssen im Team erarbeitet und in kleinen Schritten gelöst werden. Manches geht hierbei sicherlich nur perspektivisch. Eine schreckliche Architektur beispielsweise wird nicht über Nacht besser.

Timeboxing und klare Struktur

Ja, man kann sich in Diskussionen verlieren und Aussagen werden redundant getätigt. Das passiert. Hier hat der oder die Moderator:in die Aufgabe, ebendies zu erkennen und die Diskussion entsprechend zu steuern. Es muss noch Zeit für die Findung eines Lösungsansatzes gegeben sein, bevor nur schnell etwas durchgeboxt wird. Ein reifes Team entwickelt sich dahin, dass die Struktur intrinsisch befolgt wird und nicht nur auf Ermahnung durch den Scrum Master. Die Charaktere finden ihre Rolle im Team und bringen sich ein.

Retro-Demenz vermeiden

Der Feind nachhaltig umgesetzter Vorhaben ist das Besprechen und Vergessen. Wie weiter oben erwähnt, funktioniert schon das Dokumentieren nicht, fehlt auch die Nachhaltigkeit. So kann es gut sein, dass die verabschiedeten Retro-Themen erst wieder in der nächsten Retrospektive adressiert werden. Insofern die Themen keinerlei Präsenz dazwischen haben, kann auch keine Nachhaltigkeit entstehen. Neben den bereits genannten Ansätzen (im Backlog einplanen oder explizites Ownership) kann allein schon das Ansprechen des Stands bestimmter Retro-Themen im Daily Wunder bewirken.

 

Déja-vu-Erlebnisse abschaffen

Mir tut es immer leid, wenn ich erfahre, dass Teams in den vergangenen Retrospektiven ständig dieselben Punkte ansprechen, ohne dass sich etwas ändert. Die Ursachen dafür sind vielfältig, aber die Stellschrauben der bereits genannten Lösungsansätze kommen da zum Tragen. Es ist wichtig, den Zyklus der ewigen Wiederholungen zu brechen, beispielsweise dass erstmal nur ein kleiner Schritt gegangen wird, mit der Umsetzung von etwas konkret Greifbarem.

Zum Schluss noch ein Retro-Thema, das ebenfalls überraschend häufig vorkommt: der Abbau von Kopfmonopolen bzw. ihr fehlender Abbau. Ein vollumfänglich cross-funktionales Team zu haben, ist fantastisch – jedoch äußerst selten. Gerade wenn es in die Tiefe geht, ist es eher unwahrscheinlich, dass Skills gut verteilt sind. Bottlenecks (auch vor allem außerhalb der Teams) führen zu Abhängigkeiten und der Disruption von Abläufen. Hier reicht es oft schon aus, im Planning zu klären, wer gemeinsam an welcher Aufgabe mitarbeiten sollte, um schrittweise Wissen zu verbreiten. Wenn es keine Zeit dafür gibt, dann siehe oben.

Das große Bild erkennen und Ansätze finden

(Fast) alles hängt miteinander zusammen. Eine holistische Sicht hilft dabei, zu erkennen, dass kleine Änderungen schon viel bewirken können. So lässt sich ein Momentum verursachen, das Teams den Status quo verlassen lässt und in ihrer Reife voranbringt.

Also: Seid offen, mutig und respektvoll im Umgang miteinander und der Arbeit. Wählt kleine Schlachten und vergesst dabei nicht, am größeren Rad der Strategie (habt ihr eine?) mitzudrehen. Sehenden Auges eine ungewollte Verpuffung herbeizuführen, sollte etwas von gestern sein.

The post Warum Agile Retrospektiven oft scheitern: Lösungen für mehr Erfolg appeared first on BASTA!.

]]>
Cloud-Native Entwicklung mit .NET Aspire: Praxistest und bewährte Design Patterns https://basta.net/blog/cloud-applikationen-mit-net-aspire/ Mon, 22 Jan 2024 13:37:20 +0000 https://basta.net/?p=92454 Erleben Sie in unserem Praxistest die wegweisende Integration von .NET Aspire in die Welt der Cloud-Entwicklung. Microsofts leistungsstarkes Framework ermöglicht die Gestaltung resilienter, überwachbarer und konfigurierbarer Cloud-Applikationen. Von der Inbetriebnahme mit Visual Studio bis zur Analyse bewährter Design Patterns – dieser Artikel bietet einen umfassenden Einblick in den Entwicklungsprozess und die revolutionären Möglichkeiten von .NET Aspire. Tauchen Sie ein in die Zukunft der Cloud-Native Anwendungen und erfahren Sie, wie Sie mit diesem Framework effizientere und zuverlässigere Lösungen gestalten können.

The post Cloud-Native Entwicklung mit .NET Aspire: Praxistest und bewährte Design Patterns appeared first on BASTA!.

]]>
Cloud-Applikationen im Laufstall

Dass große Freiheit mit großer Verantwortung einhergeht, ist einer ältesten Scherze im Bereich des IT-Managements. Für Cloud Services gilt (logischerweise) Ähnliches: Desto weniger Optionen Entwickler:innen zur Verfügung stehen, desto wahrscheinlicher ist es, dass sie sich an vernünftigen Design Patterns orientieren.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Mit .NET Aspire schlägt Microsoft den angesprochenen Weg ein – schon in der Ankündigung findet sich das folgende Zitat, der Begriff „opinionated“ lässt sich in diesem Zusammenhang als „streng implementiert“ übersetzen: „.NET Aspire is an opinionated stack for building resilient, observable, and configurable cloud-native applications with .NET. It includes a curated set of components enhanced for cloud-native by including service discovery, telemetry, resilience, and health checks by default.“ [1]

Als Anbieter von Cloud-Lösungen ist Microsoft naturgemäß in einer einzigartigen Position, um der Nutzerschaft Vorgaben zu machen. Wenn man in der Lage ist, die Aufmerksamkeit der Entwickler:innen auf bestimmte Dienste zu richten, so lassen sich Kosten, beispielsweise für die Provisionierung, einsparen. In diesem Artikel werfen wir einen ersten Blick darauf, wie .NET Aspire funktioniert und welche praktischen Überlegungen bei der Nutzung zu beachten sind.

Inbetriebnahme der Arbeitsumgebung

Aus dem opinionated Aufbau des Entwicklungssystems folgt, dass Microsoft auch den Entwickler:innen bzw. ihren Workstationkonfigurationen enge Daumenschrauben angelegt. Wer mit Visual Studio arbeiten möchte, benötigt die brandaktuelle Preview-Version 17.9 – der Autor wird die Variante 17.9.0 Preview 1.1 verwenden und eine Windows-11-Workstation als Host einspannen. Im Visual Studio Installer findet sich dann in der Rubrik ASP.NET und Webentwicklung die Option für das Herunterladen der Payload .NET Aspire SDK (Preview) (Abb. 1).

Abb. 1: Aspire-Entwicklung setzt eine spezielle Payload voraus

Zu beachten ist außerdem, dass die Arbeit mit Aspire prinzipiell und immer die Verwendung von .NET in Version 8.0 voraussetzt. Im Hintergrund erfolgt der Großteil der Ausführung unter Nutzung von Docker-Containern. Aus diesem Grund setzt die Aspire-Payload auch das Vorhandensein von Docker Desktop voraus. Ein Produkt, dass der Visual Studio Installer schon aus lizenzrechtlichen Gründen nicht automatisch herunterladen darf.

Zur Behebung des Problems besuchen wir im ersten Schritt [2] und klicken dort auf den Knopf Download for Windows. Nach dem Herunterladen der rund 600 MB großen .msi-Datei installieren wir diese im Allgemeinen wie gewohnt. Wer Docker Desktop – wie der Autor – für die folgenden Experimente frisch installiert, muss darauf achten, dass der Startbildschirm des Produkts den erfolgreichen Start des Docker-Containers ankündigt.

Als Nächstes erfolgt der Wechsel in Visual Studio 2022, wo ein neues Projekt auf Basis der Vorlage .NET Aspire Starter Application entsteht. Die in Visual Studio implementierte Search Engine zeigt sich dabei übrigens widerspenstig. Suchen Sie bitte nach dem String „Aspire Starter“, um die Einschränkungen zu aktivieren. In der Vorlage implementiert Microsoft – auf Wunsch – dabei verschiedene vorgefertigte Elemente. Wichtig ist, dass sie im Drop-down-Menü Framework wie in Abbildung 2 gezeigt .NET 8.0 auswählen und die Checkbox für Redis aktivieren.

Abb. 2: Diese Checkbox spart Zeit und Kosten

Lohn der Mühen ist die Erzeugung des in Abbildung 3 gezeigten Projektskeletts, dass – man denke abermals an das Stichwort des opinionated Designs – aus einer ganzen Gruppe von Unterprojekten aufgebaut ist.

Abb. 3: Aspire Solutions sind durchaus kompliziert

Im Interesse der besseren Verständlichkeit ist es empfehlenswert, das Projekt im ersten Schritt unter Nutzung der Visual-Studio-Default-Payload HTTP zur Ausführung zu bringen. Hierdurch erscheinen die in Abbildung 4 gezeigten Fenster. Ein Blick auf die Docker-Desktop-Umgebung zeigt, dass Visual Studio auch einen Container für uns aktiviert hat (Abb. 5).

Abb. 4: Wer eine Aspire-Applikation startet, bekommt mehrere Fenster

Abb. 5: Dieser Container gehört zu uns

Nun wollen wir aber zum Browserfenster zurückkehren: Aspire-basierte Solutions werden von Microsoft prinzipiell mit einem Dashboard ausgestattet, das Konfiguration und Überwachung der verschiedenen Elemente der Solution erleichtert. Sinn dieser Vorgehensweise ist, dass technisch herausgeforderte Entwickler nicht mit der (oft komplizierten) Logik zum Monitoring der verschiedenen Cloud-Projekte kämpfen müssen.

Interessant ist für uns außerdem die Rubrik Projects, in der unter der Rubrik Endpoints zwei unterschiedliche Solutions zur Verfügung stehen. Unter Apiservice findet sich dabei ein Dienst, der JSON-Daten mit Dummy-Wetterdaten anzeigt. Das Webfrontend-Paket realisiert stattdessen eine Blazor-App. Zu beachten ist, dass das Anklicken der Endpoint URLs in der Rubrik Projects die jeweiligen Elemente direkt im Edge-Browser öffnet.

Analyse der Projektstruktur

Zum Zeitpunkt der Drucklegung dieses Artikels liegt Aspire noch in einem sehr frühen Zustand vor: Das äußert sich unter anderem darin, dass viele der Funktionen noch nicht im Benutzerinterface von Visual Studio angelegt sind. Als Erstes wollen wir uns das Projekt AspireApp1.AppHost ansehen, das als Dreh und Angelpunkt für die Aspire-Lösung dient und auch für die eigentliche Ausführung des Projekts verantwortlich ist. Am wichtigsten ist die Datei AspireApp1.AppHost.csproj, in der wir nach einer Analyse im Codeeditor das folgende zur Aspire-Umgebung gehörende Attribut vorfinden:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

    . . .

    <IsAspireHost>true</IsAspireHost>

In der Datei Program.cs findet sich dann die eigentliche Initialisierung, die unser Wetterdatenanzeigesystem ins Leben ruft. Microsoft orientiert sich dabei übrigens an den aus .NET Generic Host und ASP.NET Core Web Host bekannten Design Patterns, weshalb mit diesen Technologien erfahrene Entwickler:innen einen schnelleren Umstieg erleben:

var builder = DistributedApplication.CreateBuilder(args);


var cache = builder.AddRedisContainer("cache");

Nach der Erzeugung des DistributedApplication-Objekts fügen wir diesen ersten Schritt einen Redis-Container hinzu: Aus der Methode AddRedisContainer lässt sich ableiten, dass es sich dabei um ein vorgefertigtes Modul handelt. Im nächsten Schritt folgt das Hinzufügen der zwei in unserer Solution manuell angelegten Projekte (Listing 1):

Listing 1

var apiservice = builder.AddProject<Projects.AspireApp1_ApiService>("apiservice");

builder.AddProject<Projects.AspireApp1_Web>("webfrontend")

  .WithReference(cache)

  .WithReference(apiservice);

builder.Build().Run();

Zum besseren Verständnis wollen wir an dieser Stelle auf die von Microsoft bereitgestellte und in Abbildung 6 gezeigte Grafik zurückgreifen, die die in unserem Aspire-Projekt vorhandenen Komponenten visualisiert.

Aspire-Applikationen bestehen intern aus verschiedenen Instanzen von Resourcen: Zum Zeitpunkt der Drucklegung sind drei bekannt. Als Erstes die durch AddProject hinzuzufügende ProjectResource, die ein .NET-Projekt kennzeichnet. Die ContainerResource – das Einpflegen erfolgt durch AddContainer – kümmert sich um Container-Images, während die durch AddExecutable einzufügende ExecutableResource-Klasse mehr oder weniger beliebige .exe-Dateien in den Aspire-Verbund einbindet. Wichtig ist außerdem noch, dass jede im Verbund lebende Ressource prinzipiell und immer einen einzigartigen Namen aufweisen muss.

 

Zentralisierte Orchestrierung und Konfiguration

Ein von Quereinsteiger:innen im Bereich der Elektronik häufig unterschätztes Problem ist, das Hochfahren der einzelnen Spannungsversorgungen in einem System zu verwalten. Benötigt ein System mehrere Spannungen und tauchen diese in der falschen Reihenfolge auf, so kommt es mitunter zu seltsamen Problemen.

Im Fall von Cloud-basierten bzw. verteilten Systemen gibt es ähnliche Probleme: Steht ein Konfigurations-Microservice nicht zur Verfügung, wenn ein anderer Dienst hochfährt, entsteht Durcheinander. Damit ist auch schon einer der wichtigsten Problemfälle beschrieben, denen Microsoft mit .NET Aspire entgegenwirken möchte.

Zum Verständnis des Orchestrierungsprozesses wollen wir uns die Datei Program.cs ansehen, die im Projekt AspireApp1.ApiService unterkommt und für die Bereitstellung des API-Endpunkts unseres Wettervorhersagesystems verantwortlich zeichnet. Die Initialisierung erfolgt über einen AppBuilder:

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();

Interessant ist hier der Aufruf der Methode AddServiceDefaults(), die Aspire zur Nutzung der projektglobalen Standardeinstellungen für verschiedene häufig benötigte Elemente der Solution animiert. Die restliche Initialisierung kümmert sich vor allem um das Einrichten eines Event Handler, der die ausgegebenen Informationen per Zufallsgenerator generiert. Um es kompakt zu halten, drucken wir diesen Teil des Codes gekürzt ab (Listing 3). Wichtig ist noch der Aufruf von Run, der die Aspire-Arbeitsumgebung zur eigentlichen Ausführung des Codes animiert. Angemerkt sei, dass Microsoft in .NET Aspire auch verschiedene andere Methoden zur Konfiguration anbietet – unter [4] findet sich eine Übersicht.

Listing 3

app.MapGet("/weatherforecast", () =>
{
  var forecast = Enumerable.Range(1, 5).Select(index =>

    . . .
  return forecast;
});
app.MapDefaultEndpoints();
app.Run();

Interessanter ist für uns an dieser Stelle die Frage, woher der Aspire-Orchestrator die anzuwendenden Einstellungen bezieht und welche Teile der Solutions er zu konfigurieren sucht. Die Antwort auf diese Frage findet sich im Projekt AspireApp1.ServiceDefaults. Ich möchte mich auch an dieser Stelle kurz fassen; unter [5] findet sich eine vollständige Analyse des vergleichsweise reich kommentierten Codes.

Im Prinzip erfolgt die Initialisierung durch statische Methoden. Am wichtigsten ist die Funktion AddServiceDefaults, die, wie hier gekürzt gezeigt, verschiedene Basisdienste wie den Orchestrierungsservice konfiguriert und zur Solution hinzufügt:

public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) {
  builder.ConfigureOpenTelemetry();
  builder.AddDefaultHealthChecks();
  builder.Services.AddServiceDiscovery();
  . . .

Im Bereich der Telemetriedatenerfassung setzt .NET Aspire auf OpenTelemetry – weitere Informationen hierzu finden sich unter [6]. Wichtig ist, dass die in .NET Aspire enthaltene Implementierung ebenfalls konfiguriert werden muss. Das ist die Aufgabe der Methode ConfigureOpenTelemetry, die wir hier aus Platzgründen nicht abdrucken.

Zu guter Letzt implementiert Aspire auch noch Health Checks. Dabei handelt es sich um Methoden, die das Überprüfen der Systemgesundheit des von .NET Aspire verwalteten Dienst ermöglichen. Ihre Einrichtung erfolgt in den Methoden AddDefaultHealthChecks und MapDefaultEndpoints.

Aspire-Komponenten erlauben das einfache Einpflegen häufig benötigter Dienste. Neben dem Orchestrierungs-Service ist vor allem die Komponentenfunktionalität von .NET Aspire relevant. Dabei handelt es sich um ein Feature, mit dem Microsoft die direkte Integration häufig benötigter Komponenten in die Solution ermöglicht. Zum Zeitpunkt der Drucklegung dieses Artikels bietet die unter [7] ansprechbare Liste dabei die in Abbildung 7 gezeigten Komponenten an.


Abb. 7: In Microsofts Komponentenzoo wimmelt es

Zu Demonstrationszwecken werden wir in den folgenden Schritten einen Azure-Dienst hinzufügen. Im ersten Schritt klicken wir die Projektmappe rechts an, und entscheiden uns für den Assistenten zum Hinzufügen eines neuen Projekts.

Suchen Sie nach der Vorlage Workerdienst und entscheiden Sie sich für die Variante für C#. Relevant ist, dass diese Vorlage nicht Teil der von .NET Aspire eingefügten Elemente ist. Als Name wird der Autor in den folgenden Schritten AspireApp1.AzWorker vergeben. Interessant ist außerdem, dass Sie im Projektkonfigurationsfenster, wie in Abbildung 8 gezeigt, die Option haben, die Checkbox In Aspire-Orchestrierung eintragen zu aktivieren. Diese sollten Sie auswählen, weil sich Visual Studio dann darum kümmert, unsere Worker-Dienste teilweise in die Solutions einzupflegen.



Abb. 8: Die Checkbox erleichtert das Ins-Leben-Rufen neuer Services

Dank der markierten Checkbox gibt es in Program.cs eine nach folgendem Schema erfolgende Anpassung, die sich um das Einpflegen der neuen Komponente kümmert:

. . .
builder.AddProject<Projects.AspireApp1_AzWorker>("aspireapp1.azworker");
builder.Build().Run();

Im nächsten Schritt klicken wir das neu erzeugte Paket rechts an, um die NuGet-Paketverwaltung zu öffnen. Ebenda entscheiden wir uns für das Paket Aspire.Azure.Storage.Blobs, das zum Zeitpunkt der Drucklegung dieses Artikels als Version 8.0.0-preview.1.23557.2 vorliegt. Danach verfahren wir analog mit dem Paket Aspire.Azure.Storage.Queues.

Angemerkt sei hier, dass .NET Aspire als .NET-Technologie logischerweise nicht von den Zwängen befreit ist, die im Bereich der .NET-Technologien zum Tragen kommen. Wichtig ist, dass das Hinzufügen der NuGet-Pakete in allen Solutions gleichermaßen erforderlich ist, die später mit dem jeweiligen Feature des verteilten Systems interagieren wollen oder sollen.

Im nächsten Schritt ist ein Wechsel in das Azure-Backend erforderlich, wo im ersten Schritt ein neuer Storage-Account entsteht. Der Autor vergab in den folgenden Schritten den Namen susaspireexperiment und entschied sich für die preiswerteste Redundanzklasse und eine geringe Performance-Tier. Für unsere hier durchgeführten Experimente ist ein Mehr an Systemperformance nicht notwendig, würde die Kosten aber immens erhöhen.

Im nächsten Schritt benötigen wir einen Blob-Storage-Container und eine Storage-Queue-Instanz: Wichtig ist in beiden Fällen, die Connection-Strings zur Verfügung zu haben. Wichtig ist außerdem, den Connection-String auf Ebene des Storage-Accounts zu beziehen – Abbildung 9 zeigt den korrekten Platz.

 

Abb. 9: Der Connection-String ist zum Abernten bereit

Als Nächstes stellt sich die Frage, wie der derzeit als String vorliegenden Connection-String in die .NET Aspire Solution einbezogen werden kann. Die Antwort darauf ist die Datei appsettings.json – beachten Sie, dass diese Adaption für jedes mit den Azure-Ressourcen zu verbindende Projekt von Hand durchgeführt werden muss (Listing 4).

Listing 4
{
  "Logging": {
    . . .
  },
  "ConnectionStrings": {
    "blobdb": "DefaultEndpointsProtocol=https;AccountName=susaspire. . .",
    "queuedb": "DefaultEndpointsProtocol=https;AccountName=susaspire. . ."
  }
}

Im vorliegenden Fall ist derselbe Schlüssel unter zwei unterschiedlichen Namen angelegt: Ursache dafür ist die weiter oben erwähnte Bedingung, dass Namen nur einmal im Verbund vorkommen dürfen. Für die erfolgreiche Initialisierung der Containerinfrastruktur ist im AppHost außerdem das Hinzufügen des NuGet-Pakets Aspire.Hosting.Azure erforderlich.

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Nach der Erzeugung der Service-Primitiva müssen diese im ersten Schritt zum Webprojekt hinzugefügt werden. Hierzu sind einfach weitere Aufrufe der Methode Add* notwendig, die die schon vorhandenen Aufrufe um die weiteren benötigten Komponenten ergänzen:

using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddAzureStorage("tamsstore");
var blobs = storage.AddBlobs("blobdb");
var queues = storage.AddQueues("queuedb");

Im Fall des neuen von Visual Studio hinzugefügten Workers stehen noch keine Referenzen zur Verfügung, weshalb hier etwas umfangreichere Anpassungen der Deklaration anfallen:

builder.AddProject<Projects.AspireApp1_AzWorker>("aspireapp1.azworker")
  .WithReference(blobs)
  .WithReference(queues);

Im Webprojekt muss im nächsten Schritt die Deklaration aus Listing 5 eingefügt werden.

Listing 5

builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
  .WithReference(blobs)
  .WithReference(queues)
  .WithReference(cache)
  .WithReference(apiservice);


var builder = WebApplication.CreateBuilder(args);
builder.AddAzureBlobService("blobdb");
builder.AddAzureQueueService("queuedb");
builder.AddServiceDefaults();

Danach wechseln wir in die Webapplikation, in der einige benötigte Dependencies importiert werden, wie in Listing 6 zu sehen ist.

Listing 6

@page "/counter"
@rendermode InteractiveServer

@using System.ComponentModel.DataAnnotations
@using Azure.Storage.Blobs
@using Azure.Storage.Queues

@inject BlobServiceClient BlobClient
@inject QueueServiceClient QueueServiceClient

Der Container erschwert Aspire-Entwicklern an dieser Stelle das Leben insofern, als er die in die Blobs zu schreibenden Informationen ausschließlich in Form von Streams erwartet. Wer wie wir hier einen String hochladen möchte, muss diesen zunächst vergleichsweise aufwendig in eine Stream-Instanz umwandeln (Listing 7).

Listing 7
@code {
  private int currentCount = 0;
  private async void IncrementCount() {
    String usefulData ="ThisIsTheTestData";
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(usefulData);
    writer.Flush();
    stream.Position = 0;
    var docsContainer = BlobClient.GetBlobContainerClient("fileuploads");
    await docsContainer.UploadBlobAsync(  "TestFile",stream);
    currentCount++;
  }
}

Wer den Knopf dann anklickt, sieht das Aufscheinen eines Fehler-Callouts (Abb. 10) in der von .NET Aspire ebenfalls zur Verfügung gestellten Arbeitsoberfläche. Das Anklicken dieser Option führt zum Aufscheinen eines zusätzlichen Fensters, das – ganz analog zum klassischen Visual Studio – sogar Details zum Aufrufstack und zu den im Exception-Objekt angelegten Fehlerinformationen anbietet.


Abb. 10: Während der Ausführung von Aspire-Applikation auftretende Fehler werden im Web-Frontend zentralisiert präsentiert

Eine detaillierte Analyse des zurückgelieferten Fehlerstrings führt uns dann auch schon zum Ziel: Der Autor hatte einen Tippfehler: Der im Azure-Cloud-Backend angelegte Name des Blobs stimmte nicht mit dem überein, den die Methode erwartet hatte. Mit diesem Wissen lässt sich der Code wie folgt verbessern:

var docsContainer = BlobClient.GetBlobContainerClient("susaspirecont");

Bei der Programmausführung zeigt nun der Blob im Backend die per Aspire angelieferten Informationen an.

Fazit

Mit .NET Aspire integriert Microsoft Lifecycle-Management in die .NET-Arbeitsumgebung: eine Funktion, die bisher beispielsweise in Docker Compose zur Verfügung gestellt wurde. Schon durch die Möglichkeit, mehr Services aus einer Hand zu beziehen, geht das mit einer Erhöhung der Lebensqualität für Entwickler:innen einher. Fraglich ist allerdings, ob bzw. inwiefern diese systemimmanenten Vorteile ausreichen, um Microsoft das Angreifen der gut etablierten Konkurrenz im Umfeld der Containerorchestrierung zu ermöglichen.

Links & Literatur

[1] https://devblogs.microsoft.com/dotnet/introducing-dotnet-aspire-simplifying-cloud-native-development-with-dotnet-8/

[2] https://www.docker.com/products/docker-desktop/

[3] https://learn.microsoft.com/en-us/dotnet/aspire/app-host-overview

[4] https://learn.microsoft.com/en-us/dotnet/aspire/service-defaults

[5] https://learn.microsoft.com/en-us/dotnet/aspire/service-discovery/overview

[6] https://opentelemetry.io

[7] https://learn.microsoft.com/en-us/dotnet/aspire/components-overview?tabs=dotnet-cli

 
 

The post Cloud-Native Entwicklung mit .NET Aspire: Praxistest und bewährte Design Patterns appeared first on BASTA!.

]]>
Authentifizierung und Autorisierung – Sicherheit in einer Angular-Anwendung https://basta.net/blog/authentifizierung-und-autorisierung-sicherheit-in-einer-angular-anwendung/ Thu, 14 Dec 2023 10:13:52 +0000 https://basta.net/?p=92349 Dieser Artikel beleuchtet die Grundlagen und Best Practices für die Integration von Authentifizierungs- und Autorisierungsmechanismen in Angular-Anwendungen. Wir werden uns clientseitige Ansätze ansehen, die zur Absicherung von Angular-basierten Projekten beitragen, sowie gängige Herausforderungen und Lösungen in diesem Bereich diskutieren. Mit einem Fokus auf aktuelle Technologien und Methoden bietet dieser Artikel wertvolle Einblicke und Anleitungen für Entwickler:innen, die ihre Angular-Anwendungen sicherer und widerstandsfähiger gegenüber Bedrohungen machen wollen.

The post Authentifizierung und Autorisierung – Sicherheit in einer Angular-Anwendung appeared first on BASTA!.

]]>

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Bei der Diskussion um Sicherheit in Webanwendungen rückt unweigerlich die Frage in den Fokus, was genau es zu schützen gilt. In diesem Kontext bietet die sogenannte CIA-Triade [1] einen klaren Rahmen, indem sie drei essenzielle Sicherheitsziele definiert – Vertraulichkeit, Integrität und Verfügbarkeit (Abb. 1).

Abb. 1: Die CIA-Triade

  1. Vertraulichkeit (Confidentiality): Dieses Ziel bezieht sich auf den Schutz von Daten vor unbefugtem Zugriff oder Offenlegung. Es gewährleistet, dass sensible Informationen nur für diejenigen zugänglich sind, die berechtigt sind. Maßnahmen zur Aufrechterhaltung der Vertraulichkeit umfassen Verschlüsselung, Zugriffskontrollen und strenge Authentifizierungsverfahren.
  2. Integrität (Integrity): Integrität bezieht sich auf die Bewahrung der Genauigkeit und Vollständigkeit von Daten. Es geht darum, sicherzustellen, dass Informationen nicht ohne Autorisierung verändert, manipuliert oder auf andere Weise beschädigt werden. Methoden zur Sicherung der Integrität umfassen Hashfunktionen, digitale Signaturen und Redundanzmechanismen.
  3. Verfügbarkeit (Availability): Verfügbarkeit bedeutet, dass Informationen und Ressourcen im Bedarfsfall zugänglich und nutzbar sind. Dieses Ziel soll sicherstellen, dass Systeme und Daten trotz verschiedener Bedrohungen wie Hardwareausfällen, Softwareproblemen oder Cyberangriffen zugänglich bleiben. Maßnahmen zur Gewährleistung der Verfügbarkeit sind unter anderem Back-up-Systeme, Redundanzen und effiziente Wartungsprozesse.

Die CIA-Triade dient als Grundlage für das Verständnis und die Bewertung von Sicherheitsmaßnahmen in IT-Systemen. Mit der Integration von Authentifizierung und Autorisierung sorgen wir also für einen Schutz des Sicherheitsziels der Vertraulichkeit: Schutz vor unbefugtem Zugriff.

Authentifizierung und Autorisierung

Authentifizierung und Autorisierung sind zwei grundlegende Sicherheitskonzepte, die vor allem in der Welt der Informationstechnologie Verwendung finden. Immer wieder werden beide Konzepte vermischt oder sogar synonym verwendet, daher möchte ich mit einer kleinen Definition beide Sicherheitskonzepte noch einmal voneinander abtrennen:

Authentifizierung bezieht sich hierbei auf den Prozess, mit dem überprüft wird, ob jemand oder etwas tatsächlich das ist, was es vorgibt zu sein. Im Kontext von Webanwendungen bedeutet das in der Regel die Überprüfung der Identität eines Benutzers, typischerweise durch Benutzername und Passwort, biometrische Daten, Einmal-Passwörter oder andere Methoden. Authentifizierung ist der erste Schritt, um zu gewährleisten, dass der Zugriff auf ein System oder eine Anwendung von einer legitimen Quelle aus erfolgt. Oftmals werden bei der Authentifizierung sogenannte zweite Faktoren (Multi-factor Authentication) genutzt, um eine weitere Sicherheitsstufe zu integrieren. Das kann in Form eines SMS-Codes oder über eine speziell dafür ausgelegte Applikation [2] auf dem Smartphone integriert werden.

Autorisierung hingegen ist der Prozess der Entscheidung, ob ein authentifizierter Benutzer Zugriff auf bestimmte Ressourcen oder Funktionen erhalten soll. Das beinhaltet in der Regel die Überprüfung von Benutzerrechten oder Rollen gegenüber den Zugriffsanforderungen. Autorisierung erfolgt nach der Authentifizierung und bestimmt, was ein Benutzer in einem System oder einer Anwendung tun darf.

Diese beiden Konzepte sind eng miteinander verknüpft, aber dennoch unterschiedlich:

  • Authentifizierung stellt fest, wer der Benutzer ist.
  • Autorisierung bestimmt, was der Benutzer tun darf.

Die Herausforderung besteht darin, ein Gleichgewicht zu finden zwischen dem Schutz sensibler Daten und Ressourcen und der Bereitstellung eines nahtlosen und benutzerfreundlichen Erlebnisses für legitime Benutzer. So ist beispielsweise eine Ressource optimal geschützt, wenn der Benutzer noch vier oder mehr weitere Faktoren zur Authentifizierung angeben muss; der Benutzer selbst ist aber vermutlich schnell genervt von dem bereitgestellten Authentifizierungsprozess.

Authentifizierung in einer Webapplikation

Bei der Integration eines Authentifizierungsprozesses in eine Webanwendung stehen uns im Wesentlichen zwei etablierte Methoden zur Verfügung:

  • cookiebasierte Authentifizierung
  • tokenbasierte Authentifizierung

Beide Ansätze finden heutzutage große Anwendung und bringen jeweils ihre spezifischen Vorzüge und Herausforderungen mit sich. Im Folgenden werde ich diese beiden Konzepte detailliert erörtern und die potenziellen Vor- und Nachteile jedes Ansatzes beleuchten, um ein tieferes Verständnis ihrer Anwendung und Wirksamkeit in modernen Webanwendungen zu vermitteln.

Cookiebasierte Authentifizierung

Cookiebasierte Authentifizierung ist ein verbreiteter Ansatz zur Verwaltung von Benutzersitzungen in Webanwendungen. Dabei wird nach erfolgreicher Authentifizierung des Nutzers ein kleines Stück Daten – bekannt als Cookie – vom Server an den Webbrowser des Benutzers gesendet und dort gespeichert. Dieses Cookie wird dann bei jeder folgenden Anfrage des Browsers an den Server zurückgesendet, wodurch der Server den Benutzer und dessen Sitzungszustand über verschiedene Anfragen hinweg verfolgen kann. Dabei werden folgende Schritte durchgeführt:

  • Anmeldung: Der Benutzer gibt seine Anmeldeinformationen (wie Benutzername und Passwort) ein. Nach erfolgreicher Überprüfung dieser Informationen durch den Server wird ein Sitzungscookie erstellt (2).
  • Sitzungscookie: Dieses Cookie enthält in der Regel eine einzigartige Sitzungs-ID, die den Benutzer identifiziert. Es speichert keine sensiblen Benutzerdaten direkt, da Cookiedaten innerhalb eines Webbrowsers gespeichert werden und diese theoretisch jederzeit einsehbar sind (3).
  • Sicherheit: Um die Sicherheit zu erhöhen, können Cookies mit Attributen wie httpOnly (verhindert den Zugriff durch Client-side-Skripte) und Secure (sorgt dafür, dass Cookies nur über HTTPS gesendet werden) versehen werden [3].
  • Sitzungsverwaltung: Bei jeder Anfrage des Benutzers an den Server wird das Sitzungscookie mitgesendet. Der Server prüft die Gültigkeit des Cookies und erlaubt den Zugriff auf geschützte Ressourcen, wenn das Cookie gültig ist (4).
  • Ablauf und Abmeldung: Cookies haben ein Ablaufdatum. Nach dem Ablauf oder wenn der Benutzer sich explizit abmeldet, wird das Cookie entweder vom Server als ungültig markiert oder vom Browser gelöscht.

Abb. 2: Nach dem Log-in wird ein Sitzungscookie erstellt

Abb. 2: Nach dem Log-in wird ein Sitzungscookie erstellt

Abb. 3: Das Cookie wird im Browser gespeichert

Abb. 4: Der Server prüft die Gültigkeit des gespeicherten Cookies

Die cookiebasierte Authentifizierung ist besonders effektiv für traditionelle Webanwendungen, bei denen der Server eine aktive Rolle bei der Sitzungsverwaltung spielt. Ein weiterer wichtiger Faktor ist das Management der Cookies über den Browser: Entwickler:innen müssen hierbei keinen eigenen Code für das Verwalten der Cookies schreiben. Das verringert die Komplexität der Webanwendung, da wir uns darauf verlassen können, dass der Webbrowser bei jeder Anfrage an unser Backend die Cookiedaten automatisch mitsendet.

Durch den Einsatz der bereits erwähnten Cookieattribute secure und httpOnly erhöhen wir die Sicherheit in der Verwaltung unserer Cookies. Insbesondere bietet das Setzen des httpOnly-Attributs einen wirksamen Schutz gegen Manipulationen des Cookies durch bösartige JavaScript-Einschleusungen, bekannt als Cross-site-Scripting-(XSS-)Attacken. Dieses Attribut bewirkt, dass die Cookies nicht mehr über das Browser-API document.cookie zugänglich sind, wodurch ein zusätzliches Sicherheitsniveau im Umgang mit sensiblen Benutzerdaten etabliert wird.

Auch wenn die Nutzung von Cookies bösartige Manipulationen über JavaScript verringern kann, sind sie leider die Hauptursache für sogenannte Cross-site-Request-Forgery-(CSRF-)Attacken.

Eine CSRF-Attacke, auch als „One Click Attack“ oder „Session Riding“ bekannt, ist eine Art von Cyberangriff, bei dem ein Angreifer Nutzer dazu bringt, ungewollte Aktionen auf einer Webseite auszuführen, auf der sie gerade angemeldet sind. Das geschieht typischerweise, ohne dass der Nutzer sich dessen bewusst ist. Hierbei wird genau die Tatsache ausgenutzt, dass das Opfer bereits auf der Zielwebseite über ein Cookie authentifiziert ist und dieses Cookie bei allen Anfragen auf die Zielwebseite automatisch vom Webbrowser mitgesendet wird. Die Anfrage kann daraufhin verschiedene Aktionen auslösen, wie das Ändern von Kontoinformationen, das Versenden von Nachrichten oder das Durchführen von Transaktionen. Die Open-Web-Application-Security-Project-(OWASP-)Organisation hat einige Tipps und Tricks in einem Cheat Sheet [4] zusammengestellt, wie man sich am besten vor CSRF-Attacken schützen kann.

Neben der Einführung von potenziellen CSRF-Attacken haben Cookies noch einen weiteren Nachteil: Sie sind weniger geeignet für moderne, verteile Architekturen wie Single Page Applications (SPAs) und Microservices, in denen oft tokenbasierte Authentifizierungsmethoden, wie z. B. JSON Web Tokens (JWT), bevorzugt werden. In Anbetracht dessen möchten wir uns nun der tokenbasierten Authentifizierung zuwenden und diese eingehend beleuchten.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Tokenbasierte Authentifizierung

Tokenbasierte Authentifizierung ist ein Verfahren, bei dem statt traditioneller Session-Cookies ein Authentifizierungstoken für die Identifizierung und Verwaltung von Benutzersitzungen verwendet wird. Dieses Verfahren wird wie bereits erwähnt häufig in modernen Webanwendungen und insbesondere in API-basierten Diensten und Single-Page-Anwendungen eingesetzt. Folgendes sind die Kernaspekte der tokenbasierten Authentifizierung:

  • Authentifizierung und Tokenerstellung: Der Benutzer gibt seine Anmeldeinformationen (Benutzername und Passwort) ein. Nach erfolgreicher Überprüfung erstellt der Server ein Token, oft ein JSON Web Token (JWT), das Informationen über den Benutzer und die Sitzung enthält. Meist kommt hierbei ein sogenannter externer Identity Provider ins Spiel der die sichere Generierung und Benutzerverwaltung übernimmt (5).
  • Tokenversendung: Das Token wird an den Client (den Browser oder die Anwendung des Benutzers) gesendet und dort gespeichert, beispielsweise im Local Storage, in einer Session oder in einem Cookie (6).
  • Clientseitige Verwendung des Tokens: Bei jeder folgenden Anfrage an den Server fügt der Client das Token im HTTP-Header hinzu. Dies dient als Nachweis der Authentifizierung und Autorisierung für die Anfrage (7).
  • Serverseitige Validierung: Der Server validiert das Token bei jeder Anfrage, um sicherzustellen, dass es gültig und nicht manipuliert wurde. Nach erfolgreicher Validierung gestattet der Server den Zugriff auf geschützte Ressourcen.
  • Ablauf und Erneuerung: Token haben in der Regel ein Ablaufdatum. Nach Ablauf kann der Benutzer entweder erneut seine Anmeldeinformationen eingeben oder ein Refresh-Token verwenden, um ein neues Zugriffstoken zu erhalten.

Abb. 5: Bei der tokenbasierten Authentifizierung ist meist ein Identity Provider im Spiel

Abb. 6: Das Token wird an den Client gesendet

Abb. 7: Das Token im Header dient als Authentifizierungsnachweis

Diese Methode ist besonders effektiv für Anwendungen, die Ressourcen über mehrere Server oder Dienste verteilen, da das Token leicht zwischen verschiedenen Systemkomponenten übertragen werden kann, ohne die Notwendigkeit einer zentralen Sitzungsverwaltung.

Token können auf verschiedene Arten in Browsern oder Webanwendungen gespeichert werden. Doch auch hierbei gibt es einiges zu beachten: Wenn Sie den lokalen Speicher eines Browsers verwenden, kann eine Subdomain nicht auf Tokens zugreifen. Sie können jedoch von jedem JavaScript-Code auf der Webseite sowie von Browser-Plug-ins aufgerufen und manipuliert werden. Das ist also keine empfohlene Methode – sie stellt zum einen ein Sicherheitsrisiko dar und darüber hinaus müssen Sie den Speicher selbst verwalten.

Die Verwaltung der Tokens erfordert oftmals viel manuelle Arbeit: Ein Token kann beispielsweise nicht widerrufen werden. Selbst wenn ein Token gestohlen wird, bleibt es gültig, bis es abläuft, was zu einer schwerwiegenden Sicherheitslücke führt. Um dieses Problem zu umgehen, müssen Sie eine Sperrlistentechnik implementieren, die eine komplexere Einrichtung erfordert.

Aber auch ohne an bösartige Attacken zu denken, kann eine eigenständige Verwaltung der Tokens kompliziert sein: Die Informationen in einem Token stellen eine Momentaufnahme zum Zeitpunkt der ursprünglichen Erstellung des Tokens dar. Der zugehörige Benutzer verfügt möglicherweise nun über andere Zugriffsebenen oder wurde vollständig aus dem System entfernt.

Angesichts der Komplexität und Bedeutung einer sicheren Tokengenerierung und -verwaltung empfiehlt es sich fast immer, diese Aufgaben nicht eigenständig zu übernehmen. Oft ist es vorteilhafter und sicherer, externe Identity-as-a-Service-Anbieter (sogenannte Identity Provider wie Azure Active Directory [5] oder Auth0 [6] by Okta) zu integrieren oder etablierte Identity- und Access-Management-Tools zu verwenden, wie sie beispielsweise mit Keycloak [7] von Red Hat angeboten werden.

Diese Lösungen bieten nicht nur eine robuste Verwaltung von Nutzerkonten und Tokens, sondern erleichtern durch bereitgestellte Entwicklerbibliotheken auch signifikant die Integration in Webanwendungen. Dadurch wird nicht nur die Sicherheit erhöht, sondern auch der Entwicklungsprozess effizienter und benutzerfreundlicher gestaltet.

Wie das Ganze nun konkret aussehen kann, möchte ich Ihnen anhand einer beispielhaften Implementierung demonstrieren. Hierbei werde ich den Identity Provider Auth0 by Okta nutzen, um Authentifizierung und Autorisierung in eine bestehende Angular-Applikation zu integrieren.

Bevor wir mit der konkreten Implementierung starten können, müssen wir uns zuvor kurz mit den offenen Standards für Autorisierung und Authentifizierung auseinandersetzen. Diese beschreiben verschiedene Workflows und Richtlinien, wie eine Webanwendung ein Token sicher erhalten kann, und bieten eine Spezifikation für weitere Authentifizierungskonzepte wie Single Sign-on (SSO) oder die Integration sozialer Medien (Social Log-in).

 

OAuth und OpenID Connect

OpenID Connect ist eine Identitätsverwaltungsschicht, die auf dem OAuth-Protokoll aufbaut. Sie ermöglicht es Clients, die Identität eines Endbenutzers zu verifizieren und grundlegende Profilinformationen über den Benutzer zu erhalten. OpenID Connect wird oft für Single-Sign-on-(SSO-)Lösungen verwendet und ist weit verbreitet in modernen Webanwendungen und Mobile-Apps. OpenID Connect erweitert hierbei OAuth um die Einführung von ID-Tokens. Diese Tokens sind im JWT-Format und enthalten Informationen über die Authentifizierung des Benutzers.

OAuth ist ein offener Standard für Zugriffsdelegation, der es Benutzern ermöglicht, Dritten eingeschränkten Zugriff auf ihre Ressourcen auf einem anderen Server zu gewähren, ohne dabei ihre Zugangsdaten preiszugeben. Ursprünglich für die API-Autorisierung entwickelt, hat sich OAuth zu einem Schlüsselstandard in der modernen Web- und Anwendungsentwicklung entwickelt. OAuth ermöglicht es Anwendungen, im Namen des Benutzers auf Ressourcen zuzugreifen, indem sie ein Zugriffstoken verwenden, das vom Ressourcenbesitzer (dem Benutzer) genehmigt wurde. Das bedeutet, dass Anwendungen keine Benutzernamen und Passwörter speichern müssen. Kurz gesagt ist OAuth eine Spezifikation für die Autorisierung eines Benutzers: Sie können spezifische Berechtigungen (Scopes) an die Anwendungen vergeben, was eine fein abgestimmte Kontrolle darüber ermöglicht, auf welche Informationen und Funktionen die Anwendung zugreifen darf.

OAuth definiert verschiedene Flows (auch als Grant Types bezeichnet), um unterschiedliche Anwendungsfälle und Szenarien für die Authentifizierung und Autorisierung zu unterstützen. Jeder dieser Flows beschreibt einen spezifischen Prozess zur Erlangung eines Zugriffstokens – dem sogenannten Access-Token.

Je nach Anwendung werden verschiedene Flows empfohlen. Die aktuelle Version OAuth 2.1 [8] spezifiziert hierbei hauptsächlich drei Flows:

  • Client Credentials Flow
  • Device Code Flow
  • Authorization Code Flow mit Proof-Key-Code-Exchange-(PKCE-)Erweiterung

Der Client Credentials Flow wird immer dann verwendet, wenn der Zugriff zwischen zwei Anwendungen ohne Benutzerinteraktion erfolgt. Die Anwendung authentifiziert sich mit ihren eigenen Credentials (nicht mit Benutzer-Credentials) beim OAuth-Server und erhält ein Zugriffstoken. Der Device Code Flow wird für Geräte genutzt, die keine einfache Möglichkeit bieten, Text einzugeben (wie Smart-TVs oder Spielkonsolen). Es verwendet ein Gerät, das einen Code anzeigt, den der Benutzer auf einem anderen Gerät (z. B. einem Smartphone) eingibt, um die Authentifizierung zu bestätigen.

Wenn wir mit Angular eine SPA entwickeln, wird empfohlen, dass wir den Authorization Code Flow mit PKCE nutzen. Dieser Flow ist für Anwendungen gedacht, die auf einem Server laufen. Bei einem einfachen Authorization Code Flow (ohne PKCE-Erweiterung) authentifiziert sich der Benutzer zunächst bei seinem Identity Provider und erteilt der Anwendung die Berechtigung (Abb. 8). Daraufhin erhält die Anwendung einen Autorisierungscode, den sie gegen ein Zugriffstoken eintauschen kann (Abb. 9). Dieser Flow gilt als einer der sichersten, da die Nutzer-Credentials zu keinem Zeitpunkt in unserer Frontend-Applikation einsehbar sind.

Abb. 8: Authentifizierung beim Identity Provider

Abb. 9: Autorisierungscode wird gegen Access-Token getauscht

Mit der Proof-Key-Code-Exchange-Erweiterung – die seit OAuth 2.1 auch verpflichtend ist – fügt man dem Authorization Code Flow noch eine weitere Sicherheitsstufe hinzu. Hierbei generiert der Client einen zufälligen String, bekannt als Code Verifier. Daraufhin erstellt der Client eine Code Challenge aus diesem Verifier, in der Regel, indem er einen Hashwert (SHA256) des Verifiers bildet und den Hash dann in Base64-URL kodiert. Bei der erneuten Authentifizierung des Benutzers werden neben den Credentials die Code Challenge und die Hashfunktion mitgegeben, mit der eben diese Code Challenge erstellt wurde. Daraufhin erhält, wie auch zuvor, die Anwendung einen Autorisierungscode und sendet diesen, diesmal mit dem Code Verifier, an seinen Identity-Provider. Dieser verwendet den empfangenen Code Verifier, um die Code Challenge zu generieren (d. h., er berechnet den SHA256-Hash und kodiert ihn in Base64-URL). Der Identity-Provider vergleicht dann die generierte Challenge mit der ursprünglich vom Client gesendeten Challenge. Stimmen diese überein, weiß der Server, dass die Anfrage vom gleichen Client stammt, der den Autorisierungscode angefordert hat und liefert ebenfalls das Access-Token aus.

Zum Glück entfällt die Notwendigkeit, sich mit den Details der Implementierung dieser Flows auseinanderzusetzen, da das von externen Bibliotheken, wie beispielsweise denen, die Auth0 by Okta bereitstellt, abgedeckt wird. Dennoch ist ein grundlegendes Verständnis der verschiedenen OAuth Flows von unschätzbarem Wert, um eine fundierte und sichere Entscheidung über den am besten geeigneten Flow für unsere spezifischen Anforderungen treffen zu können.

Konfiguration des Identity Provider

Wie bereits erwähnt werde ich in meiner Beispielimplementierung den Identity Provider Auth0 by Okta verwenden. Sämtliche gezeigten Funktionalitäten und Konzepte finden sich aber in anderen Identity-as-a-Service-Lösungen ebenfalls wieder. Auth0 bietet einen kostenlosen Nutzungsplan [9], der bis zu 7 500 aktive Benutzer und unbegrenzte Log-ins umfasst. Diese kostenlosen Pläne sind ideal, um die grundlegenden Funktionen von Auth0 by Okta kennenzulernen und zu testen, wie gut sie sich in Ihre Anwendung oder Ihr Projekt integrieren lassen.

Wenn wir uns nun kostenlos registrieren, können wir eine neue Applikation – auch Tenant genannt – erstellen (Abb. 10). Ein Tenant bezeichnet eine dedizierte Instanz der Identity-Provider-Plattform, die für einen Kunden oder ein Projekt eingerichtet wird. Es ist im Wesentlichen ein separater Container, in dem alle Ihre Benutzer, Sicherheitseinstellungen, Anwendungen und Konfigurationen für Ihre Authentifizierungs- und Autorisierungsvorgänge gespeichert werden. Jeder Tenant bei Auth0 ist durch eine eindeutige Domain gekennzeichnet und isoliert, was bedeutet, dass die Daten und Konfigurationen eines Tenants nicht mit denen anderer Tenants geteilt werden.

Abb. 10: Erstellen einer Applikation

Nachdem der Tenant erfolgreich angelegt wurde können wir diesen nun konfigurieren. Ebenso können wir alle wichtigen Daten direkt einsehen (beispielsweise die Domain und die Client-ID), die wir benötigen, um unsere Frontend-Applikation mit Auth0 zu verbinden (Abb. 11).

Abb. 11: Informationen unserer Applikation

Wichtig hierbei ist, dass wir konfigurieren, welche sogenannten Origins, also URLs auf unseren Tenant zugreifen dürfen. Da wir eine lokale Angular-Anwendung implementieren, müssen wir http://localhost:4200 eintragen. Darüber hinaus ist dieser URL ebenfalls noch in die Liste der Allowed Callback URLs (eine URL-Liste, in der aufgelistet wird, wohin der Identity Provider nach erfolgreicher Authentifizierung wieder navigieren soll) und in die Liste der Allowed Logout URLs (ebenfalls eine Liste der URLs, auf die der Identity Provider uns navigiert, sobald sich der Benutzer erfolgreich ausloggt) einzutragen. Nach erfolgreicher Konfiguration können wir nun damit starten, Auth0 in unserer Angular-Applikation zu integrieren.

Integration in eine Angular-Anwendung

Auth0 erleichtert die Integration erheblich durch die Bereitstellung eines SDK, das als npm-Paket [10] verfügbar ist. Dieses können wir wie folgt installieren:

> npm install @auth/auth0-angular

 

Nach erfolgreicher Installation nutzen wie uns zur Verfügung gestellte provideAuth0-Funktion und konfigurieren sie mit den spezifischen Details unserer Auth0-Domain und der Client-ID wie in Listing 1.

 

Listing 1

//main.ts

import { provideAuth0 } from '@auth0/auth0-angular';

bootstrapApplication(AppComponent, {

  providers: [
    provideAuth0({
      domain: 'YOUR_AUTH0_DOMAIN',
      clientId: 'YOUR_AUTH0_CLIENT_ID',
      authorizationParams: {
        redirect_uri: window.location.origin,
      }
    }),
  ]
});

Nun fehlen lediglich noch eine Login- und eine Logout-Komponente damit sich unsere Nutzer gegenüber unserem Identity Provider authentifizieren können. Der bereitgestellte AuthService erleichtert und die Implementierung und Verwaltung des vollständigen Authorization Code Flow mit PKCE-Erweiterung (Listing 2).

 

Listing 2

import { AuthService } from "@auth0/auth0-angular";




@Component({

  selector: "app-login-button",

  template: `

    <button class="button__login" (click)="handleLogin()">Log In</button>

  `
})

export class LoginButtonComponent {
  constructor(private auth: AuthService) {
  }

  handleLogin(): void {
    this.auth.loginWithRedirect({
      prompt: "login"
    });
  }
}

Beim Klick auf den Log-in-Button erfolgt eine Weiterleitung zur Log-in-Maske unseres Identity Providers (Abb. 12). Werfen wir dabei einen Blick auf den Network-Tab unseres Webbrowsers, sehen wir einen Request an den /authorize-Endpoint unseres Identity Provider (Abb. 13). Ebenfalls zu sehen sind die Properties code_challenge_method und code_challenge. Diese sind notwendig für die PKCE-Erweiterung. Die Property response_type mit dem Wert code gibt hierbei an, dass der Client unseren Identity Provider zu einem Authorization Code Flow aufruft.

Ab.12: Log-in-Maske von Auth0

Abb. 13: Payload des Requests

Wenn wir uns nun erfolgreich einloggen, können wir eine weitere Anfrage auf den /token-Endpoint unseres Identity Provider sehen. Der mitgesendete Payload sieht dieses Mal wie folgt aus:

 

client_id: "afRDjs6KFS0TV5NL7NzltUw9Wlktukutyk77"

code: "bigvau6q0DIA9HTbJKcXyRG09j1lZX1jTa8zLAsCIV2Wh"

code_verifier: "zBtJLPMI1TcKpLcOgHoY5ukNGmVnstxhWUksfh1Rso7"

grant_type: "authorization_code"

redirect_uri: "http://localhost:4040/callback"

Wie bereits erwähnt wird bei diesem Aufruf nun der Authorization-Code (code) mitgesendet werden, sowie der Code Verifier (code_verifier), damit unser Identity Provider überprüfen kann, ob beide Anfragen vom selben Client stammen.

Als Antwort erhalten wir das Access-Token sowie ein ID-Token, da OpenID Connect ebenfalls automatisch aktiviert ist (Abb. 14). Diese Tokens werden standardmäßig in einem Cookie abgespeichert. Wir können das SDK so konfigurieren, dass die Tokens im localStorage abgelegt werden, allerdings ist das nur in Verbindung mit einem Refresh-Token ratsam.

Abb. 14: Response mit Access-Token und ID-Token

Das Access-Token können wir nun für die Autorisierung gegenüber verschiedenen Backend-APIs verwenden. Hierfür setzen wir den Authorization-Header bei allen Anfragen auf das entsprechende API. Das kann uns auch von einem von Auth0 bereitgestellten Angular Inteceptor abgenommen werden (Listing 3).

Listing 3

//main.ts

import { provideAuth0, authHttpInterceptorFn } from '@auth0/auth0-angular';

bootstrapApplication(AppComponent, {

  providers: [

    provideAuth0(...),

    provideHttpClient(

      withInterceptors([authHttpInterceptorFn])

    )

  ]

});

Senden wir nun also eine Anfrage an ein geschütztes API, wird automatisch der Authorization-Header mit dem Wert des Access-Tokens gesetzt. Um die Anwendung vollständig abzurunden, fügen wir noch eine Logout-Komponente hinzu, die erneut den AuthService injiziert der uns eine Log-out-Funktion anbietet (Listing 4).

Abb. 15: Access-Token im Header

Listing 4

import { Component, Inject } from "@angular/core";
import { AuthService } from "@auth0/auth0-angular";
import { DOCUMENT } from "@angular/common";

@Component({
  selector: "app-logout-button",
  template: `
    <button class="button__logout" (click)="handleLogout()">Log Out</button>

  `
})
export class LogoutButtonComponent {
  constructor(
    private auth: AuthService,
    @Inject(DOCUMENT) private doc: Document
  ) {
  }


  handleLogout(): void {

    this.auth.logout({ returnTo: this.doc.location.origin });
  }
}

Dieser AuthService bietet uns noch viele weitere Observables an, z. B. isAuthenticated$, um herauszufinden, ob der Benutzer bereits eingeloggt ist oder nicht. Möchten wir hingegen Daten des Benutzers erhalten, wie beispielsweise den Usernamen, einen URL auf ein Profilbild oder die E-Mail-Adresse, so erhalten wir diese Daten, wenn wir uns auf das user$-Observable des AuthServices abonnieren.

Wie wir gesehen haben, gestaltet sich die Integration eines Identity Provider in eine Angular-Applikation recht einfach. Selbst bei anderen SPA-Frameworks wie React oder Vue.js bietet Auth0 entsprechende SDKs an, um auch dort die Integration mit so wenig Aufwand wie möglich zu gestalten. Wenn Sie nicht Auth0 nutzen möchten, bietet Microsoft eigene SDKs [11] zur Integration von Azure Active Directory. Darüber hinaus gibt es auch vielerlei etablierte Open-Source-SDKs [12] für die Integration verschiedener Identity Provider in eine Angular-Anwendung.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Die Absicherung Ihrer Backend-Ressourcen durch Autorisierung ist ein absolutes Muss einer jeden modernen Webanwendung. Der richtige Umgang mit dem Access-Token bietet vielerlei Fallstricke, wie beispielsweise die Auswahl eines schwachen kryptographischen Verfahrens zur Verschlüsselung ihrer Access-Tokens. Nicht signierte Tokens können leicht manipuliert werden, sodass ein Nutzer sich mehr Rechte zuweisen kann, als er eigentlich hat. Mehr als die Hälfte der Top 10 API Security Risks [13], die von der OWASP-Organisation veröffentlicht werden, handeln von fehlerhafter oder falsch implementierter Autorisierung. Es ist also definitiv ratsam, sich nicht selbst an der Implementierung einer Authentifizierungs- und Autorisierungslösung zu versuchen und auf bestehende Lösungen zu setzen, wie sie von Identity Providern zur Verfügung gestellt werden.

Abschließend lässt sich festhalten, dass die Integration von Authentifizierung und Autorisierung in Angular-Anwendungen eine entscheidende Rolle bei der Gewährleistung von Sicherheit und Benutzerfreundlichkeit spielt. Durch die sorgfältige Implementierung von AuthN und AuthZ können Entwickler robuste und sichere Webanwendungen erstellen, die nicht nur den Schutz sensibler Daten ermöglichen, sondern auch ein nahtloses und effizientes Nutzererlebnis bieten. Die in diesem Artikel vorgestellten Methoden und Best Practices bieten dabei einen umfassenden Leitfaden, um diese komplexen Prozesse erfolgreich in Angular-basierten Projekten umzusetzen. Mit dem wachsenden Fokus auf Websicherheit und Datenschutz ist es für Entwickler unerlässlich, sich kontinuierlich weiterzubilden und die neuesten Technologien und Methoden in diesem dynamischen Bereich zu adaptieren.

Links & Literatur

[1] https://it-service.network/it-lexikon/cia-triade
[2] Google Authenticator: https://de.wikipedia.org/wiki/Google_Authenticator
[3] https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security
[4] https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
[5] https://www.microsoft.com/de-de/security/business/identity-access/microsoft-entra-id
[6] https://auth0.com/de
[7] https://www.keycloak.org
[8] https://oauth.net/2.1/
[9] https://auth0.com/pricing
[10] https://www.npmjs.com/package/@auth0/auth0-angular
[11] https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview
[12] https://github.com/manfredsteyer/angular-oauth2-oidc und https://github.com/damienbod/angular-auth-oidc-client
[13] https://owasp.org/API-Security/editions/2023/en/0x11-t10/

The post Authentifizierung und Autorisierung – Sicherheit in einer Angular-Anwendung appeared first on BASTA!.

]]>
Was ist neu in Angular 17? https://basta.net/blog/was-ist-neu-in-angular-17/ Thu, 16 Nov 2023 10:48:34 +0000 https://basta.net/?p=92230 Anfang 2023 hat Sarah Drashner, die als Director of Engineering bei Google unter anderem auch dem Angular-Team vorsteht, den Begriff Angular Renaissance geprägt. Gemeint ist damit eine Erneuerung des Frameworks, das uns nun schon sieben Jahre lang bei der Entwicklung moderner JavaScript-Lösungen begleitet.

The post Was ist neu in Angular 17? appeared first on BASTA!.

]]>
Diese Erneuerung erfolgt inkrementell sowie abwärtskompatibel und berücksichtigt aktuelle Trends aus der Welt der Frontend-Frameworks. Dabei geht es in erster Linie um Developer Experience und Performance. Standalone Components und Signals sind zwei bekannte Features, die bereits im Rahmen dieser Bewegung entstanden sind.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Angular 17 wird im Herbst 2023 weitere Beiträge zur Angular Renaissance leisten: Es kommt mit einer neuen Syntax für den Control Flow, verzögertes Laden von Seitenteilen und einer besseren Unterstützung für SSR. Außerdem setzt das CLI nun auf esbuild und beschleunigt damit den Build erheblich. In diesem Artikel gehe ich anhand einer Beispielanwendung (Abb. 1) auf diese Neuerungen ein. Den verwendeten Quellcode findet man unter [1].

 

Abb. 1: Beispielanwendung

Neue Syntax für Control Flow in Templates

Seit seinen ersten Tagen hat Angular strukturelle Direktiven wie *ngIf oder *ngFor für den Control Flow verwendet. Da es den Control Flow nun für die mit Angular 16 eingeführten Signals ohnehin umfangreich zu überarbeiten gilt, hat sich das Angular-Team entschieden, ihn gleich auf neue Beine zu stellen. Das Ergebnis ist der neue Build-in Control Flow, der sich deutlich vom gerenderten Markup abhebt (Listing 1).

 

Listing 1

@for (product of products(); track product.id) {
  <div class="card">
    <h2 class="card-title">{{product.productName}}</h2>
    […]
  </div>
}
@empty {
  <p class="text-lg">No Products found!</p>
}

 

 

Beachtenswert ist hier unter anderem der neue @empty-Block, den Angular rendert, wenn die zu iterierende Auflistung leer ist.

Auch wenn Signals ein Treiber für diese neue Syntax waren, sind sie keine Voraussetzung für ihre Nutzung. Die neuen Control-Flow-Blöcke lassen sich genauso mit klassischen Variablen oder auch mit Observables im Zusammenspiel mit der async Pipe nutzen.

Der verpflichtende track-Ausdruck erlaubt es Angular, einzelne Elemente, die innerhalb der iterierten Auflistung verschoben wurden, zu identifizieren. Damit lässt sich der Aufwand für das Rendering drastisch reduzieren und bestehende DOM-Knoten wiederverwenden. Beim Iterieren von Auflistungen primitiver Typen, z. B. number oder string, sollte track Angaben des Angular-Teams zur Folge mit der Pseudovariable $index verwendet werden (Listing 2).

 

 

Listing 2

@for (group of groups(); track $index) {
  <a (click)="groupSelected(group)">{{group}}</a>
  @if (!$last) {
    <span class="mr-5 ml-5">|</span>
  }
}

 

Neben $index stehen auch die anderen von *ngFor bekannten Werte über Pseudovariablen zur Verfügung: $count, $first, $last, $even, $odd. Bei Bedarf lassen sich deren Werte über Ausdrücke in Templatevariablen ablegen (Listing 3).

 

Listing 3

@for (group of groups(); track $index; let isLast = $last) {
  <a (click)="groupSelected(group)">{{group}}</a>
  @if (!isLast) {
    <span class="mr-5 ml-5">|</span>
  }
}

 

Das neue @if vereinfacht das Formulieren von else-/else-if-Zweigen (Listing 4).

 

Listing 4

@if (product().discountedPrice && product().discountMinCount) {
  [...]
}
@else if (product().discountedPrice && !product().discountMinCount) {
  [...]
}
@else {
  [...]
}

 

Daneben lassen sich verschiedene Fälle auch mit einem @switch unterscheiden (Listing 5).

 

Listing 5

@switch (mode) {
  @case ('full') {
    [...]
  }
  @case ('small') {
    [...]
  }
  @default {
    [...]
  }
}

 

Im Gegensatz zu ngSwitch und *ngSwitchCase ist die neue Syntax typsicher. Im betrachteten Beispiel müssen die einzelnen @case-Blöcke Stringwerte aufweisen, zumal die an @switch übergebene Variable mode auch vom Typ string ist.

Die neue Control-Flow-Syntax verringert die Notwendigkeit für den Einsatz struktureller Direktiven, die zwar mächtig, aber teilweise auch unnötig komplex sind. Trotzdem wird das Framework strukturelle Direktiven weiterhin unterstützen. Zum einen gibt es einige valide Anwendungsfälle dafür und zum anderen gilt es trotz der vielen aufregenden Neuerungen das Framework abwärtskompatibel zu gestalten.

 

Automatische Migration zum Build-in Control Flow

Wer seinen Programmcode automatisiert auf die neue Control-Flow-Syntax migrieren möchte, findet nun im Paket @angular/core ein Schematic dafür:

 

ng g @angular/core:control-flow

 

Verzögertes Laden von Seitenteilen

Typischerweise sind nicht alle Bereiche einer Seite gleich wichtig. Bei der Detailansicht eines Produkts geht es zunächst um das Produkt selbst. Vorschläge für ähnliche Produkte sind zweitrangig. Das ändert sich jedoch schlagartig, sobald Benutzer:innen die Produktvorschläge in den sichtbaren Bereich des Browserfensters, den sogenannten View Port, scrollen.

Bei besonders performancekritischen Webanwendungen wie Webshops bietet es sich an, solche zunächst weniger wichtigen Seitenteile verzögert zu laden. Das führt dazu, dass die wirklich wichtigen Elemente rascher verfügbar sind. Wer diese Idee bisher in Angular umsetzen wollte, musste sich manuell darum kümmern. Angular 17 vereinfacht auch diese Aufgabe drastisch mit dem neuen @defer-Block (Listing 6).

 

Listing 6

@defer (on viewport) {
  <app-recommentations [productGroup]="product().productGroup"></app-recommentations>
}
@placeholder {
  <app-ghost-products></app-ghost-products>
}

 

Der Einsatz von @defer zögert das Laden der angegebenen Komponente (eigentlich: das Laden des angegebenen Seitenbereichs) hinaus, bis ein bestimmtes Ereignis eintritt. Als Ersatz präsentiert es den unter @placeholder angegebenen Platzhalter. In der hier verwendeten Demoanwendung werden auf diese Weise zunächst Ghost Elements für die Produktvorschläge präsentiert (Abb. 2).

 

Abb. 2: Ghost Elements als Platzhalter

 

Nach dem Laden tauscht @defer die Ghost Elements gegen die tatsächlichen Vorschläge (Abb. 3) aus.

 

Abb. 3: @defer tauscht den Platzhalter gegen die verzögert geladene Komponente

 

Im betrachteten Beispiel kommt das Ereignis on viewport zum Einsatz. Es tritt ein, sobald der Platzhalter in den sichtbaren Bereich des Browserfensters gescrollt wurde. Weitere unterstützte Ereignisse finden sich in Tabelle 1.

 

Trigger Beschreibung
on idle Der Browser meldet, dass gerade keine kritischen Aufgaben anstehen (Standard).
on viewport Der Platzhalter wird in den sichtbaren Bereich der Seite geladen.
on interaction Der Benutzer beginnt, mit dem Platzhalter zu interagieren.
on hover Der Mouse-Cursor wird über dem Platzhalter bewegt.
on immediate So schnell wie möglich nach dem Laden der Seite.
on timer(<duration>) Nach einer bestimmten Zeit, z. B. on timer(5s), um das Laden nach 5 Sekunden anzustoßen.
when <condition> Sobald die angegebene Bedingung erfüllt ist, z. B. when (userName !=== null)

Tabelle 1: Trigger für @defer

 

 

 

Die Trigger on viewport, on interaction und on hover erzwingen standardmäßig die Angabe eines @placeholder-Blocks. Alternativ dazu können sie sich auch auf andere Seitenteile beziehen, die über eine Templatevariable zu referenzieren sind:

 

<h1 #recommentations>Recommentations</h1>
@defer (on viewport(recommentations)) { <app-recommentations [...] />}

 

Außerdem kann @defer angewiesen werden, das Bundle zu einem früheren Zeitpunkt vorzuladen. Wie beim Preloading von Routen stellt diese Vorgehensweise sicher, dass die Bundles augenblicklich verfügbar sind, sobald man sie benötigt:

 

@defer(on viewport; prefetch on immediate) { [...] }

 

Neben @placeholder bietet @defer auch noch zwei weitere Blöcke: @loading und @error. Ersteren zeigt Angular an, während es das Bundle lädt, letzteren im Fehlerfall. Um ein Flackern zu vermeiden, lassen sich @placeholder sowie @loading mit einer Mindestanzeigendauer konfigurieren. Die Eigenschaft minimum legt den gewünschten Wert fest:

 

@defer ( [...] ) { [...] }
@loading (after 150ms; minimum 150ms) { [...] }
@placeholder (minimum 150ms) { [...] }

 

Die Eigenschaft after gibt zusätzlich an, dass der Loading Indicator nur einzublenden ist, wenn das Laden länger als 150 ms dauert.

Build-Performance mit esbuild

Ursprünglich nutzte das Angular CLI webpack für das Bauen von Bundles. Allerdings ist webpack ein wenig in die Jahre gekommen und wird derzeit von jüngeren Werkzeugen, die einfacher zu nutzen und auch um einiges schneller sind, herausgefordert. Bei esbuild [2] handelt es sich um eines dieser Werkzeuge, das mit über 20 000 Downloads pro Woche auch eine beachtenswerte Verbreitung aufweist.

Bereits seit einigen Releases arbeitet das CLI-Team an einer esbuild-Integration. In Angular 16 war diese Integration bereits im Stadium einer Developer Preview enthalten. Ab Angular 17 ist die Implementierung stabil und kommt über den weiter unten beschriebenen Application Builder für neue Angular-Projekte standardmäßig zum Einsatz.

Bei bestehenden Projekten lohnt es sich, eine Umstellung auf esbuild zu prüfen. Dazu ist in der angular.json der Eintrag builder zu aktualisieren:

 

"builder": "@angular-devkit/build-angular:browser-esbuild"

 

Anders ausgedrückt: Am Ende ist -esbuild zu ergänzen. In den meisten Fällen sollte sich daraufhin ng serve und ng build wie gewohnt verhalten, jedoch um einiges schneller sein. Ersteres nutzt zur Beschleunigung den Vite dev-server [3], um npm-Pakete erst bei Bedarf zu bauen. Daneben hat das CLI-Team noch weitere Performanceoptimierungen vorgesehen.

Der Aufruf von ng build konnte durch den Einsatz von esbuild auch drastisch beschleunigt werden. Faktor 2 bis 4 wird häufig als Bandbreite genannt.

SSR ohne Aufwand mit dem neuen Application Builder

Auch die Unterstützung für Server-side Rendering (SSR) wurde mit Angular 17 drastisch vereinfacht. Beim Generieren eines neuen Projekts mit ng new steht nun ein Schalter –ssr zur Verfügung. Kommt dieser nicht zum Einsatz, fragt die CLI, ob sie SSR einrichten soll (Abb. 4).

 

Abb. 4: ng new richtet auf Wunsch SSR ein

 

Um SSR später zu aktivieren, muss lediglich das Paket @angular/ssr hinzugefügt werden:

 

ng add @angular/ssr

 

Wie der Scope @angular verdeutlicht, stammt dieses Paket direkt vom Angular-Team. Es handelt sich dabei um den Nachfolger des Communityprojekts Angular Universal. Um SSR im Zuge von ng build und ng serve direkt zu berücksichtigen, hat das CLI-Team einen neuen Builder bereitgestellt. Dieser sogenannte Application Builder nutzt die oben erwähnte esbuild-Integration und erzeugt damit Bundles, die sowohl im Browser als auch serverseitig nutzen lassen.

Ein Aufruf von ng serve startet auch einen Development-Server, der sowohl serverseitig rendert als auch die Bundles für den Betrieb im Browser ausliefert. Ein Aufruf von ng build –ssr kümmert sich auch um Bundles für beide Welten sowie um das Bauen eines einfachen Node.js-basierten Servers, dessen Quellcode die oben erwähnten Schematics genieren.

Wer keinen Node.js-Server betreiben kann oder möchte, kann mit ng build –prerender die einzelnen Routen der Anwendung bereits beim Build vorrendern.

Weitere Neuerungen

Neben den bis hier diskutierten Neuerungen bringt Angular 17 noch zahlreiche weitere Abrundungen:

 

  • Der Router unterstützt nun das View Transitions API [4]. Dieses von einigen Browsern angebotene API erlaubt das Animieren von Übergängen, z. B. von einer Route auf eine andere mittels CSS-Animationen. Dieses optionale Feature ist beim Einrichten des Routers über die Funktion withViewTransitions zu aktivieren. Zur Demonstration nutzt das Beispiel unter [1] CSS-Animationen, die vom View Transitions API übernommen wurden.
  • Die in Version 16 als Developer Preview eingeführten Signals sind nun stabil. Eine wichtige Änderung gegenüber Version 16 ist, dass Signals nun standardmäßig auf die Nutzung mit Immutables ausgelegt sind. Somit kann Angular einfacher herausfinden, an welcher Stelle die über Signals verwalteten Datenstrukturen geändert wurden. Zum Aktualisieren von Signals lässt sich die Methode set, die einen neuen Wert zuweist, oder die Methode update, die den bestehenden Wert auf einen neuen abbildet, einsetzen. Die Methode mutate wurde entfernt, zumal sie nicht zur Semantik von Immutables passt.
  • Es existiert nun ein Diagnostic, der eine Warnung ausgibt, wenn beim Lesen von Signals in Templates der Aufruf des Getters vergessen wurde (z. B. {{ products }} anstatt {{ products() }}).
  • Animationen können nun lazy geladen werden [5].
  • Das Angular CLI generiert standardmäßig Standalone Components, Standalone Directives und Standalone Pipes. Auch ng new sieht standardmäßig das Bootstrapping einer Standalone Component vor. Dieses Verhalten lässt sich mit dem Schalter –standalone false
  • Die Anweisung ng g interceptor generiert funktionale Interceptors.

Zusammenfassung

Mit Version 17 schreitet die Angular Renaissance voran. Die neue Syntax für den Control Flow vereinfacht den Aufbau von Templates. Dank Deferred Loading können weniger wichtige Seitenbereiche zu einem späteren Zeitpunkt nachgeladen werden. Damit lässt sich der initiale Page-Load beschleunigen. Durch den Einsatz von esbuild laufen die Anweisungen ng build und ng serve merkbar schneller. Außerdem unterstützt das CLI nun direkt SSR und Prerendering.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Links & Literatur

[1] https://github.com/manfredsteyer/hero-shop.git

[2] https://esbuild.github.io

[3] https://vitejs.dev

[4] https://developer.chrome.com/docs/web-platform/view-transitions/

[5] https://riegler.fr/blog/2023-10-04-animations-

The post Was ist neu in Angular 17? appeared first on BASTA!.

]]>
Dependencies Are Eating The Business Domain https://basta.net/blog/dependencies-are-eating-the-business-domain/ Mon, 06 Nov 2023 07:38:58 +0000 https://basta.net/?p=92155 Mit Domain-Driven Design Cards und dem Context Mapping Game stellen wir einen Gamification-Ansatz vor, um komplexe Beziehungsstrukturen zu analysieren und zu visualisieren.

The post Dependencies Are Eating The Business Domain appeared first on BASTA!.

]]>
Vor über einem Jahrzehnt prägte Marc Andreessen mit seinem Statement „Software is eating the world“ die Digitalisierungsbewegung von Unternehmen [1]. Inzwischen haben zahlreiche Geschäftsfelder tiefgreifende Veränderungen erfahren und neue Geschäftsmodelle sind entstanden. Diese sind auf Organisationsstrukturen und IT-Systemarchitekturen angewiesen. Wenn das Geschäftsmodell wächst, müssen auch diese Strukturen mitwachsen und sich neu ausrichten. Wächst das Geschäftsmodell zu schnell, können organisatorische und technische Strukturen oft nur schwer Schritt halten. Das führt zu einem undurchsichtigen und unpassenden Geflecht aus kommunikativen, koordinativen, technischen und fachlichen Abhängigkeiten. Dieses Schicksal, das wir in Anlehnung an Marc Andreessen als „Dependencies are eating the business domain“ bezeichnen, führt dazu, dass die Businessdomäne nur noch langsam oder gar nicht mehr wachsen und sich verändern kann. Unser Lösungsvorschlag: ein Gamification-Ansatz mit Domain-Driven Design Cards und dem Context Mapping Game.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Strategisches Domain-Driven Design

Der strategische Teil des Domain-Driven Designs beinhaltet die Dekomposition der Businessdomäne in Subdomänen und Bounded Contexts. Mit Hilfe einer Context Map werden die Abhängigkeiten zwischen den Bounded Contexts visualisiert. Dabei werden die Abhängigkeiten mittels Context-Mapping-Patterns beschrieben. Letztere stellen eine Mustersprache dar, um Abhängigkeiten auf technischer, fachlicher und organisatorischer Ebene zu gestalten. Auf diese Weise wird eine soziotechnische Architektur der Businessdomäne definiert, die nicht nur den fachlichen Systemschnitt, sondern auch die Entwicklungsteams hinter den Bounded Contexts berücksichtigt [2].

Das Ziel des Domain-Driven Architect besteht darin, Bounded Context auf der strategischen Architekturebene sinnvoll und in den meisten Fällen möglichst unabhängig von anderen Bounded Contexts zu gestalten. Eine optimale Lösung ist nicht immer möglich. Die Transparenz hinsichtlich der Abhängigkeiten sowie deren methodische Betrachtung sind jedoch sehr nützlich. Es geht darum, die Vor- und Nachteile sorgfältig abzuwägen und bewusste Entscheidungen zu treffen. Die bewusste Gestaltung von Abhängigkeiten ist dabei nicht nur zu Beginn eines Projekts wichtig. Zu diesem Zeitpunkt ist oft das Wissen unvollständig und Weiterentwicklungen sind nicht vorhersehbar. Daher ist eine regelmäßige Überprüfung und Anpassung sinnvoll. In der Praxis wird dies aber selten durchgeführt.

Eine Untersuchung der Abhängigkeiten ist besonders dann relevant, wenn das System oder das Projekt aus dem Gleichgewicht gerät. Dies geschieht in der Regel aufgrund verschiedener Arten von Wachstum. Zwei Szenarien, die Anlass geben, die Abhängigkeiten in der strategischen Architektur neu zu überdenken, sind das Wachstum des Systems und das Wachstum des Geschäftsmodells [3] (siehe Kasten: „Wachstum und Folgen“).

 

Wachstum und Folgen

Wachstum des Systems

Das System wächst rasant und die Abhängigkeiten zwischen den internen Komponenten des Systems bauen sich stetig weiter auf. Zusätzlich werden immer mehr externe Systeme angebunden. Die Komplexität der Integration verknüpft sich mit der Herausforderung, unterschiedliche Domänen- und Kommunikationsmodelle zu handhaben. Wenn sich diese Modelle ändern, führt das zu Seiteneffekten und erfordert Anpassungen im System. Das System ist aufgrund der entstandenen Komplexität nur schwer erweiterbar.

Wachstum des Geschäftsmodells

Geschäftsmodelle und Organisationen wachsen, was über die Zeit Anpassungen in der IT-Architektur bedingt. Das ist besonders dann der Fall, wenn das Geschäftsmodell auf einem monolithischen System gewachsen ist. Die Skalierung von Teams bedeutet nun eine Zerlegung des Systems in Subsysteme zur Aufteilung auf mehrere Teams. Dadurch entstehen neue Abhängigkeiten und bereits bekannte Abhängigkeiten zu externen Systemen müssen neu gedacht werden.

SIE LIEBEN AGILE?

Entdecken Sie die BASTA! Tracks

Businessdomäne, Bounded Context und Domänenmodell

Eine Businessdomäne ist der Bereich, in dem ein Unternehmen aktiv ist, am Markt teilnimmt und Kunden Produkte oder Dienstleistungen anbietet. Bounded Contexts sind das Mittel zur Zerlegung dieser Domäne in fachlich unabhängige Module der strategischen Architekturebene (Abb. 1). Ein Bounded Context ist ein fachlich abgrenzbarer Bereich, der gleichzeitig eine physische Grenze zu anderen Bounded Contexts darstellt. Er liegt in der Verantwortlichkeit eines Teams und wird unabhängig von anderen Bounded Contexts versioniert, weiterentwickelt und in Produktion gebracht.

Das Besondere an Domain-Driven Design und am Bounded Context ist, dass das Domänenmodell im Mittelpunkt steht. Durch dieses Modell werden Eigenschaften, Ereignisse und Funktionen der Businessdomäne zum Ausdruck gebracht. Als allgegenwärtige Sprache (Ubiquitous Language) dient das Domänenmodell als Bindeglied zwischen allen Projektphasen (Discover, Plan, Do, Check, Adjust), Architekturebenen (strategische, soziotechnische und taktische Architektur) sowie den beteiligten Personen (Architekt:in, Entwickler:in, Product Owner, Agile Coach, Tester:in, UX-Designer:in etc.).

 

Neben den bereits erwähnten Eigenschaften eines Bounded Context ist der entscheidende Punkt, dass er einen abgegrenzten Bereich (Bounded) um die Bedeutung eines fachlichen Modells (Context) darstellt. Domain-Driven Design formuliert die Heuristik, dass jeder Bounded Context sein eigenes Domänenmodell spezifisch und fachlich eindeutig realisieren sollte. Das Domänenmodell existiert folglich nur in diesem Bounded Context und hat dort Gültigkeit [2], [4]. Fowler [5] erklärt das anhand der Domänenobjekte „Kunde“ und „Produkt“ mittels der unterschiedlichen Perspektiven der Vertriebs- und Supporteinheiten auf diese Geschäftsobjekte. Fachliche Abhängigkeiten existieren in der Realität selbstverständlich trotzdem und müssen als Abhängigkeiten zwischen Bounded Contexts berücksichtigt werden. Das geschieht im Context Mapping.

 

Abb. 1: Dekomposition einer Businessdomäne in Subdomänen und Bounded Contexts

 

Abhängigkeiten zwischen Bounded Contexts gestalten

Wenn zwei Systeme miteinander kommunizieren müssen, ist die resultierende technische Abhängigkeit offensichtlich. Es gibt einen Konsumenten, der auf eine vom Provider angebotene Funktionalität zugreift. Auf der technischen Architekturebene kann sich diese Abhängigkeit vereinfacht wie folgt darstellen (Abb. 2):

 

  1. Der Provider stellt eine Schnittstelle zur Verfügung, die von Konsumenten über eine Netzwerkschnittstelle (z. B. HTTP) oder als Bibliothek (z. B. JAR) genutzt werden kann.
  2. Der Provider veröffentlicht über einen Broker ein Event. Darauf abonnierte Konsumenten empfangen dieses Event durch den Broker und können es weiterverarbeiten.

 

Abb. 2: Provider-Konsument-Beziehung zwischen Systemen

 

Oft wird die daraus resultierende fachliche Abhängigkeit zwischen den Systemen weniger bewusst betrachtet. Provider exponieren ihr Domänenmodell oder einen Teil davon über ihr API oder über Events nach außen. Ändert sich die Fachlichkeit des Providers, ändert sich auch sein Modell und in der Folge das exponierte Modell für die Konsumenten. Für sie stellt sich die Frage, inwieweit das externe Domänenmodell übernommen und in die eigene Domäne integriert werden sollte. Haben die Konsumenten das Ziel, schnell und mit minimalem Aufwand auf Veränderungen des Providers zu reagieren, müssen passende architektonische Lösungsstrategien angewendet werden. Ist das nicht nötig, beispielsweise aufgrund weniger hoher Anforderungen an die Reaktions- und Anpassungszeit oder wenn die Auswirkungen von Veränderungen beim Provider trotz tiefer Integration minimal sind, können einfachere Ansätze genutzt werden.

Im Context Mapping findet sich die fachliche Abhängigkeit als Model Flow wieder. In Abgrenzung dazu wird die technische Abhängigkeit als Call Flow bezeichnet. Der Influence Flow berücksichtigt, dass hinter Bounded Contexts potenziell unterschiedliche Entwicklungsteams stehen (Abb. 3).

 

Abb. 3: Darstellung des Model Flow, Call Flow und Influence Flow der Methode Context Mapping

 

Ist das der Fall, ergibt sich aus der technischen und fachlichen Abhängigkeit auch eine organisatorische Abhängigkeit zwischen diesen Teams. Domain-Driven Design untersucht dabei, wie stark diese Teams ihren gegenseitigen Erfolg beeinflussen. Je stärker die Abhängigkeit ist, desto größer ist der Einfluss [2], [6]. Auf organisatorischer Ebene spiegeln sich Abhängigkeiten mit negativer Tendenz wie folgt wider:

 

  • Notwendigkeit einer abgestimmten oder gemeinschaftlichen Planung der fachlichen Roadmap mit regelmäßigen Zielkonflikten in der Priorisierung
  • erschwerte, mehrdeutige und widersprüchliche Definition der Fachlichkeit
  • Drang, Änderungen in den eigenen Systemen vorzunehmen, aufgrund nicht abgestimmter Änderungen beim Provider
  • Drang, Features zu priorisieren, die vorwiegend auf die Ziele eines Konsumenten einzahlen

 

Bei der Betrachtung der Abhängigkeit zwischen zwei Bounded Contexts werden diese als Upstream- oder Downstream-Kontexte kategorisiert. Damit wird ausgedrückt, welcher Bounded Context weniger abhängig (Upstream) oder stärker abhängig (Downstream) vom anderen ist. Stehen Bounded Contexts in einer Upstream-Downstream-Beziehung, beeinflussen sie ihren Erfolg. Mit Hilfe von Context-Mapping-Patterns wird detaillierter ausgestaltet, wie stark sich die Abhängigkeit und die Beeinflussung des Erfolgs in der Architektur und in der Organisation manifestieren [2]. Ein Team hat Erfolg, wenn es Sprintziele und Termine mit der geforderten Funktionalität einhalten kann. Fehlerfreiheit in der Implementierung, Code- und Architekturqualität drücken ebenfalls den Erfolg eines Entwicklungsteam aus. Die fachliche Trennung mittels Bounded Contexts und die Beziehungsgestaltung mittels Context-Mapping-Patterns haben das Ziel, eine gute Basis für den Erfolg zu schaffen.

Die DDD Crew [6] beschreibt neun Context-Mapping-Patterns. An dieser Stelle werden drei Muster näher erläutert. Unterstützend ist dieser Sachverhalt in Abbildung 4 visualisiert.

Bietet ein Provider eine Schnittstelle mit exponiertem Domänenmodell an, ohne dass es durch Konsumenten beeinflusst werden kann, handelt es sich um einen Open Host Service. Ein Open Host Service kann von vielen Bounded Contexts in gleicher Weise genutzt werden.

 

Abb. 4: Darstellung des Zusammenwirkens der Context-Mapping-Patterns Open Host Service, Conformist und Anticorruption Layer

 

Wenn ein konsumierender Bounded Context das Domänenmodell akzeptiert und tief integriert, entsteht eine starke fachliche Abhängigkeit. Der Bounded Context wird dann als Conformist bezeichnet. Andererseits, wenn das exponierte Domänenmodell an der Grenze des konsumierenden Bounded Context transformiert wird, wird die Abhängigkeit auf einen Punkt, die transformierende Schicht, verlagert. Das wird durch das Muster Anticorruption Layer ausgedrückt. Ein Anticorruption Layer reduziert die Kopplung und befähigt den Konsumenten, schnell auf Veränderungen beim Provider zu reagieren. Der Open Host Service ist immer ein Upstream-Kontext, während der Conformist und der Anticorruption Layer Downstream-Kontexte darstellen [2], [6].

Die ganzheitliche Erstellung einer Context Map für eine Businessdomäne erfordert ein tiefes Verständnis aller Muster und eine intensive, analytische Auseinandersetzung mit den direkten und indirekten Beziehungsgeflechten zwischen allen Bounded Contexts. Das ist sicherlich eine Aufgabe, die Affinität und Expertise seitens der Ersteller:innen voraussetzt.

 

Gamification als Enabler für Context Mapping

Die Kernidee von Gamification besteht darin, Prinzipien und Elemente aus Spielen in andere Kontexte zu integrieren, um Motivation, Kreativität, Engagement und Interaktion von Menschen zu steigern. Ein Erfolgsfaktor ist die intrinsische Motivation sowie der natürliche Spieltrieb von Menschen, gewünschte Verhaltensweisen auszuführen, Aufgaben zu erledigen oder Ziele zu erreichen [7], [8]. Mit Fokus auf Domain-Driven Design Cards werden Spieler:innen als Team mit Herausforderungen konfrontiert, die sie gemeinsam meistern müssen. Dies geschieht in Form von Missionen, die im Spielverlauf abgeschlossen werden müssen. Gamification-Ansätze wie Domain-Driven Design Cards erfreuen sich in verschiedenen Bereichen der Organisations- und Softwareentwicklung großer Beliebtheit. So haben Domain-Driven Design Cards Inspiration in Backlog Refinement Cards [9], unFix Cards [10] oder Cards 42 [11] gefunden.

Ein weiterer wichtiger Aspekt von Domain-Driven Design Cards ist das Ziel, alle Mitglieder der Gruppe gleichwertig zu involvieren, unabhängig von ihrem Wissensstand und Charaktertyp. Das trägt zu den Zielen der Domain-Driven-Design-Philosophie bei, die eine Kultur des breiten gemeinsamen Verständnisses und der Zusammenarbeit anstrebt. Das Context Mapping Game der Domain-Driven Design Cards dient als Enabler für die soziotechnische Architektur mittels Context Mapping. Das Context Mapping Game basiert auf der Annahme, dass in der Realität selten eine Gruppe existiert, die durchgehend über tiefes Wissen im Bereich Domain-Driven Design oder in der Methode des Context Mapping verfügt. Daher ist die Zielsetzung des Context Mapping Game, dass es auch ohne diese Expertise spielbar ist.

Eine kurze Einführung im Vorfeld ist empfehlenswert, insbesondere wenn diese Methode für die Teilnehmer:innen völlig neu ist. Die Teilnahme am Spiel erfordert lediglich ein Grundverständnis der Idee von Context Mapping. Das Spiel ermöglicht die Zusammenarbeit zwischen Entwickler:innen, Architekt:innen, Product Ownern, Fachexpert:innen und Produktmanager:innen, da das Wissen über Abhängigkeiten in einer komplexen Businessdomäne auf viele Köpfe verteilt ist und für alle in gleicher Qualität transparent gemacht werden muss. Die Teilnehmer:innen werden sowohl durch die Spielkarten als auch durch eine:n Moderator:in unterstützt, die Fragen zur Methode des Context Mapping und zum Spiel beantworten kann. Die Durchführung des Spiels bedingt also mindestens eine Person mit Expertise in Domain-Driven Design und Context Mapping. Das Spiel bietet Raum für Kommunikation, Wissensaustausch, Aufbau von Methodenverständnis sowie für die gemeinschaftliche Definition von Abhängigkeiten der Bounded Contexts im eigenen Projektkontext. Bei regelmäßiger Anwendung wird dieses Verständnis gefestigt und inkrementell aufgebaut.

 

Spielanleitung des Context Mapping Game

Im Folgenden soll eine kurze Anleitung zum Context Mapping Game gegeben werden.

 

Ziel des Spiels

Das Ziel des Context Mapping Game ist es, als Team Abhängigkeiten zwischen Bounded Contexts zu ermitteln. Die Analyse eines Bounded Context in Bezug auf seine Abhängigkeiten stellt die Mission des Teams dar. Die Mission gilt als erfüllt, wenn Einigkeit hinsichtlich der Vollständigkeit der ermittelten und definierten Abhängigkeiten besteht und diese widerspruchsfrei als Context-Mapping-Pattern abgebildet sind. Die Abbildung erfolgt mit Hilfe der Spielkarten. In der Regel besteht ein Context Mapping Game aus mehreren Missionen, da komplexe Businessdomänen oft zahlreiche Abhängigkeiten aufweisen. Sobald alle Missionen abgeschlossen sind, endet das Spiel.

 

Spielvorbereitung

Die Vorbedingung für das Spielen des Context Mapping Game ist mindestens eine definierte Mission. Das bedeutet, dass mindestens eine Vision für einen Bounded Context existiert. Diese Vision umfasst Zweck, Verantwortlichkeiten und Existenzbegründung für den Bounded Context sowie die wichtigsten Geschäftsereignisse, Kommandos, Domänenobjekte und Geschäftsregeln. Die Beschreibung des Bounded Context mit den genannten Inhalten bildet das Spielfeld, auf dem die Karten abgelegt werden.

Eine empfehlenswerte Grundlage für das Spielfeld ist das Bounded Context Canvas der DDD Crew (Abb. 5). Ein Canvas ist ein visuelles Werkzeug, um komplexe Konzepte oder Probleme auf übersichtliche und strukturierte Weise darzustellen. Es bietet in der Regel klare Strukturen, die den Beteiligten dabei helfen, Informationen zu organisieren, Ideen zu generieren und Entscheidungen zu treffen. Das Bounded Context Canvas ist ein Instrument, um kollaborativ einen Bounded Context zu definieren und die wichtigsten fachlichen Designentscheidungen visuell zu dokumentieren [12].

 

Abb. 5: Das Bounded Context Canvas der DDD Crew [12]

 

Spielverlauf

Besteht das Spiel aus mehreren Missionen, was bedeutet, dass im Workshop mehrere Bounded Contexts betrachtet werden, wird im ersten Schritt die Mission ausgewählt. Jede Mission wird auf der Spieloberfläche ohne gelegte Spielkarten begonnen. Alle Spieler:innen haben ein Kartendeck in ihren Händen. Die Mission beginnt mit zehn Minuten Brainstorming von Abhängigkeiten (Dependency Storming). Im Anschluss werden gleiche Nennungen geclustert und die Abhängigkeiten in ein- bzw. ausgehende Abhängigkeiten unterteilt und als Sticky Notes auf das Spielfeld gelegt. Danach wird jede Abhängigkeit in einer Timebox von zehn Minuten diskutiert. Nach diesem Austausch legen alle Spieler:innen eine oder mehrere Spielkarten für den Upstream- und Downstream-Kontext auf das Spielfeld. Wie viele Spielkarten je Spieler:in gelegt werden, hängt vom ausgewählten Context-Mapping-Pattern ab. Weitere Details sind unter [13] beschrieben.

Wenn sich eine gemeinsame Basis mit Ausreißern ergibt, kann die Entscheidungsfindung durch die Erklärung der Ausreißer gestartet werden. Wenn das Ergebnis sehr unterschiedlich oder einheitlich ist, beginnt ein:e beliebige:r Spieler:in mit der Schilderung ihrer Sicht. Bei der aufkommenden Diskussion hat der/die Moderator:in die Aufgabe, die Diskussion zu leiten, sodass alle Spielerinnen zu Wort kommen und ein Commitment im Team hinsichtlich der final ausgewählten Spielkarten, d. h. der ausgewählten Context-Mapping-Patterns, erzielt wird. Ist es schwierig, ein Commitment zu erzielen, empfiehlt sich der Einsatz von Methoden für die Entscheidungsfindung, wie z. B. Dot-Voting oder Thumb-Voting. Für Thumb-Voting kann das Context Mapping Game mit unFix Decision Patterns [10] ergänzt werden. Dot-Voting ist sowohl vor Ort mit Stift oder Klebepunkten als auch remote auf einem Collaboration Board einfach durchführbar. Der Spielablauf ist ergänzend in Abbildung 6 dargestellt.

 

Abb. 6: Der Spielablauf des Context Mapping Game

 

Karten

Abbildung 7 zeigt die Domain-Driven Design Cards des Context Mapping Game in der Übersicht. Neben dem Namen des Context-Mapping-Patterns sind die wichtigsten Eigenschaften des Musters aufgeführt. Das fördert den Aufbau von Verständnis, analytischem Denken und Kreativität bei den Spieler:innen und ermöglicht auch Unerfahrenen die Teilnahme. Die Karten sind zum Download kostenlos unter [13] verfügbar.

 

Abb. 7: Die Spielkarten des Context Mapping Game

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Fazit und Ausblick

Ein Blick auf die Rolle der Moderator:innen im Context Mapping Game ist eine nähere Betrachtung wert. Neben der Offenheit und intrinsischen Motivation des Teams ist der/die Moderator:in ein entscheidender Erfolgsfaktor. Nach unserer Erfahrung sind die Moderator:innen oft auch die Methodenexpert:innen für Domain-Driven Design und Context Mapping. Neben der Aufgabe, Fragen zur Methodik zu beantworten, ergeben sich beim Context Mapping Wechselwirkungen und implizite Abhängigkeiten zwischen Bounded Contexts, auf die spontan reagiert werden muss. Dies deckt das Spiel nicht ab.

Ein starkes Team findet sich in der Kombination aus Agile Coach und Domain-Driven Architect. Beide Rollenbilder fördern die Zusammenarbeit und besitzen die Fähigkeit, Workshops zu moderieren und zu gestalten. Der Agile Coach findet sich weiterhin im agilen Entwicklungsprozess wieder und nutzt dort zum Beispiel Backlog Refinement Cards [9], während der Domain-Driven Architect sich intensiv mit der Überführung der strategischen in die taktische Architektur beschäftigt und dafür das Architecture Game der Domain-Driven Design Cards als methodisches Hilfsmittel einsetzt. Auf Ebene der Organisationsentwicklung finden sich Agile Coach und Domain-Driven Architect auf Basis der Context Map wieder, um die fachliche und soziotechnische Architektur nach Domain-Driven Design in einen organisatorischen Rahmen wie Team Topologies oder SAFe zu integrieren, gestützt auf die Verwendung von unFix Cards [10].

Wir erleben Gamification als echten Unterstützer kollaborativer Projektarbeit, wenn die Ansätze auf die notwendige Offenheit treffen.

 

Links & Literatur

[1] Horowitz, Andreessen: „Why Software Is Eating the World“: https://a16z.com/why-software-is-eating-the-world

[2] Plöd, Michael: „Hands-on Domain-driven Design – by example“; Leanpub, 2020

[3] Lilienthal, Carola; Schwentner, Henning: „Domain-Driven Transformation“; dpunkt.verlag, 2023

[4] Khononov, Vlad: „Learning Domain-Driven Design“; O’Reilly, 2021

[5] Fowler, Martin: „BoundedContext“: https://martinfowler.com/bliki/BoundedContext.html

[6] DDD Crew: „Context Mapping“: https://github.com/ddd-crew/context-mapping

[7] Aprea, Carmen; Weckmüller, Heiko: „Gamification in der Qualifizierung und darüber hinaus: Alles nur ein Spiel?“: https://www.haufe.de/personal/neues-lernen/gamification-wie-effektiv-ist-das-spielerische-lernen_589614_534032.html

[8] Hombergs, Tom; Schiller, Thorben: „Gamification als Treiber von Codequalität“: https://www.heise.de/ratgeber/Gamification-als-Treiber-von-Codequalitaet-3759236.html?seite=all

[9] Hyoma, Nils: „Backlog Refinement Cards“: https://www.dependencypoker.com/backlog-refinement-cards

[10] unFix: „unFix Cards“: https://unfix.com/cards

[11] Harrer, Markus et al.: „Cards for Analyzing and Reflecting on Doomed Software“: https://cards42.org

[12] DDD Crew: „The Bounded Context Canvas“: https://github.com/ddd-crew/bounded-context-canvas

[13] Eschhold, Matthias: „Domain-driven Design Cards“: LINK

The post Dependencies Are Eating The Business Domain appeared first on BASTA!.

]]>
Blazor als Insel und im Automodus https://basta.net/blog/blazor-als-insel-und-im-automodus/ Mon, 04 Sep 2023 12:08:48 +0000 https://basta.net/?p=91900 Mit den Previews Nummer 5, 6 und 7 von .NET 8.0 realisiert Microsoft schrittweise die Blazor-United-Versprechungen. Darüber hinaus gibt es neue Sprachfeatures für C# 12.0 sowie Verbesserungen beim AOT-Compiler, bei ASP.NET Core, bei .NET MAUI und sogar ein neues Steuerelement für WPF.

The post Blazor als Insel und im Automodus appeared first on BASTA!.

]]>
Ich berichtete in Windows Developer bereits über vorherige Vorschauversionen von .NET 8.0:

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Bis Einreichung dieses Artikels sind noch Preview 5 (13. Juni 2023), Preview 6 (11. Juli 2023) und Preview 7 (8. August 2023) erschienen.

Als geplanter Erscheinungstermin für die fertige Version von .NET 8.0 wurde inzwischen der 14. November 2023 verkündet. Bis dahin wird es noch zwei weitere Vorschauversionen mit dem Titel „Release Candidate“ geben. Üblich ist, dass Microsoft mit den Release-Candidate-Versionen eine Go-live-Lizenz verbindet, das heißt ab dann ist der produktive Einsatz von .NET 8.0 erlaubt. Das heißt aber nicht, dass es in der Release-Candidate-Phase keine Änderungen mehr geben wird.

 

Blazor United: Ein flexibles Blazor für alle Webarchitekturen

Microsoft hatte am 24. Januar 2023 auf YouTube [1] die Integration von Blazor Server und Blazor WebAssembly zu Blazor United verkündet, sodass aus Benutzerinnen- und Benutzersicht zur Laufzeit ein nahtloser Übergang der Rendering-Arten entsteht.

Blazor United umfasst neben den bisher verfügbaren Blazor-Architekturen für den Webbrowser (Blazor Server und Blazor WebAssembly) die folgenden fünf neuen Architekturen:

 

  1. Blazor Server-side Rendering (Blazor SSR)
  2. Blazor Server-side Rendering mit Streaming (Blazor SSR mit Streaming)
  3. Hosting von Blazor Server innerhalb von Blazor SSR (anstelle des bisherigen Hostings von Blazor Server in ASP.NET Core Razor Pages)
  4. Hosting von Blazor WebAssembly innerhalb von Blazor SSR (anstelle des bisherigen Hostings von Blazor Server in ASP.NET Core Razor Pages)
  5. Wechsel einer Komponente von Blazor Server zu Blazor WebAssembly

 

Spannend ist, dass bei Blazor United diese Architekturmodelle innerhalb eines einzigen Projekts mischbar sind und zwar nicht nur pro Seite, sondern pro einzelner Komponente. Das bedeutet zum Beispiel, dass man eine Webanwendung erschaffen kann, in der alle statischen Inhalte auf dem Server gerendert werden und nur die Teile, die tatsächlich Interaktivität benötigen (z. B. Eingabeformulare, Suchdialoge) dann mit Blazor Server oder Blazor WebAssembly arbeiten. So wird Blazor in Version 8.0 zu einem deutlich universelleren Ansatz für Webanwendungen. Blazor eignet damit auch für öffentliche Websites (z. B. Firmen- und Produktpräsentationen, Webshops) und nicht nur für Intra- und Extranetanwendungen.

 

Blazor SSR als Nachfolger für MVC und Razor Pages

Der Begriff Server-side Rendering (Blazor SSR) kann verwirren. Erste Erfahrungen in der Praxis zeigen, dass viele Entwickler:innen denken: „Das gibt es doch schon mit Blazor Server“. Aber nein, Blazor Server und Blazor SSR sind nicht das Gleiche.

Das schon seit .NET Core 3.1 verfügbare Blazor Server rendert zwar auf dem Server, es entsteht aber dennoch eine Singel Page Application (SPA). Über eine WebSockets-Verbindung werden Unterschiede des Rendering zum vorherigen Rendering (im Programmiererlatein „Diff“ oder „Change Set“ genannt) zum Client gesendet und dort per JavaScript ausgetauscht. Die Benutzer:innen bemerken daher nicht, dass es ein Server-Rendering gab. Die Seite ist genauso interaktiv wie beim Einsatz von Blazor WebAssembly oder eines JavaScript-basierten Webfrontendframeworks wie Angular, React oder VueJS.

Mit dem neuen Blazor Server-side Rendering (Blazor SSR) bietet Microsoft im Rahmen von Blazor nun auch eine rein serverseitige, aus der Clientsicht statische HTML-Erzeugung zu einer Multi Page Application (MPA). Das Rendering-Ergebnis wird bei Blazor SSR in einem Rutsch über eine normale HTTP-Verbindung zum Client gesendet und es werden immer ganze Seiten ausgetauscht (oberer Teil in Abb. 1), sodass der Benutzer ein typisches Flackern der Darstellung beim Seitenwechsel wahrnimmt. Neue HTTP-Anfragen beim Server werden nur durch Hyperlinks oder Formulareinsendungen ausgelöst. .NET- oder JavaScript-Code im Browser sind bei Blazor SSR nicht notwendig. Optional kann man aber per JavaScript Streaming ermöglichen, das Benutzer:innen schon während des Renderings Anzeigen bietet.

Blazor SSR ist schon seit .NET 8.0 Preview 3 verfügbar. Bei Blazor SSR können (mit einigen Abstrichen) die gleichen Razor Components, die bisher bei Blazor Server, Blazor WebAssembly, Blazor MAUI und Blazor Desktop eingesetzt wurden, nun für reines Server-side Rendering verwendet werden. Blazor SSR besitzt grundsätzlich die gleiche Multi-Page-Application-Architektur wie ASP.NET Core Model View Controller (MVC), das es seit .NET Core 1.0 gibt, und ASP.NET Core Razor Pages, eingeführt in .NET Core 2.0 im Jahr 2017.

Der Unterschied zu MVC und Razor Pages ist, dass bei Blazor SSR auf dem Webserver sogenannte Razor Components mit der Blazor-Variante der Razor-Template-Syntax arbeiten, anstelle von Controller plus Razor Views (bei MVC) bzw. Page Model plus Razor Pages (bei Razor Pages). Genau wie bei den älteren Modellen muss auf dem Webserver für Blazor SSR natürlich ASP.NET Core laufen.

 

Abb. 1: Blazor Server-side Rendering ohne Streaming und mit Streaming sowie Blazor Server und Blazor WebAssembly

 

Neue Projektvorlage Blazor Web App

Während man in .NET 8.0 Preview 3 und 4 als Ausgangspunkt für Blazor SSR noch ein ASP.NET-Core-MVC- oder Razor-Pages-Projekt anlegen musste, gibt es seit .NET 8.0 Preview 5 sowie Visual Studio 2022 17.7 Preview 2 eine neue Projektvorlage Blazor Web App (Abb. 2). Alternativ kann man so ein Projekt über die Kommandozeile anlegen:

 

dotnet new blazor --use-server -o Projektname

 

Abb. 2: Einstellungen der Projektvorlage Blazor Web App

 

Die neue Projektvorlage legt die in Blazor schon übliche Webanwendung auf Basis des CSS-Frameworks Bootstrap in Version 5 an. Die Webanwendung zeigt links (Abb. 3 und 4) eine seitliche Navigationsleiste, die sich bei kleinem Browserfenster auf ein Toastmenü rechts oben reduziert. Wenn das Häkchen Use interactive Server components nicht aktiviert wird (auf der Kommandozeile das –use-server weglassen), entstehen nur zwei Seiten und Menüeinträge: Index.razor mit einer Willkommensnachricht und Weather.razor mit der Anzeige zufälliger Wettervorhersagedaten (diese Seite hieß in vorherigen Blazor-Versionen FetchData.razor). Im Ordner /Shared gibt es die Rahmenseite MainLayout.razor und NavMenu.razor für die Navigationsleiste. Den Wurzelcode der HTML-Seite findet man in App.razor. Dort findet man ebenso die Einstellungen für den Blazor-Router (<Found>, <NotFound>, optional auch <Navigating>), der nun sowohl für das Routing auf dem Client als auch auf dem Server zuständig ist. _Imports.razor beinhaltet wie bisher Namensraumimporte, die für alle .razor-Dateien gelten sollen.

 

Abb. 3: Wetterdatenseite Weather.razor während des Ladens

 

Abb. 4: Wetterdatenseite Weather.razor nach dem Laden

 

Während die Startseite Index.razor nur statischen Text zeigt, sieht man im Menüpunkt Weather zunächst eine Ladenachricht („Loading…“, Abb. 3) und dann etwa eine Sekunde später die zufällig generierte Wettervorhersage (Abb. 4). Während man das Datenobjekt als Klasse WeatherForecast in einer eigenen Datei /Models/WeatherForecast.cs findet, steckt der Generierungscode für die zufälligen Wettervorhersagen direkt in Weather.razor (Listing 1).

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

Listing 1: Zufällig generierte Wettervorhersage mit absichtlicher Verzögerung von einer Sekunde in Weather.razor

@page "/weather"
@attribute [StreamRendering(true)]

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data from the server.</p>

@if (forecasts == null)
{

<p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <thead>
      <tr>
        <th>Date</th>
        <th>Temp. (C)</th>
        <th>Temp. (F)</th>
        <th>Summary</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var forecast in forecasts)
      {
        <tr>
          <td>@forecast.Date.ToShortDateString()</td>
          <td>@forecast.TemperatureC</td>
          <td>@forecast.TemperatureF</td>
          <td>@forecast.Summary</td>
        </tr>
      }
    </tbody>
  </table>
}

@code {
  private static readonly string[] Summaries = new[]
  {
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
  };

  private WeatherForecast[]? forecasts;

  protected override async Task OnInitializedAsync()
  {

    // Simulate retrieving the data asynchronously.
    await Task.Delay(1000);

    var startDate = DateOnly.FromDateTime(DateTime.Now);
    forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
      Date = startDate.AddDays(index),
      TemperatureC = Random.Shared.Next(-20, 55),
      Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    }).ToArray();
  }
}

Die Razor Components kommen in der Projektvorlage leider (wie immer bei Microsoft) als Single-File-Komponenten mit Inline-Code (als Blöcke @code { … }) vor. Die Trennung von Programmcode und Layout ist dennoch bei Blazor SSR, wie bei allen anderen Blazor-Arten, möglich. Entwicklerinnen und Entwickler haben die Wahl, Code-Behind-Dateien als partielle Klassen und via Vererbung anzulegen.

Die Verwendung der Schnittstelle IRazorComponentApplication zu Seitenbeginn, die bei Blazor SSR in Preview 3 und 4 noch notwendig war, entfällt seit Preview 5.

Die Zeile @implements IRazorComponentApplication<Confirmation> muss aus älteren Komponenten ersatzlos gestrichen werden.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Verhältnis von Blazor SSR zu MVC und Razor Pages

Blazor SSR lässt die Vorgänger MVC und Razor Pages zu Auslaufmodellen werden, denn die Razor Components von Blazor bieten eine einfachere Syntax, insbesondere für die Formulardatenbehandlung, Datenbindung und das Einbetten von Komponenten ineinander. Zudem bietet Razor Components bei Blazor SSR auch Streaming, was es bei MVC und Razor Pages nicht gibt.

Für eine Migration von MVC oder Razor Pages zu Blazor SSR bietet Microsoft eine Integration an: So kann ein MVC-Controller nicht nur eine View, sondern nun auch eine moderne Razor-Komponente rendern, indem er RazorComponentResult<Komponentenname> zurückliefert (Listing 2).

 

Listing 2

public class MVCRCController : Controller
{
  public IResult Index()
  {
    return new RazorComponentResult<Confirmation>(new { Name = "Dr. Holger Schwichtenberg" });
  }
}

 

Zudem kann man eine solche moderne Razor-Komponente via Tag Helper <component> auch in eine Razor Page (.cshtml-Datei) einbetten:

 

<component type="typeof(Confirmation)" render-mode="Static" param-name='"Dr. Holger Schwichtenberg"' />

 

Das funktioniert bisher schon genauso bei Blazor Server.

Streaming beim Server-side Rendering

Streaming ist eine optionale Funktion von Blazor SSR. Beim Streaming wird initial eine komplette Seite vom Webserver zum Browser übertragen, sobald das erste Rendering der Seite auf dem Webserver stattgefunden hat. Anders als beim normalen SSR-Rendering wird die HTTP-Verbindung aber nicht nach dem Ende des gerenderten Inhalts geschlossen. Falls sich dann nach Ende der Ausführung asynchroner Methoden Teile der gerenderten Seite noch mal ändern, werden diese Änderungen in der offen gehaltenen HTTP-Verbindung noch nachübertragen und die Inhalte per JavaScript in der schon angezeigten Seite ausgetauscht. Indem man ein this.StateHasChanged() in den Programmcode einbaut, werden auch Zwischenstände übertragen, während die asynchrone Methode noch läuft. Man kann also über die offene HTTP-Verbindung auch mehrmals übertragen – aber nur bis zum Ende des Laufs aller asynchronen Methoden auf dem Webserver.

Abbildung 1 zeigt die Architektur von Blazor SSR ohne Streaming und Blazor SSR mit Streaming sowie Blazor Server und Blazor WebAssembly im Vergleich. Wie das Bild zeigt: JavaScript muss der Browser nur für Blazor Server, Blazor WebAssembly und Blazor SSR mit Streaming können, denn nur in diesen Fällen wird eine von Microsoft gelieferte JavaScript-Datei (blazor.server.js, blazor.webassembly.js bzw. blazor.web.js) in den Webbrowser geladen. Bei Blazor SSR wird kein .NET-Code in den Browser geladen und daher ist auch kein WebAssembly im Browser notwendig.

Streaming zeigt die Projektvorlage Blazor Server App, wenn das Häkchen Use interactive Server components (Abb. 2) gesetzt oder auf der Kommandozeile das –use-server verwendet wurde in der Komponente Weather.razor. Die Benutzersicht auf /Weather zeigen Abbildung 3 und 4.

OnInitializedAsync() in Listing 1 (Weather.razor aus der Projektvorlage von Microsoft) implementiert als erste Codezeile eine absichtliche Verzögerung um eine Sekunde, die das Laden von Daten aus einer Datenbank oder einem Webservice simulieren soll. Dass der Benutzer der Webanwendung in der Zwischenzeit „Loading…“ sieht, liegt an der Direktive @attribute [StreamRendering(true)] am Beginn von Weather.razor in Verbindung mit der @if-Abfrage in Listing 3.

 

Listing 3

@if (forecasts == null)
{
  <p><em>Loading...</em></p>
}
else
{
...
}

Wie sich das Streaming aus der Sicht des Webbrowsers darstellt, sieht man in Abbildung 5: Zunächst wird eine komplette HTML-Seite innerhalb des Tags <html>…</html> übertragen. Die Datei mit dem „Loading…“ stellt der Browser dar. Die verzögert eingehenden Render-Ergebnisse aller asynchronen Operationen hängt ASP.NET Core in der gleichen HTML-Antwort noch der Seite an (siehe <template> innerhalb von <blazor-ssr> in Zeilen 42 und 45 in Abbildung 6). Die JavaScript-Datei blazor.web.js, die am Ende der ursprünglichen Seite geladen wurde (Zeile 39 in Abb. 6), baut die in den Tags <template> enthaltenen HTML-Fragmente dann in die ursprünglich dargestellte Seite ein, sodass die Benutzer:innen mit etwas Verzögerung die Wetterdatentabelle statt des Ladehinweises sehen.

Die in Preview 4 eingeführten Sektionen für Blazor (<SectionOutlet> und <SectionContent>) funktionieren seit Preview 7 auch beim Streaming-Rendering sowie in Verbindung mit kaskadierenden Werten und Error Boundaries.

 

Abb. 5: Counter.razor ist zunächst interaktiv über eine WebSocket-Verbindung zum Blazor-Server-Prozess; die Blazor-WebAssembly-Laufzeitumgebung wird im Hintergrund nachgeladen

 

Abb. 6: Serverstreaming bei Blazor SSR: Nach dem </html> kommen noch später eingehende Fragmente in der gleichen HTML-Antwort, die blazor.web.js in die Seite einsetzt

 

Listing 4 zeigt eine Erweiterung des Programmcodeblocks aus Listing 1. Dieses Mal wird zusätzlich zweimal die Überschrift geändert. Der Benutzer sieht (jeweils nach einer Sekunde künstlicher Wartezeit) drei Aktualisierungen der Seite:

 

  • Die Überschrift wird das erste Mal geändert und zeigt eine Ladenachricht.
  • Die Daten werden geladen und als Tabelle dargestellt.
  • Die Überschrift wird das zweite Mal geändert, ebenso die Anzahl der geladenen Datensätze.

 

Wichtig ist nach jedem Schritt, die Methode this.StateHasChanged() auszuführen, sonst sieht der Benutzer keine Aktualisierung der Seite.

 

Listing 4: Erweiterter Code von Weather.razor mit mehreren Bildschirmaktualisierungen

@page "/showdata"
@attribute [StreamRendering(true)]

<PageTitle>@title</PageTitle>

<h1>@title</h1>

<p>This component demonstrates showing data from the server.</p>

@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <thead>
      <tr>
        <th>Date</th>
        <th>Temp. (C)</th>
        <th>Temp. (F)</th>
        <th>Summary</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var forecast in forecasts)
      {
        <tr>
          <td>@forecast.Date.ToShortDateString()</td>
          <td>@forecast.TemperatureC</td>
          <td>@forecast.TemperatureF</td>
          <td>@forecast.Summary</td>
        </tr>
      }
    </tbody>
  </table>
}

@code {
  private static readonly string[] Summaries = new[]
  {
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
  };

  private WeatherForecast[]? forecasts;
  string title = "Weather forecast";
  protected override async Task OnInitializedAsync()
  {
    // 1. UI-Update: Nur Titel
    await Task.Delay(1000);
    title = "Loading Weather forecasts...";
    this.StateHasChanged();

    // 2. UI-Update: Tabelle
    await Task.Delay(1000);
    var startDate = DateOnly.FromDateTime(DateTime.Now);
    forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
      Date = startDate.AddDays(index),
      TemperatureC = Random.Shared.Next(-20, 55),
      Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    }).ToArray();

    this.StateHasChanged();

    // 3. UI-Update: Nochmal Titel
    await Task.Delay(1000);
    title = forecasts.Length + " Weather forecasts loaded";
  }
}

Komponenten als eigenständige Seiten oder eingebettet

Sowohl Index.razor als auch Weather.razor besitzen eine @page-Direktive:

 

@page "/"

 

bzw.

 

@page "/weather"

 

Beide Komponenten sind also über die relativen URLs “/” bzw. “/Weather” direkt im Browser aufrufbar. Wie in Blazor üblich, kann man auch bei Blazor Server-side Rendering jede Komponente auch per Tag in eine andere Komponente einbetten. Wenn man also die Wetterkomponente bereits auf der Startseite sehen will, erweitert man die Index.razor-Datei einfach um das Tag <Weather>:

 

@page "/"
<PageTitle>Home</PageTitle>
<h1>Home</h1>
Welcome to your new app.
<Weather></Weather>

 

Auch die Kurzschreibweise <Weather/> anstelle von <Weather></Weather> ist möglich.

Auch serverseitig gerenderte Blazor-Komponenten können Parameter besitzen. Wie in Blazor üblich, deklariert man sie in der Komponente mit einer C#-Property mit Annotation [Parameter]:

 

[Parameter]
public int Days { get; set; } = 5;

 

Die Property nutzt man dann im Komponentencode, z. B.:

 

forecasts = Enumerable.Range(1, Days).Select(…)

 

 

Nun können Entwicklerinnen und Entwickler den Parameter auf Nutzerseite auf einen anderen Wert als den Standardwert setzen:

 

<Weather Days=”10″></Weather>

 

Die Einbettung von Blazor-Komponenten ineinander ist damit deutlich einfacher als die Arbeit mit Partial Views bei ASP.NET Core MVC und ASP.NET Core Razor Pages [2]. Blazor bietet im Gegensatz zu den Vorgängern ein echtes Komponentenmodell.

Möchte man, dass der Parameter auch per URL gefüllt werden kann, verwendet man [SupplyParameterFromQuery] statt [Parameter]:

 

[SupplyParameterFromQuery]
public int Days { get; set; } = 5;

 

Jetzt kann man die Seite über diesen URL dazu bringen, die Wettervorhersage für 10 Tage zu erzeugen:

 

https://localhost:7009/weather?days=10

 

Auch Razor Components, die keine @page-Direktive haben und daher nur als Teil einer anderen Seite verwendbar sind, können seit Preview 6 [SupplyParameterFromQuery] nutzen. Falls sowohl [SupplyParameterFromQuery] als auch [Parameter] vor einem Property stehen und die Hostkomponente einen Wert per Attribut vorgibt, wiegt das schwerer als eine Angabe eines Parameters im URL.

 

Keine Interaktivität im Client

Bei Blazor SSR ist zu beachten, dass alle Clientereignisbehandlungen (z. B. @onclick, @onmousemove, @onkeydown) nicht funktionieren, denn im Browser ist kein Code, der sie behandeln könnte. Wenn man so ein Ereignis behandelt, sieht man gar nichts, also weder im Browser noch in Visual Studio eine Fehlermeldung. Auch die Ausführung von eigenem JavaScript-Code, z. B.:

 

await js.InvokeVoidAsync("alert", "Hello World");

 

funktioniert nicht. Immerhin kassiert man hier einen Laufzeitfehler: „System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered.“ Visual Studio warnt bislang leider nicht, wenn man in einer Razor-Komponente bei Blazor SSR solche Ereignisse behandelt oder JavaScript-Code ausführt.

Mögliche Clientinteraktionen in Blazor SSR sind, wie in Multi Page Applications üblich:

 

  1. Hyperlinks (<a href=”…”>)
  2. Formulare, die mit HTTP-POST gesendet werden

 

Zudem ist eine serverseitige Navigation mit dem aus Blazor schon bekannten NavigationManager möglich:

 

@inject NavigationManager NavigationManager
...
NavigationManager.NavigateTo("/Weather");

Wenn man das zum Beispiel im Rahmen des Lebenszyklusereignisses OnInitialized() oder nach dem Einsenden eines Formular ausführt, sendet der Webserver eine HTTP-Antwort mit Statuscode 302 und der neuen Adresse im Headerfeld “Location”.

 

Einfachere Formulare bei Blazor SSR

Die in .NET 8.0 Preview 4 und 5 umständliche Handhabung von Formulareingaben beim Server-side Rendering in Blazor via CascadingModelBinder und FormData.Entries.TryGetValue() ist seit .NET 8.0 Preview 6 einfacher, indem Entwicklerinnen und Entwickler nun einfach ein Property mit [SupplyParameterFromForm] annotieren können (Listing 5). Alle in der HTTP-Anfrage gelieferten Name-Wert-Paare werden in die dem Namen entsprechenden Properties abgelegt. Die annotierten Properties können einfache Datentypen, komplexe Typen (Klassen, Strukturen, Records) oder Mengentypen sein. Vorhandene Validierungsannotationen werden berücksichtigt und Validierungsfehlerausgaben sind mit den eingebauten Blazor-Komponenten <ValidationMessage> und <ValidationSummary> möglich. Seit Preview 7 werden auch die Annotationen [DataMember] und [IgnoreDataMember] berücksichtigt, mit denen man die abzubildenden Namen ändern kann.

Seit Preview 7 ist Voraussetzung für die Formularbindung, dass das Tag <EditForm> via Tagattribut FormName einen Namen deklariert:

 

<EditForm method="POST" FormName="Registration" Model="reg" OnValidSubmit="HandleSubmit">

 

Ohne diese Angabe kommt es zum Laufzeitfehler: „The POST request does not specify which form is being submitted. To fix this, ensure <form> elements have a @formname attribute with any unique value, or pass a FormName parameter if using <EditForm>.“ Es ist richtig, was diese Fehlermeldung ebenfalls suggeriert: Seit Preview 7 ist die serverseitige Formularbehandlung auch ohne die eingebaute Blazor-Komponente <EditForm> mit dem Standard-HTML-Tag <form> und dem Zusatz @formname möglich.

 

Listing 5: Vereinfachte Formulardatenbindung einschließlich Validierung bei Blazor Server-side Rendering

@page "/Registration/"
@using NET8_BlazorSSR;
@layout MainLayout
@inject NavigationManager nav

<h3>Bestellung des Fachbuchabos</h3>
<hr />

@if (!reg.Success)
{
  <EditForm method="POST" FormName="Registration" Model="reg" OnValidSubmit="HandleSubmit">
    <DataAnnotationsValidator />
    <p>Ihr Name: <InputText @bind-Value="reg.Name" />   <ValidationMessage For="@(()=>reg.Name)" /></p>
    <p>Ihr E-Mail-Adresse: <InputText @bind-Value="reg.EMail" />   <ValidationMessage For="@(()=>reg.EMail)" /></p>
    <button type="submit">Bestellen</button>
  </EditForm>
}

@if (reg.Success)
{
  <div>
    <p>Liebe(r) @reg.Name,</p>
    <p>vielen Dank für Ihre Registrierung zum Fachbuchabo!</p>
    <p><a href="proxy.php?url=/confirmation/@reg.Name">Bestätigung ausdrucken</a></p>
  </div>
}
@code {
  [SupplyParameterFromForm]
  BookSubscriptionRegistration? reg { get; set; }

  protected override void OnInitialized()
  {
    reg ??= new();
  }

  void HandleSubmit()
  {
    reg.Save();
    reg.Success = true;
  }
  }

 

 

SPA-Inseln mit Blazor Server

Sofern man Use interactive Server components bei der Projektvorlage Blazor Web App wählt, erhält man im Blazor-Projekt eine dritte Seite namens Counter.razor (Quellcode in Listing 6), die man als Counter im Navigationsmenü sieht (Abb. 3 und 4). Der Zähler auf der Seite funktioniert interaktiv auch ohne Verzögerung und Flackern der Seite, das heißt, dass die Seite nicht komplett neu geladen wird. Möglich wird das durch folgende Direktive zu Beginn der Datei Counter.razor:

 

@attribute [RenderModeServer]

 

Das führt zu einer Single-Page-App-Insel innerhalb des Server-side Rendering: Counter.razor wird mit Blazor Server gerendert – wie üblich unter Verwendung einer WebSockets-Verbindung, die aber erst beim ersten Aufruf von Counter.razor aufgebaut wird. Die Projektvorlage zeigt nur eine einzelne solcher Single-Page-App-Inseln und es stellt sich natürlich die Frage, ob es bei mehreren solchen SPA-Inseln auch mehrere WebSockets-Verbindungen gibt. Ein Test mit einer Erweiterung der Projektvorlage zeigt: Nein. Es wird nur eine WebSockets-Verbindung aufgebaut, die dann für alle SPA-Inseln innerhalb der gleichen Blazor-Anwendung zum Einsatz kommt.

Was die Projektvorlage auch nicht zeigt: Eine SPA-Insel kann nicht nur eine Seite mit eigenem URL sein, sondern auch Teil einer anderen Seite. Wenn man zum Beispiel die Counter.razor-Komponente in die Index.razor-Seite einbettet via Tag <Counter></Counter> (oder kurz <Counter/>) funktioniert die Interaktivität der Insel via Blazor Server und WebSockets-Verbindung ebenso.

Neben der Deklaration einer SPA-Insel per Direktive innerhalb der Razor Component selbst, gibt es auch die Möglichkeit, dass der Nutzer einer Komponente den RenderMode vorgibt:

 

<Counter @rendermode="@RenderMode.Server" />

 

Sie sollten sich nicht wundern, wenn Visual Studio nicht bei der Eingabe mithilft: Diese Syntax ist korrekt, aber es gibt in Visual Studio 2022 (bis einschließlich der zum Redaktionsschluss für diesen Beitrag aktuellen Version 17.8 Preview 1) keinerlei IntelliSense-Eingabeunterstützung dafür.

Es ist übrigens nicht möglich, gleichzeitig den Render-Modus in der Komponente mit @attribute [RenderModeServer] und nochmals beim Aufrufer mit @rendermode=”@RenderMode.Server” festzulegen. Ein solchen Versuch quittiert Blazor mit dem Laufzeitfehler: „The component type ‘Counter’ has a fixed rendermode of ‘Microsoft.AspNetCore.Components.Web.ServerRenderMode’, so it is not valid to specify any rendermode when using this component.“

 

Listing 6: Counter.razor mit dem RenderModeServer @page “/counter”

@attribute [RenderModeServer]

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
  private int currentCount = 0;

  private void IncrementCount()
  {
    currentCount++;
  }
}

 

Startdateien bei Blazor SSR

Die Voraussetzungen für Blazor SSR, Streaming und SPA-Inseln legt die Projektvorlage Blazor Web App automatisch an. Dazu gehört:

 

  1. Für Blazor SSR braucht man in der cs einen Aufruf von AddRazorComponents() und MapRazorComponents<App>(): builder.Services.AddRazorComponents() und app.MapRazorComponents<App>()
  2. Für SPA-Inseln mit Blazor Server braucht man in der cs zusätzlich einen Aufruf von AddServerComponents() und .AddServerRenderMode(): builder.Services.AddRazorComponents().AddServerComponents() und app.MapRazorComponents<App>().AddServerRenderMode()
  3. Für das Streaming braucht man in der razor dieses Tag zum Laden der JavaScript-Datei:
<script src="proxy.php?url=_framework/blazor.web.js" suppress-error="BL9992"></script>

SPA-Inseln mit Blazor WebAssembly

SPA-Inseln mit Blazor Server hat Microsoft in .NET 8.0 Preview 5 eingeführt, wobei das @rendermode in der Hostseite erst mit Preview 6 kam. Während in .NET 8.0 Preview 5 die SPA-Inseln ausschließlich mit Blazor Server möglich waren, bietet Microsoft seit .NET 8.0 Preview 6 an dieser Stelle alternativ nun auch Blazor WebAssembly an.

Bisher gibt es für diese Konstellation keine Projektvorlage; man kann aber die Projektvorlage Blazor Web App dazu selbst manuell umbauen. Wesentliche Voraussetzung ist, dass sich alle mit Blazor WebAssembly zu rendernden Razor-Komponenten in einer separaten, referenzierten Razor Class Library (also einer eigenen DLL) befinden, die auf dem SDK Microsoft.NET.Sdk.BlazorWebAssembly basiert:

 

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

 

Diese Abtrennung ist notwendig, um den in den Browser zu ladenden Code von dem Servercode abzugrenzen.

Auch das Hauptprojekt (nun nur noch mit dem Code, der nur auf dem Server laufen soll) muss man wie folgt umbauen:

 

  1. Man muss das Paket AspNetCore.Components.WebAssembly.Server referenzieren (hier mit der Versionsnummer für die .NET 8.0 Preview 7):
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.0-preview.7.23375.9" />
  1. Dann muss man in der Startdatei cs die Methode AddWebAssemblyComponents()nach AddRazorComponents() aufrufen:
builder.Services.AddRazorComponents().AddWebAssemblyComponents();
  1. Bei MapRazorComponents<App>() muss man die Methode AddWebAssemblyRenderMode() nutzen:
app.MapRazorComponents<App>().AddWebAssemblyRenderMode();

 

Nun ist es möglich, eine Komponente aus dieser DLL mit dem Render-Modus WebAssembly zu nutzen:

 

<Counter @rendermode="@RenderMode.WebAssembly" />

 

Alternativ kann diesen Render-Modus auch die Komponente selbst deklarieren:

 

@attribute [RenderModeWebAssembly]

 

Im Standard werden solche WebAssembly-Komponenten serverseitig vorgerendert. Das können Entwicklerinnen und Entwickler bei Bedarf ausschalten:

 

@attribute [RenderModeWebAssembly(prerender: false)]

 

bzw.

 

<Counter @rendermode="@(new WebAssemblyRenderMode(prerender: false))

 

Ein funktionierendes Beispiel für die Integration von Blazor WebAssembly in eine Anwendung mit Blazor Sever-side Rendering findet man unter [3].

 

Modus für das Blazor-Rendering

In .NET 8.0 Preview 7 kam dann zum RenderMode.Server und RenderMode.WebAssembly auch noch der RenderMode.Auto hinzu, z. B.:

 

<Counter IncrementAmount="1" @rendermode="@RenderMode.Auto" />

 

Alternativ kann eine Blazor-Komponente den Automodus statisch in ihrer Definition selbst festlegen:

 

@attribute [RenderModeAuto]

 

Dazu aktiviert man in der Startdatei beide Render-Modi (also Blazor Server und Blazor WebAssembly) nacheinander:

 

builder.Services
.AddRazorComponents()
.AddWebAssemblyComponents()
.AddServerComponents();

und

 

app.MapRazorComponents<App>()
.AddWebAssemblyRenderMode()
.AddServerRenderMode();

 

Falls die Blazor-WebAssembly-Laufzeitumgebung innerhalb von 100 Millisekunden geladen werden kann, verwendet der Automodus immer Blazor WebAssembly. Eine so kurze Zeit kann allerdings nur in sehr schnellen Netzwerken erreicht werden oder wenn die Laufzeitumgebung schon im Cache des Browsers liegt. Falls ASP.NET auf dem Webserver diese Zeitspanne als nicht erreichbar ansieht, wird die Komponente zunächst per Blazor Server gerendert und eine WebSockets-Verbindung zwischen Browser und Webserver für die Interaktivität aufgebaut.

Die Blazor-WebAssembly-Laufzeitumgebung lädt dann im Hintergrund nach (Das Laden der .wasm-Dateien nach dem Aufbau der WebSockets-Verbindung ist in Abbildung 6 zu sehen.). Ein Wechsel zu Blazor WebAssembly erfolgt dann aber nicht im laufenden Betrieb der Komponente, sondern erst, wenn die einzelne Komponente neu initialisiert wird, zum Beispiel durch einen zwischenzeitlichen Wechsel zu einer anderen Seite. Es muss aber nicht die ganze Anwendung neu geladen werden.

Der neue Automodus bietet den Vorteil einer schnellen ersten Sicht auf die Inhalte für die Benutzer:innen, die aber dennoch via WebSockets interaktiv ist. Bei der weiteren Nutzung kann dann das im Hintergrund geladene Blazor WebAssembly zum Einsatz kommen, wodurch die Nutzung der Komponente auch bei instabiler Netzwerkverbindung möglich wird. Hier wird man noch Konzepte brauchen, wie man Benutzer:innen so lenkt, dass die Komponente ein weiteres Mal initialisiert wird, denn sonst vollzieht sich der Wechsel auf Blazor WebAssembly ja nicht.

Bisher gibt es für den Automodus keine Projektvorlage. Interessierte Entwickler:innen finden ein Beispiel mit einer einzigen Komponente im Automodus (Counter.razor) auf GitHub [4]. Man sieht darin, dass die Komponente, die im Automodus laufen soll, in einer getrennten Assembly liegen muss. Die Tatsache, dass es für die Komponente Counter.razor im Projekt Client auch noch eine korrespondierende Datei Counter.razor im Projekt Server gibt, ist ein temporärer Workaround, weil die Routen aus Razor Class Libraries derzeit nicht im Serverprojekt erfasst werden. Microsoft arbeitet daran, das via .AddComponentAssemblies() im Startcode zu erledigen [5].

Was das Beispielprojekt nicht zeigt: Falls die Komponente Daten aus Datenbanken abruft, muss sie so gestaltet sein, dass das nicht nur bei Blazor Server, sondern auch Blazor WebAssembly funktioniert. Das bedeutet: Es darf nicht generell ein direkter Datenbankzugriff erfolgen, sondern man muss entweder immer die Daten per Web-Service/Web-API laden oder aber man muss eine Abstraktion einbauen, die die Daten bei Blazor Server per Direktzugriff lädt und bei Blazor WebAssembly per Web-Service/Web-API.

 

Mischung der Render Modes

Das Mischen von SPA-Inseln mit Blazor WebAssembly, Blazor Server und dem Automodus in einer Anwendung und sogar in seiner Seite ist möglich (Listing 7), sofern beide Render-Modi in der Startdatei aktiviert wurden:

 

builder.Services
.AddRazorComponents()
.AddWebAssemblyComponents()
.AddServerComponents();

und

 

app.MapRazorComponents<App>()
.AddWebAssemblyRenderMode()
.AddServerRenderMode();

 

Listing 7 zeigt eine Startseite Index.razor, die eine Komponente Counter.razor einmal per Blazor Server und einmal per Blazor WebAssembly einbindet. Abbildung 7 zeigt die Auswirkung auf den Webbrowser: Der Browser baut für Blazor Server eine WebSockets-Verbindung auf und lädt danach die WebAssembly-Dateien für Blazor WebAssembly. Die Rahmenkomponente Home.razor entsteht rein per Server-side Rendering.

Die Ausgabe, mit welchem Render-Modus das Rendering erfolgt ist, geschieht auf Basis des Typs, den Blazor für IJSRuntime injiziert. Das ist Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime bei Blazor Server und Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime bei Blazor WebAssembly. Leider gibt es für den aktuellen Render-Modus noch keine direkte Abfrage auf Komponentenebene.

 

Listing 7: Home.razor nutzt eine explizite Mischung von Blazor Server und Blazor WebAssembly in einer Seite

@page "/"
<PageTitle>Mischung von Blazor Server und Blazor WebAssembly</PageTitle>

<hr />
<h3>1. Counter</h3>
<hr />
<div>
  <Net8BlazorAuto.Client.Pages.Counter CurrentCount="42" @rendermode="@RenderMode.Server" />
</div>
<hr />
<h3>2. Counter</h3>
<hr />
<div>
  <Net8BlazorAuto.Client.Pages.Counter CurrentCount="42" @rendermode="@RenderMode.WebAssembly" />
</div>
<hr />

 

Abb. 7: Der Browser baut für Blazor Server eine WebSockets-Verbindung auf und lädt für Blazor WebAssembly die WebAssembly-Dateien

 

Abbildung 7 zeigt auf der linken Seite auch, dass die Counter.razor-Komponente jeweils nun auch eine Unterkomponente mit gelblichem Kasten bekommen hat. Diese Unterkomponenten (Unterkomponente.razor) verwenden automatisch den gleichen Render-Modus wie die Host-Komponente.

Nun könnte man auf die Idee kommen, diesen Unterkomponenten wieder einen expliziten Render-Modus zu verpassen. Das funktioniert aber nicht, denn wenn man versucht, in einer per Blazor Server gerenderten Komponente eine Blazor-WebAssembly-Komponente zu laden, erhält man die Laufzeitfehlermeldung:

 

"Cannot create a component of type 'Net8Blazor.Client.Pages.Unterkomponente' because its 
render mode 'Microsoft.AspNetCore.Components.Web.WebAssemblyRenderMode' is not supported by 
interactive server-side rendering."

 

Umgekehrt, wenn man in einer per Blazor WebAssembly gerenderten Komponente eine Komponente explizit per Blazor Server rendert, heißt der Fehler: „Cannot create a component of type ‘Net8Blazor.Client.Pages.Unterkomponente’ because its render mode ‘Microsoft.AspNetCore.Components.Web.ServerRenderMode’ is not supported by WebAssembly rendering.“ Möglich ist aber, Unterkomponente.razor auf den Automodus zu setzen und damit als Unterkomponente in beiden Render-Modi zu verwenden.

Hinsichtlich der Mischung von Render-Modi bedeutet das also: Innerhalb einer per Blazor SSR gerenderten Komponente kann man über Blazor Server und Blazor WebAssembly gerenderte Komponenten beliebig mischen. Wenn allerdings einer dieser beiden Render-Modi für eine Komponente vorgegeben ist, kann man in untergeordneten Komponenten den Render-Modus nicht mehr wechseln.

 

Andere Blazor-Projektvorlagen

Wenn man sich die Projektvorlagen mit dem Wort „Blazor“ in Visual Studio oder per Kommandozeile (Abb. 8) ansieht, findet man dort neben der neuen Blazor Web App auch die altbekannten Vorlagen für Blazor WebAssembly, Blazor Server und Blazor MAUI. Bei der Nutzung der Projektvorlagen für Blazor Server stellt man aber fest, dass im Visual-Studio-Dialog .NET 8.0 für Blazor Server nicht angeboten wird. Wenn man versucht, .NET 8.0 an der Kommandozeile zu erzwingen

 

dotnet new blazorserver --framework net8.0

 

sieht man die Fehlermeldung in Abbildung 9. Tatsächlich will Microsoft die Projektvorlage Blazor Server nicht mehr anbieten, denn Blazor Server ist nun ein Teil der allgemeinen Vorlage Blazor Web App. An die Stelle der bisherigen Rahmencodes in _Host.cshtml und der Fehlerbehandlung in Error.cshtml (beides sind Razor Pages!) tritt nun eine komplette Lösung mit Razor Components (also Blazor-Komponenten!).

Eine Projektvorlage für Blazor WebAssembly gibt es noch im .NET 8.0 SDK und Visual Studio 2022 mit Auswahl .NET 8.0, aber die Option ASP.NET Core hosted ist verschwunden.

Diese Änderungen begründet Microsoft im Blogeintrag zu .NET 8.0 Preview 6 [6]: „Im Rahmen der Vereinheitlichung der verschiedenen Blazor-Hostingmodelle in einem einzigen Modell in .NET 8 konsolidieren wir auch die Anzahl der Blazor-Projektvorlagen. In dieser Vorschauversion haben wir die Blazor-Server-Vorlage und die Option ‚ASP.NET Core hosted‘ aus der Blazor-WebAssembly-Vorlage entfernt. Beide Szenarien werden durch Optionen dargestellt, wenn die neue Blazor-Web-App-Vorlage verwendet wird.“.

Möglicherweise wird Microsoft die Produktbezeichnungen Blazor Server und Blazor WebAssembly in Zukunft auch gar nicht mehr verwenden, sondern nur noch allgemein von „Blazor“ und den Render-Modi „server-side“ (gleich Blazor SSR), „interactive server-side“ (gleich Blazor Server) und „WebAssembly“ (gleich Blazor WebAssembly) sprechen.

 

Abb. 8: Blazor-Projektvorlagen im .NET 8.0 SDK

 

Abb. 9: Fehlermeldung beim Versuch, ein Projekt mit der Projektvorlage Blazor Server für .NET 8.0 anzulegen

 

Globale kaskadierende Blazor-Werte

Kaskadierende Werte, die man bisher nur in Razor-Komponenten definieren konnte, können Entwicklerinnen und Entwickler in .NET 8.0 im Startcode einer Blazor-Anwendung innerhalb der Program.cs registrieren, um diese Werte als Zustand für alle Komponenten in einer Komponente verfügbar zu machen. Dazu registriert man ein Objekt wahlweise ohne expliziten Namen:

 

builder.Services.AddCascadingValue(sp => new Autor { Name = "Dr. Holger Schwichtenberg1", Url = "www.dotnet-doktor.de" });

 

oder mit Namen:

 

builder.Services.AddCascadingValue("autor2", sp => new Autor { Name = "Dr. Holger Schwichtenberg", Url = "www.dotnet-doktor.de" });

 

oder via CascadingValueSource:

 

builder.Services.AddCascadingValue(sp =>
  {
    var a = new Autor { Name = "Holger Schwichtenberg", Url = "www.dotnet-doktor.de" };
    var source = new CascadingValueSource<Autor>("autor3",a, isFixed: false);
    return source;
});

 

Dann kann jede Razor-Komponente innerhalb der Blazor-Anwendung dieses Objekt konsumieren und auch verändern (Listing 8).

 

Listing 8

@code
{
  [CascadingParameter]
  Autor autor1 { get; set; }

  [CascadingParameter(Name = "autor2")]
  Autor autor2 { get; set; }

  [CascadingParameter(Name = "autor3")]
  Autor autor3 { get; set; }

  [ParameterAttribute]
  public int id { get; set; }

  protected override void OnInitialized()
  {
    autor3.Name = "Dr. " + autor3.Name;
  }
}

Antiforgery-Token zum Schutz gegen Cross-Site Request Forgery

Für den Schutz gegen Angriffe nach dem Prinzip der Cross-Site Request Forgery (CSRF/XSRF) liefert Microsoft in ASP.NET Core 8.0 ab Preview 7 eine neue Middleware, die Entwicklerinnen und Entwickler im Startcode einer ASP.NET-Core-Anwendung via builder.Services.AddAntiforgery(); integrieren können. Dieser Aufruf aktiviert zunächst nur in der Verarbeitungs-Pipeline das IAntiforgeryValidationFeature. Ein auf ASP.NET Core aufbauendes Webframework (z. B. Blazor, WebAPI, MVC, Razor Pages) muss sodann ein Antiforgery-Token im Programmcode berücksichtigen. Der Blogeintrag [7] zeigt Implementierungsbeispiele für ASP.NET Core Minimal APIs [8] und Blazor (bei Verwendung von <EditForm>) [9], lässt aber offen, wie der Implementierungsstatus für andere ASP.NET-Core-basierte Webframeworks wie MVC und Razor Pages ist.

 

WebCIL ist nun der Standard bei Blazor WebAssembly

Seit .NET 8.0 Preview 4 gibt es das WebCIL-Format für Blazor WebAssembly; seit Preview 7 ist es nun im Standard aktiv. Die Implementierung von WebCIL zusammen mit WebAssembly-Modulen zielt darauf ab, die Blockierung von Blazor WebAssembly durch Firewalls und Antivirensoftware zu verhindern. Microsoft hat das Format inzwischen so angepasst, dass Standard-WebAssembly-Module mit der Dateinamenserweiterung .wasm generiert werden (in Preview 4 war es noch .webcil).

Entwicklerinnen und Entwickler haben die Option, WebCIL zu deaktivieren, indem man den Schalter <WasmEnableWebcil>false</WasmEnableWebcil> in der Projektdatei verwendet. In Blogeintrag [10] gibt Microsoft jedoch keine expliziten Hinweise darauf, in welchen Szenarien das sinnvoll sein könnte. Bisher waren die Blazor-WebAssembly-Dateien standardmäßig mit der Erweiterung .dll versehen.

 

Identity Server ist raus

Gemäß der Ankündigung auf der Build-Konferenz im Mai 2023 hat Microsoft den Duende Identity Server nun aus seinen Projektvorlagen für React- und Angular-Projekte mit einem ASP.NET-Core-Backend entfernt. Dieser Schritt wurde vom .NET-Entwicklungsteam unternommen, da der Identity Server, der in den Versionen 1 bis 4 als Open-Source-Software und Projekt der gemeinnützigen .NET Foundation verfügbar war, mittlerweile zu einem kommerziellen Produkt geworden ist. Ausgenommen von den Lizenzgebühren sind lediglich Open-Source-Projekte. Microsoft hat sich entschieden, die besagten Projektvorlagen nun auf das in ASP.NET Core integrierte Identity-System zu stützen. Für Entwicklerinnen und Entwickler, die den Identity Server weiterhin nutzen möchten, stellt Microsoft die Dokumentation von Duende zur Verfügung [11].

 

Weitere WebAPIs für ASP.NET Core Identity

In .NET 8.0 Preview 4 hatte Microsoft für ASP.NET Core Identity, das integrierte Benutzerverwaltungssystem von ASP.NET Core, erstmals WebAPI-Endpunkte eingeführt. Diese Ergänzung erfolgte neben der bereits vorhandenen Weboberfläche, die auf serverseitigem Rendering basiert. Dies geschah, um ASP.NET Core Identity effektiver in Single-Page-Webanwendungen (unter Verwendung von JavaScript oder Blazor) zu integrieren. Zuvor beschränkten sich die verfügbaren WebAPI-Endpunkte lediglich auf die Benutzerregistrierung (/register) und die Benutzeranmeldung (/login).

In Preview-Version 7 wurden weitere Endpunkte hinzugefügt. Diese umfassen die Bestätigung von Benutzerkonten per E-Mail (/confirmEmail und /resendConfirmationEmail), das Zurücksetzen von Passwörtern (/resetPassword), die Aktualisierung von Tokens (/refresh) sowie die Unterstützung für die 2-Faktor-Authentifizierung (/account/2fa) sowie das Lesen und Aktualisieren von Profildaten (/account/info).

Die Aktivierung dieser WebAPI-Endpunkte für ASP.NET Core Identity erfolgt in der Datei Program.cs, indem nach AddIdentityCore<T>() die Funktion AddApiEndpoints() aufgerufen wird, wie in Listing 9 dargestellt.

 

Listing 9: Startcode einer ASP.NET-Core-Anwendung, die eine Benutzerverwaltung via ASP.NET Core Identity via WebAPI bereitstellt (Quelle: [12])

using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();

builder.Services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("AppDb"));

builder.Services.AddIdentityCore<MyUser>()
                .AddEntityFrameworkStores<AppDbContext>()
                .AddApiEndpoints();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Adds /register, /login and /refresh endpoints
app.MapIdentityApi<MyUser>();

app.MapGet("/", (ClaimsPrincipal user) => $"Hello {user.Identity!.Name}").RequireAuthorization();

if (app.Environment.IsDevelopment()
{
  app.UseSwagger();
  app.UseSwaggerUI();
}

app.Run();

class MyUser : IdentityUser { }

class AppDbContext : IdentityDbContext<MyUser>
{
  public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

 

Fehlertoleranz bei ASP.NET Core SignalR

Der Benachrichtigungsdienst ASP.NET Core SignalR kann seine Verbindung nun nach kurzen Verbindungsabbrüchen automatisch nahtlos wiederherstellen (Seamless Reconnect). Seit .NET 8.0 Preview 5 funktioniert das aber vorerst nur mit .NET-Clients. Ein .NET-Client muss die Verbindung mit dem Zusatz UseAcks = true aufbauen:

 

var hubConnection = new HubConnectionBuilder()
  .WithUrl("https://ServerName/Hub">",
    options =>
    {
      options.UseAcks = true;
  })
  .Build();

 

Es gibt noch keinerlei Konfiguration dieser Fehlertoleranz. Das ist aber in Arbeit [13]. Von der neuen Fehlertoleranz wird auch Blazor Server profitieren, weil dort ASP.NET Core SignalR für die Übertragung der Seitenänderungen zum Einsatz kommt.

 

Neuerungen in C# 12.0

Für C# 12.0 gibt es vier neue Sprachfeatures in den Previews 5 bis 7. Die wesentliche Neuerung sind dabei die Collection Literals. Mit dieser neuen Syntaxform kann man die bisher sehr heterogenen Initialisierungsformen von Objektmengen im Stil von JavaScript stark vereinheitlichen, also mit den Werten in eckigen Klammern, getrennt durch Kommata (Tabelle 1).

 

Bisherige Initialisierung Nun auch möglich
int[] a = new int[3] { 1, 2, 3 }; int[] a = [1,2,3];
Span<int> b = stackalloc[] { 1, 2, 3 }; Span<int> b = [1,2,3];
ImmutableArray<int> c = ImmutableArray.Create(1, 2, 3); ImmutableArray<int> c = [1,2,3];
List<int> d = new() { 1, 2, 3 }; List<int> d = [1,2,3];
IEnumerable<int> e = new List<int>() { 1, 2, 3 }; IEnumerable<int> e = [1,2,3];

Tabelle 1: Collection Literals in C# 12.0

 

Weitere Neuerungen in C# 12.0 sind:

 

  • Der Operator nameof funktioniert jetzt auch mit Mitgliedsnamen, einschließlich Initialisierern, bei statischen Mitgliedern und in Attributen (Listing 10).
  • Ein Interceptor erlaubt, einen Methodenaufruf abzufangen und umzulenken. Das will Microsoft vor allem einsetzen, um mehr Code kompatibel zum Ahead-of-Timer-Compiler zu machen [14].
  • Zur Optimierung gibt es jetzt Inline-Arrays [14].

 

Listing 7: Erweiterungen für nameof() in C# 12.0

/// <summary>
/// nameof-Erweiterung in C# 12.0, siehe auch:
/// https://github.com/dotnet/csharplang/issues/4037
/// </summary>
[Description($"{nameof(StringLength)} liefert die Eigenschaft {nameof(Name.Length)}")]
public struct Person
{
public string Name;
public static string MemberName1() => nameof(Name); //  bisher schon möglich
public static string MemberName2() => nameof(Name.Length); // bisher: error CS0120: An object reference is required for the non-static field, method, or property 'Name.Length'

public void PrintMemberInfo()
{
Console.WriteLine($"Die Struktur {nameof(Person)} hat ein Mitglied {nameof(Name)}, welches eine Eigenschaft {nameof(Name.Length)} besitzt!"); // Die Struktur Person hat ein Mitglied Name das eine Eigenschaft Length besitzt!
}

[Description($"{nameof(StringLength)} liefert die Eigenschaft {nameof(Name.Length)}")]
public int StringLength(string s)
{
return s.Length;
}
}

Verbesserungen beim Native-AOT-Compiler

Die seit .NET 8.0 Preview 3 verfügbare Ahead-of-Time-Kompilierung für ASP.NET Core Minimal WebAPIs kommt nun auch mit komplexen Objekten klar, die mit [AsParameters] annotiert sind [15].

Microsoft stellt seit .NET 8.0 Preview 6 die Möglichkeit zur Verfügung, .NET-Anwendungen für iOS, Mac Catalyst und tvOS mit Hilfe des neuen .NET-Native-AOT-Compilers zu kompilieren. Diese Möglichkeit steht sowohl für Apps, die auf diese Plattformen beschränkt sind („.NET for iOS“), als auch für das .NET Multi-Platform App UI (.NET MAUI) zur Verfügung. Dadurch laufen die Anwendungen nicht mehr auf Mono, und die App-Pakete für „.NET for iOS“ werden spürbar kompakter. Hingegen verzeichnen die App-Pakete für .NET MAUI eine Zunahme in ihrer Größe (Abb. 10). In einem Blogeintrag [16] hat Microsoft bestätigt, dass dieses Problem erkannt wurde und aktiv daran gearbeitet wird, eine Lösung zu finden, die zu einem Größenvorteil von etwa 30 Prozent führt.

 

Abb. 10: Verkleinerte App-Pakete durch Native AOT (Bildquelle: Microsoft [16])

Verbesserungen für System.Text.Json

In .NET 8.0 Preview 6 und 7 gab es wieder einmal Verbesserungen für den JSON-Serialisierer/-Deserialisierer im NuGet-Paket System.Text.Json.

System.Text.Json vermag nun neuere Zahlentypen wie Half, Int128 und UInt128 sowie die Speichertypen Memory<T> und ReadOnlyMemory<T> zu serialisieren. Bei den Letzteren entstehen, wenn es sich um Memory<Byte> und ReadOnlyMemory<Byte> handelt, Base64-kodierte Zeichenketten. Andere Datentypen werden als JSON-Arrays serialisiert.

 

  • Beispiel 1:
JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 42, 43, 44 }); wird zu "Kiss"
  • Beispiel 2:
JsonSerializer.Serialize<Memory<Int128>>(new Int128[] { 42, 43, 44 }); wird zu [42,43,44]
  • Beispiel 3:
JsonSerializer.Serialize<Memory<string>>(new string[] { "42", "43", "44" }); wird zu ["42","43","44"]

 

Zusätzlich dazu ermöglichen die Annotationen [JsonInclude] und [JsonConstructor] es Entwicklerinnen und Entwicklern, die Serialisierung nichtöffentlicher Klassen-Member zu erzwingen. Für jedes nichtöffentliche Mitglied, das mit [JsonInclude] annotiert ist, muss im Konstruktor, der mit [JsonConstructor] annotiert ist, ein Parameter vorhanden sein, um den Wert während der Deserialisierung setzen zu können. Ein aussagekräftiges Beispiel dazu zeigt Listing 10.

 

Listing 10: Serialisierung und Deserialisierung mit [JsonInclude] und [JsonConstructor]

public class Person
{
  [JsonConstructor] // ohne dies: 'Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported.
  internal Person(int id, string name, string website)
  {
    ID = id;
    Name = name;
    Website = website;
  }

  [JsonInclude] // ohne dies: 'Each parameter in the deserialization constructor on type 'FCL_JSON+Person' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.'

  internal int ID { get; }

  public string Name { get; set; }

  [JsonInclude] // ohne dies: 'Each parameter in the deserialization constructor on type 'FCL_JSON+Person' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.'
  private string Website { get; set; }

  public override string ToString()
  {
    return $"{this.ID}: {this.Name} ({this.Website})";
  }
}

...
// Serialisierung
var p1 = new Person(42, "Dr. Holger Schwichtenberg", "www.dotnet-doktor.de");
string json4 = JsonSerializer.Serialize(p1); // {"X":42}
Console.WriteLine(json4);

// Deserialisierung
var p2 = JsonSerializer.Deserialize<Person>(json4);
Console.WriteLine(p2);

Die bereits vorhandene Klasse JsonNode hat neue Methoden wie DeepClone() und DeepEquals() erhalten. Außerdem wird bei JsonArray nun IEnumerable angeboten, was Aufzählung mit foreach und Language Integrated Query (LINQ) ermöglicht:

 

JsonArray jsonArray = new JsonArray(40, 42, 43, 42);
IEnumerable<int> values = jsonArray.GetValues<int>().Where(i => i == 42);
foreach (var v in values)
{
  Console.WriteLine(v);
}

 

Zu den Neuerungen in System.Text.Json in Version 8.0 gehört auch, dass die auf zu serialisierende Klassen anwendbare Annotation [JsonSourceGenerationOptions] nun alle Optionen bietet, die auch die Klasse JsonSerializerOptions beim imperativen Programmieren mit der Klasse System.Text.Json. JsonSerializer erlaubt. Für System.Text.Json in Verbindung Native AOT gibt es eine neue Annotation [JsonConverter]:

 

[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
public enum MyEnum { Value1, Value2, Value3 }

 

Source Generator für COM

In .NET 7.0 Preview 6 präsentierte Microsoft den Source Generator für native API-Aufrufe über die [LibraryImport]-Annotation [17] auf allen Betriebssystemplattformen. In .NET 8.0 Preview 6 wurde nun eine vergleichbare Option für die Nutzung des Component Object Models (COM), das ausschließlich unter Windows verfügbar ist, eingeführt. Um die COM-Schnittstelle zu nutzen, muss der entsprechende Wrapper die Annotation [GeneratedComInterface] tragen. Klassen, die diese Schnittstellen implementieren, werden mit [GeneratedComClass] annotiert. Zusätzlich zu der bereits in .NET 7.0 eingeführten [LibraryImport]-Annotation können Entwickler nun auch COM-Schnittstellen als Parameter- und Rückgabetypen verwenden. Diese Annotationen ermöglichen es dem C#-Compiler, den normalerweise zur Laufzeit generierten COM-Zugriffscode bereits zur Entwicklungszeit zu erzeugen. Der generierte Code findet sich im Projekt unter /Dependencies/Analyzers/Microsoft.Interop.ComInterfaceGenerator.

Für bestehende Schnittstellen, die [ComImport]-Annotationen aufweisen, schlägt Visual Studio die Konvertierung in [GeneratedComInterface] vor. Analog dazu wird für Klassen, die diese Schnittstellen implementieren, von Visual Studio vorgeschlagen, sie mit [GeneratedComClass] zu annotieren.

Wie schon in der Vergangenheit vorgekommen, hat Microsoft auch in diesem Ankündigungsblogeintrag [18] Fehler eingebaut. Bei allen im Zusammenhang mit COM behandelten Beispielen fehlt das Schlüsselwort partial. Microsoft schreibt zum Beispiel:

 

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
interface IComInterface
{
  void DoWork();
  void RegisterCallbacks(ICallbacks callbacks);
}

 

Dieser Code führt aber in Visual Studio 2022 (sowohl in der mit Preview 6 erschienenen Version 17.7 Preview 3.0 als auch in der zum Redaktionsschluss aktuellen Version 17.8 Preview 1) zu diesem Compilerfehler: „The interface ‘IComInterface’ or one of its containing types is missing the ‘partial’ keyword. Code will not be generated for ‘IComInterface’.“ Korrekt ist:

 

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
  void DoWork();
  void RegisterCallbacks(ICallbacks callbacks);
}

 

Microsoft hat zumindest einige der Begrenzungen des Source Generators für COM dokumentiert [19]. Das schließt ein, dass der Generator nicht für Schnittstellen funktioniert, die auf IDispatch und IInspectable basieren. Des Weiteren werden weder COM-Properties noch COM-Events unterstützt. Es ist auch wichtig zu beachten, dass Entwickler:innen nicht in der Lage sind, eine COM-Klasse mit dem Schlüsselwort new zu aktivieren; das ist lediglich durch den Aufruf von CoCreateInstance() möglich. Diese Beschränkungen sollen auch in der finalen Version von .NET 8.0 bestehen bleiben, die am 14. November 2023 veröffentlicht werden soll. Eventuelle Verbesserungen sind möglicherweise erst in einer späteren Hauptversion zu erwarten.

 

Neues Steuerelement OpenFolderDialog für WPF

Viele Jahre lang gab es keine neuen Steuerelemente für die Windows Presentation Foundation (WPF). In .NET 8.0 Preview 7 liefert Microsoft nun einen neuen Dialog für das Auswählen von Ordnern im Dateisystem (Listing 8). Es öffnet sich der Standarddialog des Windows-Betriebssystems. Realisiert wurde die Klasse Microsoft.Win32.OpenFolderDialog aber nicht von Microsoft selbst, sondern dem Communitymitglied Jan Kučera [20].

 

Listing 8: Einsatzbeispiel für das neue WPF-Steuerelement OpenFolderDialog

OpenFolderDialog openFolderDialog = new OpenFolderDialog()
{
Title = "Select folder to open ...",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
};

string folderName = "";
if (openFolderDialog.ShowDialog() == true)
{
folderName = openFolderDialog.FolderName;
}

 

Tastaturshortcuts für .NET-MAUI-Menüs

Wie angekündigt konzentriert sich das Entwicklungsteam von .NET MAUI in .NET 8.0 auf Fehlerbehebungen, siehe die Blogeinträge zu Preview 5 [21], Preview 6 [22] und Preview 7 [23].

Ein wesentliches neues Feature in diesen drei Previews sind Tastaturshortcuts, die Entwicklerinnen und Entwickler nun per SetAccelerator() einem Menüeintrag zuweisen können:

 

<MenuFlyoutItem x:Name="AddProductMenu"
Text="Add Product"
Command="{Binding AddProductCommand}"
/>
...
MenuItem.SetAccelerator(AddProductMenu, Accelerator.FromString("Ctrl+A"));

 

Im Hauptblogeintrag zu .NET 8.0 Preview 7 [24] ist noch eine Verbesserung für internationalisierte Mobile-Anwendungen auf Apple-Betriebssystemen (iOS, tvOS und MacCatalyst) erwähnt: Hier kann die Größe der länderspezifischen Daten mit einer neuen Einstellung um 34 Prozent reduziert werden:

 

<HybridGlobalization>true</HybridGlobalization>

 

Einige APIs ändern aber durch HybridGlobalization ihr Verhalten [25]. HybridGlobalization ist auch für .NET-Anwendungen möglich, die auf WebAssembly laufen [26]. Die Einstellung lädt dann die Datei icudt_hybrid.dat, die 46 Prozent kleiner sein soll als die bisherige icudt.dat.

Außerdem gibt es nun eine .NET-MAUI-Erweiterung für Visual Studio Code [27].

 

Funkstille bei Entity Framework Core

Beim Entwicklungsteam von Entity Framework Core ist es derzeit recht still. Für die Previews 5, 6 und 7 gab es zwar jeweils eine neue Version auf NuGet [28], aber nicht den sonst zu allen Vorschauversionen üblichen eigenständigen Blogeintrag des Entity-Framework-Core-Entwicklungsteams. Auch das GitHub Issue [29], in dem Entity-Framework-Core-Engineering-Manager Arthur Vickers [30] bisher im Abstand von zwei Wochen über den Fortschritt berichtete, lieferte zuletzt nur am 22. Juni und 6. Juli einen Eintrag mit einigen kleineren Verbesserungen.

Laut einer Tabelle in oben genanntem Issue hat das Entwicklungsteam die meisten der kleineren Neuerungen in Entity Framework Core in den ersten Preview-Versionen bereits erledigt und arbeitet nun an den größeren Projekten. Dazu gehören:

 

  • Tree Shaking/Trimming sowie Ahead-of-Time-Kompilation für ADO.NET und Entity Framework Core
  • der neue, schnellere Microsoft-SQL-Server-Datenbanktreiber für ADO.NET und Entity Framework Core mit dem Codenamen „Woodstar“
  • das Mapping von Value Objects für Domain-Driven Design
  • verbesserte Werkzeuge für Database-First-Entwicklung (alias Reverse Engineering) in Visual Studio

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Fazit

.NET 8.0 bietet in den Previews 6, 7 und 8 spannende Neuerungen. Es wird nun deutlich, dass Microsoft Blazor United tatsächlich umsetzt. Bis zum Erscheinungstermin am 14. November 2023 im Rahmen der .NET Conf 2023 sind aber noch einige Abrundungen notwendig. Das Entity-Framework-Core-Entwicklungsteam hat noch einiges zu liefern.

 

Vermischte weitere Neuerungen

Neben dem im Haupttext besprochenen Neuerungen sollen weitere Verbesserungen in den .NET 8.0 Previews 5, 6 und 7 kurz erwähnt werden:

 

  • Im .NET 8.0 SDK führt beim Publishing einer .NET-8.0-Anwendung mit dotnet publish die Angabe eines Runtime Identifiers (mit –runtime oder kurz -r) nicht mehr automatisch dazu, dass eine Self-contained-App (SCA) entsteht. Bisher musste man, wenn man das nicht wollte, –no-self-contained Das gehört zu den inzwischen zahlreichen Breaking Changes in .NET 8.0, die Microsoft unter [31] dokumentiert.
  • In .NET 8.0 Preview 5 gab es eine Verbesserung des Metrics API: Per Dependency Injection kann man nun ein Objekt mit der Schnittstelle IMeterFactory beziehen, wenn man zuvor AddMetrics() aufgerufen hat.
  • Die Klasse IO.Compression.ZipFile, die es seit dem klassischen .NET Framework 4.5 und im modernen .NET seit Version .NET Core 1.0 gibt, erhält zwei neue statische Methoden CreateFromDirectory() und ExtractToDirectory(). Diese ermöglichen das direkte Erstellen eines ZIP-Archivs aus einem Dateisystemordner bzw. das Entpacken in einen Zielordner.
  • Bei ASP.NET Core gibt es in der Klasse WebApplication eine neue Methode CreateEmptyBuilder(), die einen WebApplicationBuilder ohne vordefiniertes Verhalten erzeugt. Der Aufrufer muss also alle Middleware und Dienste selbst hinzufügen. Dafür ist das Anwendungs-Bundle kleiner.
  • Bei ASP.NET Core WebAPI gibt es seit Preview 5 generische Annotationen für Fälle, in denen man bisher einen Parameter vom Typ Type übergeben musste: [ProducesResponseType<T>], [Produces<T>], [MiddlewareFilter<T>], [ModelBinder<T>], [ModelMetadataType<T>], [ServiceFilter<T>] und [TypeFilter<T>].
  • Die Klasse HttpClient bietet nun auch Unterstützung für HTTPS-basierte Proxies [32].
  • Wenn sys als Webserver unter Windows verwendet wird, haben Entwickler und Entwicklerinnen, die Option, Response Buffering im Windows-Kernel zu aktivieren. Microsoft behauptet unter [33]: „In betroffenen Szenarien kann dies die Reaktionszeiten drastisch von Minuten (oder völligem Ausfall) auf Sekunden verkürzen.“
  • Redis konnte bisher schon für Distributed Caching in ASP.NET Core verwendet werden [34]. Microsoft ermöglicht in .NET 8.0 nun auch die Nutzung von Redis für das in ASP.NET Core 7.0 eingeführte Webserverausgabencaching mit dem NuGet-Paket Extensions.Caching.StackExchangeRedis und dem Startcodeaufruf services.AddStackExchangeRedisOutputCache().
  • Bei den Hashing-Klassen in Security wird nun auch SHA-3 angeboten. Die neuen Klassen heißen SDA3_256, SHA3_386 und SHA3_512 in Ergänzung zu den bisherigen Klassen SDA_256, SHA_386 und SHA_512.
  • Microsoft hat in .NET 8.0 Preview 5 die Debugger-Ansicht einiger ASP.NET Core-Klassen verbessert, sodass diese nun statt dem Klassennamen einige der wesentlichen Daten anzeigen (11).

 

Abb. 11: Verbesserte Debugger-Ansicht in ASP.NET-Core-Projekten (Bildquelle: Microsoft)

Dr. Holger Schwichtenberg (20-maliger MVP) – alias „Der DOTNET-DOKTOR“ – gehört zu den bekanntesten .NET- und Webexperten in Deutschland. Er ist Chief Technology Expert bei der Softwaremanufaktur MAXIMAGO. Mit dem 43-köpfigen Expertenteam bei www.IT-Visions.de bietet er zudem Beratung und Schulungen zu über 950 Entwicklerthemen an. Seit 1998 ist er ununterbrochen Sprecher auf sämtlichen BASTA!-Konferenzen und Autor von mehr als 90 Fachbüchern sowie über 1 500 Fachartikeln.
E-Mail: [email protected]
Twitter: @dotnetdoktor
Web: www.dotnet-doktor.de

Links & Literatur

[1] https://www.youtube.com/watch?v=48G_CEGXZZM
[2] https://learn.microsoft.com/en-us/aspnet/core/mvc/views/partial
[3] https://github.com/mkArtakMSFT/BlazorWasmClientInteractivity
[4] https://github.com/danroth27/Net8BlazorAuto
[5] https://github.com/dotnet/aspnetcore/issues/49756
[6] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-6
[7] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-7
[8] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-7/#antiforgery-integration-for-minimal-apis
[9] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-7/#antiforgery-integration
[10] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-5/#improved-packaging-of-webcil-files
[11] https://docs.duendesoftware.com/identityserver/v5/quickstarts/5_aspnetid
[12] https://github.com/davidfowl/IdentityEndpointsSample
[13] https://github.com/dotnet/aspnetcore/issues/46691
[14] https://devblogs.microsoft.com/dotnet/new-csharp-12-preview-features
[15] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-5/#native-aot
[16] https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-6/#support-for-targeting-ios-platforms-with-nativeaot
[17] https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.libraryimportattribute
[18] https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-6
[19] https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-6/#limitations
[20] https://github.com/miloush
[21] https://devblogs.microsoft.com/dotnet/announcing-dotnet-maui-in-dotnet-8-preview-5
[22] https://devblogs.microsoft.com/dotnet/announcing-dotnet-maui-in-dotnet-8-preview-6
[23] https://devblogs.microsoft.com/dotnet/announcing-dotnet-maui-in-dotnet-8-preview-7
[24] https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-7/#hybridglobalization-mode-on-ios-tvos-maccatalyst-platforms
[25] https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-hybrid-mode.md
[26] https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-6/#hybridglobalization-mode-on-wasm
[27] https://devblogs.microsoft.com/visualstudio/announcing-the-dotnet-maui-extension-for-visual-studio-code
[28] https://www.nuget.org/packages/Microsoft.EntityFrameworkCore
[29] https://github.com/dotnet/efcore/issues/29989
[30] https://github.com/ajcvickers
[31] https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0
[32] https://github.com/dotnet/runtime/issues/31113
[33] https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-6/#http-sys-kernel-response-buffering
[34] https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed

The post Blazor als Insel und im Automodus appeared first on BASTA!.

]]>
SQL Server 2022 https://basta.net/blog/sgl-server-2022-neuerungen/ Mon, 26 Jun 2023 08:11:18 +0000 https://basta.net/?p=91768 Seit nicht ganz einem Jahr ist SQL Server 2022 die neueste Version von Microsofts Datenbankflaggschiff. Zeit also, die Neuerungen im Bereich T-SQL mal ein wenig unter die Lupe zu nehmen und zu schauen, welche von ihnen tatsächlich Verbesserungen im Projekt mit sich bringen und welche eher unbedeutend sind. Im Vergleich zu den direkten Vorgängern sind die Neuerungen auf jeden Fall umfangreicher.

The post SQL Server 2022 appeared first on BASTA!.

]]>
Neben den reinen Neuerungen in komprimierter Form erwarten interessierte Leser und Leserinnen also auch ein – sicherlich persönlich geprägtes – Minifazit. Sollte dies von den eigenen Anforderungen oder der eigenen Meinung abweichen, so mögen Sie es dem Autor verzeihen. Kenntnisse älterer SQL-Server-Versionen sind hilfreich.

Unterscheidbar oder nicht? Filtern mit NULL

Zu prüfen, ob zwei Werte gleich sind oder nicht, klingt im ersten Moment trivial. Und zwar genau so lange, bis einer (oder gar beide) NULL sein können. Ab diesem Moment muss mit einer Kombination von gleich und ungleich sowie mit dem IS-Operator gearbeitet werden. (Zur Erinnerung: NULL = NULL OR NULL <> NULL ist immer falsch, auch wenn es dem einen oder anderen erst einmal widersinnig erscheint). Aus diesem Grund bietet SQL Server 2022 nun die Syntax A IS DISTINCT FROM B beziehungsweise A IS NOT DISTINCT FROM B. Eine Abfrage kann so aussehen:

 

SELECT * FROM <Tabelle/ Sicht> WHERE A IS DISTINCT FROM B;

SELECT * FROM <Tabelle/ Sicht> WHERE A IS NOT DISTINCT FROM B;

 

Abbildung 1 zeigt, wie der Server dies umsetzt – etwas umfangreich, aber korrekt und nötig. Sicher ist, dass es sich bei A/B um Ausdrücke oder Spalten handeln kann.

 

Abb. 1: Decoding der neuen IS (NOT) DISTINCT FROM-Syntax

 

Minifazit: Wer bereit ist, statt A=B einfach A IS NOT DISTINCT B zu schreiben, bekommt ein Ergebnis, das in jedem Fall korrekt ist. Mehr ist nicht nötig. Notlösungen mit der ISNULL()-Funktion sollten damit endgültig der Vergangenheit angehören.

RTRIM(), LTRIM()? Besser TRIM()!

Das Entfernen von Zeichen von Anfang und/oder Ende einer Zeichenkette ist eine Grundfunktionalität, für die es eigentlich für jedes Framework eine Lösung gibt. Nun auch für T-SQL mit der Angabe, welche Zeichen entfernt werden sollen. Whitespaces sind also nicht mehr Pflicht. Außerdem kann nun für die TRIM()-Funktion bestimmt werden, ob sie den Anfang oder das Ende einer Zeichenkette berücksichtigen soll oder beides. Letzteres wird nicht über einen Parameter, sondern den Zusatz LEADING, TAILING oder BOTH gesteuert. Listing 1 zeigt, wie das genau aussieht.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Listing 1
DECLARE @caption VARCHAR(100) = '-== SQL Server 2022 ==-';


SELECT LTRIM(@caption, '-= ') AS [LTRIM],

       RTRIM(@caption, '-= ') AS [RTRIM];


SELECT TRIM(LEADING '-= ' FROM @caption) AS [LEADING],

       TRIM(TRAILING '-= ' FROM @caption) AS [TRAILING],

       TRIM(BOTH '-= ' FROM @caption) AS [BOTH];

 

 

Minifazit: Praktisch, dass genau bestimmt werden kann, welche Zeichen (‘-= ‘ in Listing 1) entfernt werden sollen. Nur an die neue Syntax der TRIM()-Funktion muss man sich sicherlich erst gewöhnen.

Horizontales Maxi-/Minimum

Um den größten beziehungsweise kleinsten Wert einer Reihe von Werten und Ausdrücken zu ermitteln, waren bisweilen „interessante“ Lösungen notwendig. Die dabei naheliegenden Aggregate MIN() und MAX() funktionieren nicht, da diese nur einen Parameter akzeptieren und vertikal (aus Spalten) arbeiten. GREATEST() und LEAST() kann man nun eine Menge an kompatiblen Werten übergeben und erhält den größten oder den kleinsten von diesen. Folgender Abschnitt zeigt ein paar Aufrufe und das daraus folgende Ergebnis:

 

SELECT GREATEST('6.62', 3.1415, N'7') AS 'GREATEST', -- 7.0000

       LEAST( '6.62', 3.1415, N'7') AS 'LEAST'; -- 3.1415




SELECT GREATEST('A', 'B', 'C') AS 'GREATEST', -- C

       LEAST('A', 'B', 'C') AS 'LEAST'; -- A

 

Minifazit: Klein, aber sehr hilfreich. Wer hatte so eine Anforderung noch nicht?

Zahlenreihen erzeugen

Ebenfalls öfter mal benötigt und nun leicht zu erzeugen: eine Zahlenreihe von einem Start- bis zu einem Endwert; auf Wunsch auch mit Schrittweite. Damit ist der konkrete Datentyp unerheblich, so lange er nur numerisch ist oder als solcher interpretiert wird. In der Praxis bedeutet dies Zahlen mit oder ohne Komma. Beides akzeptiert die neue GENERATE_SERIES()-Funktion. Neben den obligatorischen Werten für Start und Ende ist der Wert für die Schrittweite optional und SQL Server verwendet ohne Angabe 1, bzw. -1. Listing 2 zeigt ein paar Aufrufe mit der Funktion.

 

Listing 2

-- Von 1 bis <=50 in 5er Schritten => 1..6..11..41..46

SELECT value FROM GENERATE_SERIES(/* Start */ 1, /*STOP*/ 50, /*STEP*/ 5);




-- Auch andere (numerische) Datentypen sind erlaubt

SELECT value FROM GENERATE_SERIES(/* Start */ 1.0, /*STOP*/ 10.0, /*STEP*/ .5);




-- Alle Kalenderwochen 2022

SELECT CAST(DATEADD(WEEK, value - 1, '2022-01-03') AS DATE), value FROM GENERATE_SERIES(1, 52);

 

 

Soll eine Serie mit anderen Datentypen erzeugt werden, so ist das auf Basis der Zahlenreihe natürlich auch kein Problem, wie die letzte Abfrage aus Listing 2 zeigt. Die Ausgabe ist in Abbildung 2 zu sehen.

 

Abb. 2: Kalenderwochen mit der GENERATE_SERIES()-Funktion

 

Minifazit: Ziemlich hilfreiche kleine Funktion für vielfältige Einsätze.

DATETRUNC()

Die neue DATETRUNC()-Funktion schneidet alle Details eines Datums/einer Uhrzeit ab der übergebenen Einheit ab. So liefert DATETRUNC(YEAR, ‘2022-10-25 19:45:32’) schlicht ‘2022-01-01 00:00:00.0000000’. Für Month wäre es ‘2022-10-01 00:00:00.0000000’. Listing 3 zeigt einige Varianten mit deren jeweiligen Ausgaben.

 

Listing 3

DECLARE @d DATETIME2 = GETDATE();

SELECT 'Year', DATETRUNC(YEAR, @d); -- 2022-01-01 00:00:00.0000000

SELECT 'Quarter', DATETRUNC(QUARTER, @d); --2022-10-01 00:00:00.0000000

SELECT 'Month', DATETRUNC(MONTH, @d); -- 2022-12-01 00:00:00.0000000

SELECT 'Week', DATETRUNC(WEEK, @d); -- 2022-12-11 00:00:00.0000000

SELECT 'Iso_week', DATETRUNC(ISO_WEEK, @d); -- 2022-12-12 00:00:00.0000000

SELECT 'DayOfYear', DATETRUNC(DAYOFYEAR, @d); -- 2022-12-17 00:00:00.0000000

SELECT 'Day', DATETRUNC(DAY, @d); -- 2022-12-17 00:00:00.0000000

SELECT 'Hour', DATETRUNC(HOUR, @d); -- 2022-12-17 19:00:00.0000000

SELECT 'Minute', DATETRUNC(MINUTE, @d); -- 2022-12-17 19:45:00.0000000

SELECT 'Second', DATETRUNC(SECOND, @d); -- 2022-12-17 19:45:17.0000000

SELECT 'Millisecond', DATETRUNC(MILLISECOND, @d); -- 2022-12-17 19:45:17.5800000

SELECT 'Microsecond', DATETRUNC(MICROSECOND, @d); -- 2022-12-17 19:45:17.5800000

 

 

Minifazit: Diese Funktion sorgt für einfacheren und lesbareren Code, wenn ein vollständiges Datum oder eine vollständige Uhrzeit vorliegt (z. B. von GETDATE() oder GETUTCDATE()), aber nur ein Teil davon tatsächlich benötigt wird.

SELECT … WINDOW-Klausel

Wer viel mit Window-Funktionen oder Aggregaten bei seinen Abfragen arbeitet, wird diese kleine Verbesserung zu schätzen wissen, da sich so repetitiver Code vermeiden lässt. Mit der WINDOW-Klausel lässt sich PARTITION BY und ORDER BY pro Abfrage einmal definieren, mit einem Namen versehen und dann mehrfach verwenden. Statt also eine Abfrage wie in Listing 4 ist nun eine wie in Listing 5 möglich.

 

Listing 4

SELECT SalesOrderID, ProductID, OrderQty

  ,SUM(OrderQty) OVER (PARTITION BY SalesOrderID) AS Total

  ,AVG(OrderQty) OVER (PARTITION BY SalesOrderID) AS "Avg"

  ,COUNT(OrderQty) OVER (PARTITION BY SalesOrderID) AS "Count"

  ,MIN(OrderQty) OVER (PARTITION BY SalesOrderID) AS "Min"

  ,MAX(OrderQty) OVER (PARTITION BY SalesOrderID) AS "Max"

  FROM Sales.SalesOrderDetail

WHERE SalesOrderID IN(43659,43664);


 

Listing 5

SELECT SalesOrderID, ProductID, OrderQty

  ,SUM(OrderQty) OVER win AS Total

  ,AVG(OrderQty) OVER win AS "Avg"

  ,COUNT(OrderQty) OVER win AS "Count"

  ,MIN(OrderQty) OVER win AS "Min"

  ,MAX(OrderQty) OVER win AS "Max"

FROM Sales.SalesOrderDetail

WHERE SalesOrderID IN(43659,43664)

WINDOW win AS (PARTITION BY SalesOrderID);

 

 

Die Abfrage in Listing 5 ist kompakter und Fehler bei Änderungen haben ebenfalls weniger Chancen.

Minifazit: Praktisch, wenn man viel mit umfangreichen Window-Funktionen oder Aggregaten arbeitet. Sonst eher hinderlich; dafür handelt es sich aber auch nur um eine Option, die alte Syntax existiert natürlich nach wie vor.

FIRST_VALUE() und LAST_VALUE()

Die Funktionen FIRST()_VALUE und LAST_VALUE() sind bei weitem nicht neu, sondern schon seit SQL Server 2012 im Portfolio. Nun sind sie um eine Syntax erweitert worden, die steuert, ob NULL berücksichtigt wird (RESPECT NULLS) oder nicht (IGNORE NULLS). Listing 6 zeigt beide nun möglichen Varianten.

 

Listing 6

SELECT Name, ListPrice,

       FIRST_VALUE(Name) RESPECT NULLS OVER (ORDER BY ListPrice ASC) AS LeastExpensive

FROM Production.Product

WHERE ProductSubcategoryID = 37;

SELECT Name, ListPrice,

       FIRST_VALUE(Name) IGNORE NULLS OVER (ORDER BY ListPrice ASC) AS LeastExpensive

FROM Production.Product

WHERE ProductSubcategoryID = 37;

 

 

Minifazit: Die neue Syntax erspart eine zusätzliche Unterabfrage und macht damit die Abfrage an sich einfacher, lesbarer und performanter. Wer die beiden Funktionen bis dato noch nicht verwenden konnte, wird allerdings auch nicht bekehrt werden.

 

 

 

STRING_SPLIT() mit Ordinal (Index)

Auch die STRING_SPLIT()-Funktion ist nicht neu, wurde aber mit SQL Server 2022 um einen optionalen Parameter erweitert, der neben den aufgeteilten Elementen der Zeichenketten auch den entsprechenden Indexwert (beginnend mit 1) in einer zusätzlichen Spalte liefert –  zumindest wenn eine 1 als Wert übergeben wird. Die folgende Abfrage liefert das Ergebnis, das in Abbildung 3 gezeigt wird:

 

SELECT * FROM STRING_SPLIT('Lorem ipsum dolor sit amet.', ' ', 1);

 

Abb. 3: STRING_SPLIT()-Funktion mit Ordinal (Index)

 

Minifazit: Wahrlich eine Detailerweiterung mit wenig Einsatzpotenzial, oder?

Genäherte Aggregate

An sich sind die grundlegenden Funktionen PERCENTILE_CONT() und PERCENTILE_DISC() ebenfalls nicht neu. APPROX_PERCENTILE_CONT() und APPROX_PERCENTILE_DISC() sind nun Varianten, die auch bei größeren Datenmengen sehr performant sind, dafür aber nur genähert und so mit einer Abweichung versehen sind. Listing 7 zeigt diese beiden Funktionen.

 

Listing 7

-- Kontinuierliche Werte

SELECT DeptId,

       APPROX_PERCENTILE_CONT(0.10) WITHIN GROUP(ORDER BY Salary) AS 'P10',

       APPROX_PERCENTILE_CONT(0.90) WITHIN GROUP(ORDER BY Salary) AS 'P90'

FROM #Employee

GROUP BY DeptId;




-- Diskrete Werte

SELECT DISTINCT DeptId,

       PERCENTILE_DISC(0.10) WITHIN GROUP (ORDER BY Salary) OVER (PARTITION BY DeptId) AS 'P10',

       PERCENTILE_DISC(0.90) WITHIN GROUP (ORDER BY Salary) OVER (PARTITION BY DeptId) AS 'P90' 

FROM #Employee;

 

 

Minifazit: Ebenfalls eine Detailerweiterung für die wenigen, die ausgiebig mit PERCENTILE_CONT() und PERCENTILE_DISC() arbeiten – für den Rest eher uninteressant.

Zeitliche Abschnitte mit der DATE_BUCKET()-Funktion

Für das Erzeugen von zeitlichen Abschnitten steht ab SQL Server 2022 die DATE_BUCKET()-Funktion bereit. Sie liefert den Anfang eines Abschnittes (Woche, Monat, Jahr etc.) basierend auf einem Datum. Und dabei muss es sich nicht um den ersten Abschnitt handeln, er kann auch per Index bestimmt werden. Listing 8 zeigt den Aufruf.

 

Listing 8

DECLARE @date DATETIME2 = '2022-02-24 00:00:00';

SELECT 'WEEK', DATE_BUCKET(WEEK, 1, @date); -- 2022-02-21 00:00:00.0000000

SELECT 'MONTH', DATE_BUCKET(MONTH, 1, @date); -- 2022-02-21 00:00:00.0000000

SELECT 'YEAR', DATE_BUCKET(YEAR, 1, @date); -- 2022-01-01 00:00:00.0000000




DECLARE @date DATETIME2 = '2022-02-24 00:00:00';

DECLARE @origin1 DATETIME2 = '2021-01-01 00:00:00';

DECLARE @origin2 DATETIME2 = '2021-02-22 00:00:00';

SELECT 'WEEK', DATE_BUCKET(WEEK, 1, @date, @origin1); -- 2022-02-18 00:00:00.0000000

SELECT 'WEEK', DATE_BUCKET(WEEK, 1, @date, @origin2); -- 2022-02-21 00:00:00.0000000

Minifazit: Eine sehr spezielle Funktion, die für viele keine Rolle spielen wird.

Bit-Funktionen

Gleich eine Handvoll neuer Funktionen für die Arbeit mit Bit-Werten bietet SQL Server 2022, auch wenn es wenige Gelegenheiten gibt, im T-SQL-Umfeld damit zu arbeiten – die UPDATE()-Funktion im Zuge von DML-Triggern ist eine der wenigen davon. Tabelle 1 zeigt die einzelnen Funktionen zusammen mit einer kleinen Erläuterung. Listing 9 zeigt die Verwendung dieser Funktionen in einem kurzen Beispiel.

 

Funktion Erläuterung
RIGHT_SHIFT() Verschiebt den numerischen Wert binär nach rechts
LEFT_SHIFT() Verschiebt den numerischen Wert binär nach links
BIT_COUNT () Liefert die Anzahl der (aus 1) gesetzten Bits
GET_BIT() Liefert den Status (0 oder 1) eines bestimmten Bits
SET_BUT() Setzt den Status (0 oder 1) eines bestimmten Bits

Tabelle 1: Neue Bit-Funktionen

 

Listing 9

-- RIGHT_SHIFT()/ LEFT_SHIFT()

SELECT @number, RIGHT_SHIFT(@number, 1) '>> 1', RIGHT_SHIFT(@number, 2) '>> 2', RIGHT_SHIFT(@number, 3) '>> 3'; -- 10011010010   1001101001  100110100  10011010

SELECT @number, LEFT_SHIFT(@number, 1) '<< 1', LEFT_SHIFT(@number, 2) '<< 2', LEFT_SHIFT(@number, 3) '<< 3'; -- 10011010010  100110100100  1001101001000  10011010010000




-- BIT_COUNT()

SELECT @number, BIT_COUNT(@number) 'Number of 1s'; -- 10011010010  5




-- SET_BIT()/ GET_BIT()

SELECT GET_BIT(@number, 0) 'Pos 0', GET_BIT(@number, 1) 'Pos 1';

 

 

Minifazit: Wie bereits erwähnt, gibt es nur wenige Stellen, an denen in T-SQL mit Bit-Operationen sinnvoll gearbeitet werden kann. Aber wenn man es mit so einer Stelle zu tun hat, machen die Neuerungen vieles einfacher.

Arbeiten mit JSON

Und vielleicht das Beste zum Schluss: Arbeiten mit JSON ist seit SQL Server 2016 möglich und wird nun mit SQL Server 2022 essenziell erweitert. Um genau zu sein, gibt es drei neue Funktionen und eine Erweiterung einer bestehenden Funktion. Tabelle 2 zeigt diese Funktionen mit einer kleinen Erläuterung für ihren jeweiligen Zweck. Einige Funktionen arbeiten dabei mit unterschiedlichen JSON-Typen, die in Tabelle 3 aufgelistet sind. Listing 10 zeigt den Einsatz der JSON-Funktion im Beispiel.

 

Funktion Zweck
JSON_ARRAY() Erzeugt ein JSON-Array (Neu)
JSON_OBJECT() Erzeugt ein einfaches JSON-Objekt (Neu)
ISJSON() Erweitert um die neue JSON-Typangabe
JSON_PATH_EXIST() Prüft, ob ein JSON-Objekt den angegebenen Pfad besitzt (Neu)

Tabelle 2: Neue und erweiterte JSON-Funktionen

 

JSON-Type Beschreibung
VALUE Testet auf einen Wert (object, array, number, string, true, false, null
ARRAY Testet auf ein JSON-Array
OBJECT Testet auf ein JSON-Objekt
SCALAR Testet auf einen gültigen Skalarwert – Zahl, String etc.

Tabelle 3: Unterstützte JSON-Typen

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Listing 10

DECLARE @jsonInfo NVARCHAR(MAX)=N'{"info":{"address":[{"town":"Nidderau-Erbstadt"},{"town":"Rom"}]}}';




-- JSON_ARRAY

SELECT JSON_ARRAY();

SELECT JSON_ARRAY('a', 1, 'b', 2);

SELECT JSON_ARRAY('name', name, 'id', database_id) FROM sys.databases;

GO




-- JSON_OBJECT

SELECT JSON_OBJECT('name':'value', 'type':null, 'abc': 3);

SELECT JSON_OBJECT('name':'value', 'type':null, 'abc': 3 NULL ON NULL /* Standard */ );

SELECT JSON_OBJECT('name':'value', 'type':null, 'abc': 3 ABSENT ON NULL);

GO




-- ISJSON

DECLARE @json VARCHAR(MAX) = '["a",1,"b",2]';

SELECT ISJSON(@json, ARRAY); -- 1

SELECT ISJSON(@json, SCALAR); -- 0

-- ARRAY, SCALAR, VALUE, OBJECT

GO




-- JSON_PATH_EXISTS

DECLARE @jsonInfo NVARCHAR(MAX)=N'{"info":{"address":[{"town":"Nidderau-Erbstadt"},{"town":"Rom"}]}}';

SELECT JSON_PATH_EXISTS(@jsonInfo,'$.info.address'); -- 1

SELECT JSON_PATH_EXISTS(@jsonInfo,'$.info.email'); -- 0

GO


 

Minifazit: Sinnvolle und nützliche Funktionen und Erweiterungen, für die man bei der Arbeit mit JSON schnell einen Einsatz findet. Leider ist (immer noch) kein JSON-Datentyp vorhanden, um mit großen Datenmengen im JSON-Format performant arbeiten zu können.

Gesamtfazit

SQL Server 2022 bietet einiges Neue für T-SQL: IS [not ]DISTINCT FROM, RTRIM(), LTRIM() & TRIM(), GREATEST() & LEAST() und DATETRUNC() sind nützliche Verbesserungen, die schnell Verwendung finden. Andere Neuerungen sind recht speziell, finden aber bestimmt auch ihre Anwender:innen. Insgesamt sicher keine echte Revolution, aber eine Evolution ist es schon. Vielleicht schafft es Microsoft sogar noch, T-SQL sprachlich in die Zukunft zu führen.

The post SQL Server 2022 appeared first on BASTA!.

]]>
PWAs mit Blazor WebAssembly https://basta.net/blog/pwas-mit-blazor-webassembly/ Wed, 14 Jun 2023 10:03:17 +0000 https://basta.net/?p=91745 Progressive Web Apps (PWAs) sind ein webbasiertes Anwendungsmodell. Einmal geschrieben, können sie auf allen Plattformen ausgeführt werden, auf denen ein halbwegs aktueller Webbrowser läuft.

The post PWAs mit Blazor WebAssembly appeared first on BASTA!.

]]>
Das Leben einer PWA beginnt in einem regulären Browsertab, sei es in Chrome, Edge, Firefox oder Safari. Dort laufen die Anwendungen selbst dann, wenn die Anwender:innen gerade offline sind. Mit dem passenden Browser lassen sich PWAs auch auf dem Gerät installieren, um einen noch schnelleren Zugriff auf sie zu erhalten: Das funktioniert unter Android und iOS, macOS, Linux und Windows.

Da es sich bei PWAs um Websites handelt, können sie ausschließlich Schnittstellen und Gerätefunktionen aufrufen, die im Web bereitgestellt werden. Glücklicherweise ist das Web spätestens seit der Einführung von HTML5 immer mächtiger geworden. Auch über die vergangenen Jahre kamen immer mehr Schnittstellen ins Web, um die Leistungsfähigkeit webbasierter Anwendungen noch weiter zu erhöhen, zuletzt über die Initiative „Project Fugu“.

Blazor in vier Ausprägungen

Da PWAs auf Webtechnologien aufsetzen, können sämtliche Bibliotheken und Frameworks auf ihre Schnittstellen zugreifen: Angular, React, Vue.js ganz ohne Framework, aber eben auch Microsofts Webframework Blazor, das es in verschiedenen Ausprägungen gibt:

  1. Bei Blazor WebAssembly wird der Anwendungscode komplett clientseitig ausgeführt.

  2. Bei Blazor Server wird die App auf einem Server ausgeführt, der die HTML-Fragmente rendert und per Socket-Verbindung zum Client überträgt. Der Client übermittelt sämtliche Benutzerinteraktionen an den Server, was zu erneutem Rendern führt.

  3. Bei Blazor Hybrid wird Blazor innerhalb einer MAUI-, Windows-Forms- oder WPF-Anwendung ausgeführt, läuft also nicht im Browser (vergleichbar mit Electron). Im Gegenzug können sämtliche plattformspezifischen Schnittstellen aufgerufen werden.

  4. Für .NET 8 wurde zudem noch Blazor United angekündigt, das die Vorteile von Blazor WebAssembly, Blazor Server und Razor Pages vereinen soll: HTML-Fragmente können auf dem Server vorgerendert und danach interaktiv weiterbetrieben werden, wahlweise auf dem Client oder dem Server [1].

Tabelle 1 stellt die Ansätze Blazor WebAssembly, Blazor Server und Blazor Hybrid anhand häufig gewünschter Kriterien für Webanwendungen gegenüber.

Für Progressive Web Apps kommt nur das Modell Blazor WebAssembly in Frage, denn nur dieser Ansatz unterstützt die Ausführbarkeit der Anwendung direkt im Browser in Kombination mit der Unterstützung von Offlineverfügbarkeit. Auf Wunsch lässt sich die Anwendung dennoch für Mobil- und Desktopbetriebssysteme verpacken, um sie über die althergebrachten Deployment-Wege wie App Stores oder Installer zu vertreiben. In diesem Fall ist dann auch der Aufruf sämtlicher plattformspezifischer Schnittstellen möglich. Ein weiterer Vorteil ergibt sich durch die einfache Skalierbarkeit, da die Anwendung auf dem Rechner des Clients ausgeführt wird. Bei Blazor Server muss der Server sämtliche Sessions verwalten können. Von Nachteil ist jedoch, dass auch der Programmcode auf den Rechner des Anwenders übertragen wird und somit nicht geheim gehalten werden kann, wie es für sämtliche Anwendungen gilt, deren Code an Anwender:innen übertragen wird.

Blazor WebAssembly Blazor Server Blazor Hybrid
Ausführbarkeit in Browsern + +
Unterstützung für Offlineverfügbarkeit + +
Paketierbarkeit für Mobile und Desktop + +
Aufrufbarkeit beliebiger plattformspezifischer Schnittstellen – (nur wenn verpackt) + (nur auf dem Server) +
Einfache Skalierbarkeit + +
Code kann geheimgehalten werden +

Tabelle 1: Vergleich der Blazor-Ausprägungen

Da Blazor WebAssembly den einzigen relevanten Ansatz zur Entwicklung von Progressive Web Apps darstellt, wird sich der Artikel allein auf diese Ausprägung konzentrieren. Für das zum Zeitpunkt des Verfassens noch nicht veröffentlichte Blazor United trifft zu, was in Tabelle 1 in der Spalte „Blazor WebAssembly“ steht, sofern die vorgerenderten Inhalte zustandslos sind und die übrige Interaktion clientseitig durchgeführt werden kann, andernfalls gilt die Spalte „Blazor Server“.

Referenzarchitektur Single Page Application

Blazor WebAssembly implementiert das von anderen Frameworks wie Angular, React oder Vue.js bekannte Architekturmuster der Single Page Application (SPA): Hierbei werden die erforderlichen Dateien (HTML, JavaScript, CSS, WebAssembly-Module, DLLs und weitere Assets wie Bilder oder Videos) beim Start der Anwendung zum Client übertragen. Die Quelldateien können dabei lokal zwischengespeichert werden, was die Anwendung grundsätzlich offlinefähig macht. Dieser Schritt lässt sich damit vergleichen, eine EXE-Datei inklusive all ihrer Abhängigkeiten auf den Computer der Anwender:innen zu übertragen. Nach dem Laden finden alle Interaktionen und Sichtwechsel clientseitig, also auf dieser Single Page, statt. Eine Kontaktaufnahme zum Server ist nicht erforderlich, was die Anwendung somit sehr performant macht.

Zum Austausch von Daten können Webschnittstellen über HTTPS oder WebSockets angesprochen werden, wie Abbildung 1 zeigt. Darüber hinaus stehen clientseitige Speichermethoden und Datenbanken zur Verfügung, sodass Anwendungsdaten auch offline angezeigt und bearbeitet werden können, eine passende Synchronisationslogik vorausgesetzt.

Abb. 1: Single-Page-App-Architektur mit Blazor WebAssemblyAbb. 1: Single-Page-App-Architektur mit Blazor WebAssembly

Progressive Web Apps: Installierbarkeit und Offlinefähigkeit

Progressive Web Apps sind ein Überbegriff für eine bestimmte Art Webanwendung. Sie sollen folgende Eigenschaften besitzen [2]:

  • Responsive: Sie passen sich an die verfügbaren Bildschirmabmessungen an (vom Smartphone bis zum 80-Zoll-Fernseher, wenn sinnvoll).

  • Linkable: Sie erlauben das Verweisen auf Zielzustände innerhalb der Anwendung über den URL (z. B. eine konkrete Seite oder einen gezielten Datensatz).

  • Discoverable: Sie sind unterscheidbar von normalen Websites.

  • Installable: Sie können dem Home-Bildschirm bzw. der Programmliste des Betriebssystems hinzugefügt werden.

  • App-like: Sie sehen so aus und verhalten sich wie plattformspezifische Apps.

  • Connectivity independent: Sie sind auch mit schwacher oder ohne Internetverbindung lauffähig, zumindest im Rahmen der Möglichkeiten.

  • Fresh: Sie sind trotz Offlinekopie immer so aktuell wie möglich.

  • Safe: Sie übertragen Inhalte nur über eine gesicherte Verbindung (HTTPS).

  • Re-engageable: Sie fordern zur Wiederverwendung der Anwendung per Pushnachricht auf.

  • Progressive: Sie schließen die Verwendung älterer/leistungsschwächerer Browser nicht aus.

Die Kernspezifikationen des Anwendungsmodells sind das Web Application Manifest, das das Aussehen und Verhalten der auf dem Gerät installierten PWA definiert, und die Service Workers, die unter anderem für die Offlineverfügbarkeit der Anwendung zuständig sind. Beide Spezifikationen werden vom World Wide Web Consortium (W3C) verwaltet.

Beim Web App Manifest [3] handelt es sich um eine JSON-basierte Datei (Listing 1), die unter anderem den Anzeigenamen der Anwendung (Eigenschaften name und short_name), das App-Icon (Eigenschaft icons) sowie den gewünschten Anzeigemodus (Eigenschaft display) definiert. Das Web Application Manifest wird in der index.html-Datei der Anwendung referenziert.

Listing 1

{
  "short_name": "EM",
  "name": "entwickler magazin",
  "theme_color": "white",
  "icons": [{ "src": "icon.png", "sizes": "512x512" }],
  "start_url": "./",
  "display": "standalone"
}

Durch die Verfügbarkeit des Web App Manifest lässt sich die PWA nun auf den Home-Bildschirm des Mobilgerätes (iOS, Android) beziehungsweise in die Programmliste des Desktopgerätes (macOS, Linux, Windows mit Chrome oder Edge) installieren. Von dort aus gestartet, erscheint die Webanwendung aufgrund des gewählten Anzeigemodus standalone wie eine plattformspezifische App: auf Mobilgeräten vollflächig, auf dem Desktop in einem eigenen Fenster, in beiden Fällen komplett ohne Browserstatusleisten. Vor Chromium 111 war für die Chromium-basierten Browser auch das Vorhandensein eines Service Workers verpflichtend; diese Bedingung ist jedoch entfallen.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Die Service Workers [4] hingegen sind in JavaScript verfasste Skripte, die durch die Website registriert werden und danach die Rolle eines Controllers beziehungsweise Proxys übernehmen: Sie können auch ausgeführt werden, wenn die Website geschlossen ist, um Hintergrundaufgaben wie die Anzeige von Pushbenachrichtigungen oder die Synchronisierung von Daten durchzuführen.

Außerdem besitzt der Service Worker einen Cache, in dem er Antworten zu GET-Anfragen lokal zwischenspeichern kann. Darüber lässt sich die Offlineverfügbarkeit abbilden (Abb. 2): Nach Installation und Aktivierung erhält der Service Worker Zugriff auf sämtliche ausgehenden Netzwerkanfragen – sowohl auf die eigenen Quelldateien als auch auf externe Anfragen wie an CDNs. Der Service Worker kann im lokalen Zwischenspeicher die Antworten zu den Abfragen hinterlegen.

Abb. 2: Schema des Service WorkersAbb. 2: Schema des Service Workers

Ab diesem Zeitpunkt können die Quelldateien der Anwendung direkt aus dem Cache ausgelesen werden, was sie nicht nur offlinefähig macht, sondern auch noch besonders schnell starten lässt. Der Service-Worker-Updateprozess stellt darüber hinaus sicher, dass neue Versionen der Anwendung schnellstmöglich verfügbar gemacht werden.

Bei Blazor WebAssembly lässt sich die Unterstützung für Progressive Web Apps auf sehr einfache Art aktivieren, wie Abbildung 3 zeigt. Wird das Kontrollkästchen Progressive Web Application beim Anlegen eines neuen Blazor-WebAssembly-Projekts gesetzt, wird dem Projekt automatisch ein passendes Web Application Manifest hinzugefügt und jeweils ein passender Service Worker erzeugt. Im Web App Manifest müssen Anwendungstitel, Icon und Farben angepasst werden. Änderungen am Service-Worker-Skript sind nur dann erforderlich, wenn über die Offlinefähigkeit hinaus weitere Funktionen wie Pushunterstützung implementiert werden sollen.

Abb. 3: Die PWA-Unterstützung beim Anlegen eines neuen Blazor-WebAssembly-ProjektsAbb. 3: Die PWA-Unterstützung beim Anlegen eines neuen Blazor-WebAssembly-Projekts

Pushunterstützung jetzt plattformübergreifend verfügbar

Ähnlich wie plattformspezifische Anwendungen können auch Progressive Web Apps Pushbenachrichtigungen empfangen und Benachrichtigungsbanner darstellen. Hierfür werden drei Webtechnologien kombiniert: das Verfahren Web Push, das Web Notifications API und das Push API [5], die eine Erweiterung der Service-Worker-Schnittstellen ist. Da Service Worker, wie oben erwähnt, auch losgelöst von der eigentlichen Website ausgeführt werden können, ist der Empfang von Pushnachrichten auch dann möglich, wenn die Website gar nicht geöffnet ist.

Was in Chrome, Edge und Firefox schon seit vielen Jahren möglich ist, funktioniert seit Kurzem endlich auch auf der Apple-Plattform: Seit Safari 16 auf macOS Ventura (veröffentlicht im September 2022) können sich Websites für Pushbenachrichtigungen registrieren; auf iOS und iPadOS wurde mit Version 16.4 (veröffentlicht im März 2023) die Unterstützung nachgeliefert. Abbildung 4 zeigt ein Benachrichtigungsbanner am Beispiel einer Demo-PWA auf der Betaversion von iPadOS 16.4. Auf iOS und iPadOS ist zu beachten, dass Pushbenachrichtigungen nur dann empfangen werden können, wenn die Anwendung dem Home-Bildschirm hinzugefügt wurde.

Abb. 4: PWA-Push-Benachrichtigungen unter iPadOS 16.4 Beta 1Abb. 4: PWA-Push-Benachrichtigungen unter iPadOS 16.4 Beta 1

Mächtigeres Web dank Project Fugu

Wie eingangs erwähnt, können Progressive Web Apps lediglich auf die Schnittstellen zurückgreifen, die auf der Webplattform zur Verfügung stehen. Selbst wenn das Web über die vergangenen Jahre stark aufgeholt hat, blieb doch immer der sogenannte „Web App Gap“: eine Lücke zwischen den Möglichkeiten plattformspezifischer Apps und Webanwendungen.

Eine Initiative bestehend aus den Chromium-Beitragenden Google, Intel, Microsoft und weiteren Akteur:innen hat sich zum Ziel gesetzt, diese Lücke weiter zu verkleinern. Als Teil dieses Projekts wurden viele weitere Schnittstellen in das Web eingeführt, etwa für den Zugriff auf die Zwischenablage des Betriebssystems, das Dateisystem oder die Liste installierter Schriftarten.

Alle Schnittstellen werden dabei innerhalb der Web Incubator Community Group (WICG) des W3C diskutiert, gemeinsam mit anderen Browserherstellern und der breiteren Webcommunity. Somit können sie zu jedem Zeitpunkt von anderen Browserherstellern zur Implementierung aufgegriffen werden. Um offizieller Webstandard zu werden, braucht eine Schnittstelle mindestens zwei unabhängige Implementierungen (das sind Apples WebKit, Googles Blink und Mozillas Gecko).

Abb. 5: Der Browser übersetzt die JavaScript-Schnittstellen in die passenden plattformspezifischen AufrufeAbb. 5: Der Browser übersetzt die JavaScript-Schnittstellen in die passenden plattformspezifischen Aufrufe

Abbildung 5 zeigt beispielhaft das Web Share API, eine Schnittstelle, um Inhalte (Texte, URLs oder Dateien) aus einer Webanwendung mit einer anderen, auf dem Betriebssystem installierten Anwendung zu teilen. Das geschieht über die Teilen-Funktion des Betriebssystems. Wenn der Browser die Funktionalität unterstützt, stellt er in JavaScript auf dem navigator-Objekt die Methode share() zur Verfügung – andernfalls gibt es die Methode nicht, was für .NET-Augen ungewöhnlich erscheinen mag. Im Gegensatz zu manch älteren Cross-Plattform-Ansätzen wie Xamarin müssen sich Entwickler:innen nicht selbst darum kümmern, je nach Plattform die korrekte plattformspezifische Schnittstelle aufzurufen. Das fällt in die Zuständigkeit des Browsers: Zu dessen Kompilierungszeit wird die JavaScript-Schnittstelle mit dem passenden plattformspezifischen API verknüpft. Webentwickler:innen rufen nur die eine JavaScript-Methode auf, wenn sie vorhanden ist, egal, auf welcher Plattform.

NuGet-Pakete machen Fugu-Schnittstellen in C# verfügbar

Die allermeisten Webplattform-APIs basieren jedoch auf JavaScript. Zur Verwendung in Blazor und C# muss daher zuerst eine Brücke von C# und WebAssembly zur JavaScript-Welt gebaut werden. Das erfordert fortgeschrittene Kenntnisse beider Welten und kann je nach Schnittstelle sehr aufwendig sein. Aus diesem Grund hat die Blazor-Community Wrapper geschrieben, die in Form quelloffener NuGet-Pakete bezogen werden können. Je nach Schnittstelle wird der Kontakt mit JavaScript dabei entweder komplett vermieden oder zumindest deutlich reduziert. Tabelle 2 zeigt eine Auswahl an Wrapper-Paketen für einige Webschnittstellen aus dem Umfeld von Project Fugu. Im Folgenden werden drei dieser Pakete näher besprochen.

API NuGet-Paket
Async Clipboard API Thinktecture.Blazor.AsyncClipboard
Badging API Thinktecture.Blazor.Badging
File System API KristofferStrube.Blazor.FileSystem
File System Access API KristofferStrube.Blazor.FileSystemAccess
File Handling API Thinktecture.Blazor.FileHandling
Web Share API Thinktecture.Blazor.WebShare

Tabelle 2: Eine Auswahl quelloffener NuGet-Wrapper-Pakete für moderne Webschnittstellen

Progressive Enhancement berücksichtigen

Wie oben beschrieben, sind nicht alle Schnittstellen in jedem Browser und auf jedem Betriebssystem verfügbar. Daher muss zuvor geprüft werden, ob die Schnittstelle auf der Zielplattform auch wirklich nutzbar ist. Der Versuch, eine nicht existente Methode in JavaScript aufzurufen, führt zu einem Fehler. Daher bieten alle oben aufgeführten NuGet-Pakete die Methode IsSupportedAsync() an, mit der sich herausfinden lässt, ob die Schnittstelle auf dem jeweiligen System unterstützt wird (Listing 2).

Listing 2

var isSupported = await badgingService.IsSupportedAsync();
if (isSupported)
{
  // enable badging feature
}
else
{
  // use fallback mechanism or hide/disable feature
}

Steht die Schnittstelle nicht zur Verfügung, gibt es zwei Möglichkeiten: Manchmal steht eine alternative Implementierung bereit, die anstelle der gewünschten Funktion verwendet werden kann. So unterstützt etwa Firefox auf Desktopsystemen das Web Share API nicht. Hier könnte stattdessen das mailto-Pseudoprotokoll genutzt werden. Wird es aufgerufen, öffnet sich der E-Mail-Client. Damit lassen sich Texte dann immerhin aus der Anwendung heraus teilen, wenngleich nicht ganz so komfortabel und umfangreich wie mit dem Web Share API. Gibt es auch keine Fallback-Implementierung, sollte die Funktion ausgegraut oder komplett versteckt werden.

Wichtig ist, dass die Anwendung ihren Dienst auf leistungsschwächeren Systemen nicht komplett quittiert, nur weil eine einzelne Funktionalität nicht verfügbar ist. Genau das beschreibt das Konzept des Progressive Enhancement, das sogar Namensbestandteil des PWA-Modells ist.

 

 

 

Badging API: Zahlenplaketten für App-Icons

Das Badging API [6] ist eine sehr einfache Schnittstelle, die es Entwickler:innen erlaubt, ein Notification Badge auf dem Icon ihrer Anwendung anzuzeigen. Über diesen Weg können Anwendungen auf unaufdringliche Art und Weise das Vorhandensein von zu erledigenden Aufgaben, ungelesenen Mails oder Instant Messages kommunizieren. Das setzt voraus, dass es auch ein Icon der Anwendung gibt, sie also zuvor installiert wurde.

Nachdem das passende NuGet-Paket installiert wurde (dotnet add package Thinktecture.Blazor.Badging) und der BadgingService in der Program.cs der ServiceCollection hinzugefügt wurde (builder.Services.AddBadgingService()), kann eine Instanz des BadgingService an der gewünschten Stelle angefordert und die Methode SetAppBadgeAsync() mit der gewünschten Zahl aufgerufen werden:

await badgingService.SetAppBadgeAsync(3);

Abbildung 6 zeigt eine installierte Blazor-Demoanwendung im Standalone-Anzeigemodus unter Windows. Auf ihrem Icon in der Taskleiste wird die Windows-spezifische Zahlenplakette mit der Zahl 3 angezeigt.

Abb. 6: Auf dem Icon der installierten Blazor-PWA wird eine Zahlenplakette dargestelltAbb. 6: Auf dem Icon der installierten Blazor-PWA wird eine Zahlenplakette dargestellt

Verfügbar ist die Schnittstelle auf den Chromium-basierenden Browsern unter macOS und Windows sowie auf iOS und iPadOS. Unter Android funktionieren Badges grundsätzlich anders, weswegen das API hier nicht bereitgestellt wird.

File System Access API: Zugriff auf das Dateisystem

Viele Produktivitätsanwendungen wie Büroprogramme, Bild- oder Videobearbeitungsprogramme arbeiten mit einem traditionellen dateibasierten Workflow: Dateien werden eingelesen, bearbeitet und im Dateisystem danach wieder überschrieben. Um diesen Workflow zu ermöglichen, wurde das File System Access API eingeführt. Es erlaubt Entwickler:innenn, ein Handle auf Dateien oder Verzeichnisse anzufordern. Damit können Dateien ausgelesen und überschrieben, aber auch gelöscht und neue Dateien erstellt werden.

Auch hier muss zuvor das passende NuGet-Paket KristofferStrube.Blazor.FileSystemAccess installiert, der Service der ServiceCollection hinzugefügt und die Unterstützung der Schnittstelle auf dem Zielsystem geprüft werden. Listing 3 zeigt, wie das File System Access API aus Blazor WebAssembly heraus angesprochen werden kann: Die Methode ShowOpenFilePickerAsync() auf dem IFileSystemAccessService öffnet einen Dateiauswahldialog. Mit Hilfe der FilePickerOptions kann angegeben werden, welche Dateiformate akzeptiert werden und ob mehr als eine Datei eingelesen werden soll.

Listing 3

try
{
  var fileHandles = await _fileSystemAccessService
    .ShowOpenFilePickerAsync(_filePickerOptions);
  _fileHandle = fileHandles.Single();
}
catch (JSException ex)
{
  Console.WriteLine(ex);
}

Wählen Benutzer:innen im Picker eine Datei aus und bestätigen die Auswahl mit Open, wie in Abbildung 7 gezeigt, teilt der Browser das Datei-Handle mit der Website.

Abb. 7: Auswahl einer Datei über das File System Access APIAbb. 7: Auswahl einer Datei über das File System Access API

Das File System Access API ist derzeit auf Desktopsystemen nur unter Chrome und Edge verfügbar. Apple und Mozilla möchten die Schnittstelle derzeit nicht implementieren. Glücklicherweise gibt es jedoch Fallback-Schnittstellen, um eine Datei vom Dateisystem einzulesen und danach im Downloads-Verzeichnis abzuspeichern. Damit lassen sich manche Anwendungen auch in anderen Browsern umsetzen.

 

 

 

File Handling API: PWAs als Bearbeitungsprogramm registrieren

Das File Handling API ist eine Erweiterung des File System Access API. Es erlaubt einer PWA, sich bei ihrer Installation als Bearbeitungsprogramm für bestimmte Dateiformate zu registrieren. Auch das ist derzeit nur unter Chrome und Edge auf dem Desktop möglich. Zur Implementierung des API sind zwei Teile erforderlich: Ein deklarativer Teil, bei dem die Unterstützung für bestimmte Dateierweiterungen beim Betriebssystem angemeldet wird, und ein imperativer Teil, bei dem die geöffnete Datei entgegengenommen wird.

Listing 4 zeigt, wie sich die Anwendung bei ihrer Installation als Bearbeitungsprogramm hinterlegen kann. Dazu muss dem Web App Manifest die Eigenschaft file_handlers hinzugefügt werden. Diese Eigenschaft nimmt ein Array entgegen, in dem verschiedene Registrierungen vorgenommen werden können. Jedes Registrierungsobjekt besteht aus einem Dictionary (Eigenschaft accept), das die unterstützten Mediatypen mit ihren jeweiligen Dateiendungen aufzählt. Da auch diese Schnittstelle plattformübergreifend verfügbar ist, müssen beide Angaben hinterlegt werden. Der URL in der Eigenschaft action wird aufgerufen, wenn die PWA mit einer Datei von diesem Format geöffnet wird. Auch ließe sich noch ein Icon hinterlegen, das der Dateiexplorer für Dateien mit dieser Erweiterung darstellen soll.

Listing 4

{
  "file_handlers": [{
    "action": "./",
    "accept": {
      "text/plain": [".txt"]
    }
  }]
}

Wird die Anwendung unter Zuhilfenahme dieses Manifests installiert, registriert sie sich nun als Bearbeitungsprogramm für TXT-Dateien. Abbildung 8 zeigt die Blazor PWA im Öffnen-mit-Menü des Windows Explorers.

Abb. 8: Eine Blazor PWA hat sich als Bearbeitungsprogramm für TXT-Dateien registriertAbb. 8: Eine Blazor PWA hat sich als Bearbeitungsprogramm für TXT-Dateien registriert

Im nächsten Schritt muss der Blazor-Anwendungscode noch angepasst werden, um eine Datei, mit der die PWA als Bearbeitungsprogramm geöffnet wurde, entgegenzunehmen. Auch hierfür muss das passende NuGet-Paket Thinktecture.Blazor.FileHandling installiert, der Service der Collection hinzugefügt und die Unterstützung ermittelt werden. Dann kann mit Hilfe der Methode SetConsumerAsync() ein Launch Consumer gesetzt werden. Dieser Callback nimmt die Startparameter der PWA entgegen. In der Eigenschaft Files findet sich dann die Liste der Handles, mit denen die Anwendung gestartet wurde. Sie entsprechen genau den Handles aus dem File System Access API, die oben demonstriert wurden. Listing 5 zeigt, wie durch die Handles iteriert werden kann und Inhalte aus den Textdateien ausgegeben werden können.

Listing 5

await _fileHandlingService.SetConsumerAsync(async (launchParams) =>
{
  foreach (var fileSystemHandle in launchParams.Files)
  {
    if (fileSystemHandle is FileSystemFileHandle fileSystemFileHandle)
    {
      var file = await fileSystemFileHandle.GetFileAsync();
      var text = await file.TextAsync();
      Console.WriteLine(text);
    }
  }
});

PWAs eignen sich für viele Einsatzgebiete

Progressive Web Apps bieten eine vielversprechende Alternative zu plattformspezifischen Anwendungen: Sie sind schnell und flexibel, sind breit verfügbar auf verschiedenen Endgeräten, einfach zu installieren und zu aktualisieren und können auch offline betrieben werden.

Daher können sie in einer Vielzahl von Anwendungsbereichen eingesetzt werden. Unter anderem lassen sich Produktivitätsanwendungen realisieren, ebenso wie integrierte Entwicklungsumgebungen oder Codeeditoren. Auch Kreativanwendungen wie Bild- oder Videobearbeitungsprogramme können als PWAs implementiert werden, zudem Büroanwendungen wie Textverarbeitungsprogramme, Tabellenkalkulationen oder Präsentationsprogramme. Nicht zuletzt bieten sich PWAs auch für Enterprise-Awendungen, wie z. B. ERP-Systeme oder Kundenportale, an.

Spotify, Visual Studio Code und Photoshop können als Web-App genutzt werden

Dass das Anwendungsmodell marktreif ist, demonstrieren gleich drei sehr bekannte Anwendungen: Spotify hat eine webbasierte Benutzeroberfläche. In den Desktopversionen von Spotify ist diese in einem Electron Wrapper verpackt. Das Spotify PWA unter open.spotify.com sieht dem Desktopclient daher zum Verwechseln ähnlich, sobald man es auf dem System installiert. Als cloudbasierte Anwendung hat es Spotify jedoch relativ leicht, eine Webvariante herauszugeben.

Anspruchsvoller ist da der Code-Editor Visual Studio Code von Microsoft, der auf lokalen Dateien arbeitet. Auch dessen Benutzeroberfläche war schon immer webbasiert. Sein Desktopclient ist mit Electron verpackt, um dem Editor Zugriff auf die plattformspezifischen Schnittstellen zu geben. Mit der Veröffentlichung des File System Access API war es jedoch für Microsoft möglich, Visual Studio Code unter vscode.dev als Webvariante herauszugeben (Abb. 9). Damit können ganze Verzeichnisse geöffnet und bearbeitet werden, auch Plug-ins funktionieren, sofern sie keinen plattformspezifischen Anteil aufweisen. Was jedoch nicht lauffähig ist, ist das Terminal. Denn aus dem Webbrowser heraus gibt es keinen Shellzugriff und es ist unwahrscheinlich, dass er ohne weitere Einschränkungen jemals eingeführt wird. Dennoch lässt sich der Codeeditor damit schnell und unkompliziert öffnen und einfache Anpassungen können vorgenommen werden, ganz ohne ein großes Installationspaket herunterladen zu müssen.

Abb. 9: Visual Studio Code läuft dank des File System Access API direkt im BrowserAbb. 9: Visual Studio Code läuft dank des File System Access API direkt im Browser

Visual Studio Code ist allerdings HTML- und TypeScript-basiert. Etwas näher an Blazor WebAssembly ist die Webvariante von Photoshop, die Adobe im September 2021 veröffentlichte. Die Anwendung setzt ein Creative-Cloud-Abonnement voraus und ist dazu gedacht, kleinere Anpassungen an Bildern vornehmen zu können, ohne gleich den kompletten, sehr umfangreichen Photoshop-Client herunterladen zu müssen.

Photoshop ist eine über Jahrzehnte gewachsene Anwendung. Um in C++ geschriebenen Bestandscode ins Web zu bringen, kooperierte Adobe mit Google, das fehlende Features in WebAssembly und andere fehlende Webschnittstellen implementierte [7]. Und so erhält alter Photoshop-Bestandscode dank WebAssembly ein zweites Leben im Browser. Neu geschriebene Progressive Web Apps reihen sich also in gute Gesellschaft ein.

Abb. 10: Dank Googles Unterstützung brachte Adobe Photoshop ins WebAbb. 10: Dank Googles Unterstützung brachte Adobe Photoshop ins Web

PWAs lösen ihr Versprechen ein

Dank der Progressive Web Apps wird der alte Java-Traum „Write once, run anywhere“ endlich Realität. Über die Jahre ist das Anwendungsmodell immer leistungsfähiger geworden, was die Verfügbarkeit von Pushbenachrichtigungen unter iOS und iPadOS erneut unterstreicht. Dank Initiativen wie Project Fugu ist vorläufig auch kein Ende in Sicht. Außerdem sind Progressive Web Apps gut zugänglich, da sie einfach aus dem Browser heraus aufgerufen werden können.

Blazor WebAssembly wiederum ist interessant für Teams mit bestehendem Wissen in C# und .NET. In C# geschriebene Datenmodelle oder Validierungslogik können zwischen Client und Server geteilt werden und selbst bestehender Code kann gegebenenfalls ins Web übertragen werden.

Einschränkungen des PWA-Modells

Allerdings gibt es auch Einschränkungen im Anwendungsmodell der PWAs: Von Nachteil ist, dass die Installation einer PWA für Anwender:innen möglicherweise nicht offensichtlich ist. Die Installation wird unter iOS und iPadOS über das Teilen-Menü des Browsers ausgeführt, dazu müssen Anwender:innen innerhalb des Menüs auch noch nach unten scrollen. In den Chromium-basierten Browsern wird die Installation in Form einer kleinen Schaltfläche auf der rechten Seite der Adresszeile angeboten.

Darüber hinaus können nur die Schnittstellen genutzt werden, die direkt im Browser verfügbar sind. Bestimmte Anwendungsarten wie Antivirenscanner oder Cloud-Sync-Programme, die einer besonders tiefen plattformspezifischen Integration bedürfen, können nicht als PWA implementiert werden. Schließlich sind nicht alle interessanten APIs auf allen Betriebssystemen und Browsern implementiert, was die Funktionalität einer PWA auf diesen Geräten einschränken kann.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Einschränkungen von Blazor WebAssembly

Schließlich weist auch Blazor WebAssembly Einschränkungen auf. Während Entwickler:innen nun vorrangig mit C# arbeiten können, werden sie trotzdem Kenntnisse in HTML, CSS und auch JavaScript benötigen. Darüber hinaus funktioniert die Entwicklungsunterstützung nur in Microsoft Visual Studio auf Windows gut, in Rider wird Blazor bisher nur stiefmütterlich unterstützt.

Das wohl größte Problem von Blazor WebAssembly ist allerdings die umfangreiche Bundle-Größe der Anwendung. Eine Hello-World-Anwendung wird schon im Minimalfall (z. B. durch Entfernung der Kulturinformationen) rund 2 MByte groß – ein Vielfaches, verglichen mit den 50 KByte einer einfachen Angular-Anwendung. Da wird auch die Ahead-of-Time-Kompilierung (AoT) keine Abhilfe schaffen; hier werden die Apps tendenziell sogar noch größer. Die Bundle-Größe führt daher zu einem Nachteil in der initialen Ladezeit, insbesondere bei Nutzung von weniger schnellen Verbindungen, etwa bei mobilen Anwendungen.

Um dieses Problem abzumildern, setzt Blazor ein recht aggressives Caching ein. Das kann allerdings zur Entwicklungszeit zu einiger Verwirrung führen, wenn im Browser eine veraltete Version angezeigt wird.

Des Weiteren werden bei Blazor WebAssembly DLL-Dateien zum Browser übertragen. Da es sich dabei um ausführbare Dateien handelt, können Unternehmensproxys und -firewalls einen Strich durch die Rechnung machen, sodass die Blazor-Anwendung gar nicht erst startet. Mit dem WebCIL-Format ist hier jedoch bereits eine Lösung in Sicht.

Zusammengefasst ist Blazor WebAssembly für B2C-Anwendungen oder andere Umgebungen, die nicht kontrolliert werden können, möglicherweise nicht die beste Wahl.

Zusammenfassung

Blazor, Progressive Web Apps und Project Fugu sind großartige Technologien, um Webanwendungen zu entwickeln: Blazor WebAssembly ermöglicht es Entwickler:innen, ihre bestehenden C#- und .NET-Kenntnisse zu nutzen, um Apps zu entwickeln, die nicht nur auf allen relevanten Browsern laufen, sondern auch ohne Installation direkt im Browser. Progressive Web Apps verbessern das Nutzungserlebnis von Webanwendungen durch Offlinefähigkeit und Installierbarkeit. Project Fugu bringt auch weiterhin neue Schnittstellen ins Web, um eine noch tiefere Integration mit dem Betriebssystem zu erreichen. C#-basierte NuGet-Pakete aus der Community erleichtern die Verwendung dieser Schnittstellen. Zu guter Letzt sollte immer an das Konzept des Progressive Enhancement gedacht werden, um sicherzustellen, dass Anwender:innen älterer oder weniger leistungsfähiger Browser die App weiterhin benutzen können. Wenn Sie all diese Konzepte berücksichtigen, schreiben Sie großartige Webanwendungen genau einmal und bringen Sie zu all Ihren Anwender:innen, ganz egal, welche Plattform sie nutzen.

The post PWAs mit Blazor WebAssembly appeared first on BASTA!.

]]>
.NET 7.0 bringt Desktop-Features zu .NET MAUI https://basta.net/blog/dotnet-maui-mit-dotnet-7-0/ Fri, 21 Apr 2023 08:34:39 +0000 https://basta.net/?p=91563 .NET MAUI bringt in .NET 7.0 einige spannende Neuerungen, die vor allem die Herzen von Desktopentwickler:innen höherschlagen lassen. Das ist vor allem wegen des knappen Zeithorizonts eine angenehme Überraschung, denn gerade einmal fünfeinhalb Monate lagen zwischen der verspäteten Erstveröffentlichung von .NET MAUI am 23. Mai 2022 und dem ersten großen Update zur .NET Conf 2022 am 7. November 2022. Microsofts Cross-Plattform-Team hatte also weniger als halb so viel Zeit wie andere .NET-Produktteams.

The post .NET 7.0 bringt Desktop-Features zu .NET MAUI appeared first on BASTA!.

]]>
Als Microsoft .NET MAUI erstmals ankündigte, wurde das Cross-Plattform-Framework als „Evolution von Xamarin.Forms, von mobilen erweitert auf Desktop-Szenarien“ vorgestellt.

SIE LIEBEN UI?

Entdecken Sie die BASTA! Tracks

Auch wenn es die versprochene Erweiterung mit .NET 6 schon gab – schließlich lief MAUI bereits unter Windows und macOS –, wirkte die Desktopumsetzung an einigen Stellen noch unfertig. Umso erfreulicher ist es, dass der Fokus der neuen Funktionen von .NET MAUI klar im Desktopbereich liegt.

Fensterbauer

Das Öffnen mehrerer Anwendungsfenster ist aus der Desktopentwicklung seit über 30 Jahren nicht mehr wegzudenken. Unter .NET MAUI und dem Vorgänger Xamarin.Forms war es aufgrund der ursprünglichen Fokussierung auf mobile Betriebssysteme jedoch nicht ohne weiteres möglich. Mit .NET 7 gibt es nun den in Listing 1 gezeigten plattformübergreifenden Weg zum Öffnen neuer Fenster über die Methode OpenWindow des Application-Objekts.

Listing 1

private void OpenDetailsPageInNewWindow(Session session)
{
  var detailsVM = new SessionDetailPageViewModel
  {
    Session = session
  };
  var sessionPage = new SessionDetailPage(detailsVM);
  var window = new Window(sessionPage);
 
  Application.Current.OpenWindow(window);
}

Die neue Funktionalität, die Sie in Abbildung 1 im Einsatz sehen, kann unter Android, macOS und iPadOS (also iOS auf dem iPad) genutzt werden. Auf dem iPhone können keine weiteren Fenster geöffnet werden.

Abb. 1: .NET MAUI ermöglicht unter .NET 7 das Öffnen mehrerer Anwendungsfenster

Das Öffnen neuer Fenster ist nicht die einzige Verbesserung im Fensterumfeld, die .NET MAUI unter .NET 7 mitbringt. Unter Windows können außerdem Fenstergröße und Fensterposition konfiguriert werden, wie Listing 2 zeigt. Der abgebildete Quellcode läuft so jedoch nur unter Windows. Unter macOS gibt es zumindest einen Workaround, über den die Fenstergröße gesetzt werden kann, unter Android und iOS ist es gar nicht möglich. Eine detaillierte Anleitung zum Workaround finden Sie unter [1].

Listing 2

protected override Window CreateWindow(IActivationState activationState)
{
  var window = base.CreateWindow(activationState);
 
  // Fensterposition setzen
  window.X = 300;
  window.Y = 100;
 
  // Größe des Fensters definieren
  window.Width = 700;
  window.Height = 800;
  return window;
}

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Menüs und ToolTipps

Bereits unter Xamarin.Forms gab es das SwipeView-Element, mit dem über eine horizontale Wischgeste ein seitliches Kontextmenü, ähnlich wie man es von E-Mail- oder Messenger-Apps auf dem Mobiltelefon kennt, öffnen kann. Dieses Feature gibt es auch unter .NET MAUI, nur ist es leider für die meisten Desktopnutzer:innen unbenutzbar. Denn eine Wischgeste zum Öffnen eines Menüs ist unter Desktopbetriebssystemen im Unterschied zu Mobilgeräten unüblich und dürfte daher von den meisten Benutzer:innen übersehen werden. Außerdem lässt sich diese Geste nicht mit der Maus durchführen, sondern nur auf einem Windows-Gerät mit Touchscreen.

Microsoft löst das Problem mit den in Abbildung 1 gezeigten neuen Kontextmenüs. Sie lassen sich unter Windows und macOS wie gewohnt über einen Klick auf die sekundäre Maustaste öffnen. Unter Android und iOS stehen sie nicht zur Verfügung. Wie Listing 3 zeigt, werden Kontextmenüeinträge durch MenuFlyoutItem-Elemente umgesetzt, die über einen Text, ein Command oder einen Click Event Handler sowie ein Icon verfügen können. Icons werden jedoch nur unter Windows, nicht aber unter macOS unterstützt. Neben MenuFlyoutItem-Elementen gibt es noch den MenuFlyoutSeperator zur Anzeige einer Trennlinie und das MenuFlyoutSubItem-Element zur Definition von Untermenüs.

Die neuen ToolTips sind übrigens ein eleganter Weg, die Benutzer:innen dezent darauf hinzuweisen, dass ein Kontextmenü geöffnet werden kann. Genau wie Kontextmenüs, die Sie auch in Listing 3 sehen, können ToolTips zu jedem Bildschirmelement hinzugefügt werden.

Listing 3

<Grid RowDefinitions="25, auto" 
  ToolTipProperties.Text="Kontextmenü über Rechtsklick öffnen">
  <Label Text="{Binding Time}" />
  <Label Text="{Binding Title}" Grid.Row="1" FontSize="Subtitle" FontAttributes="Bold"/>
  <FlyoutBase.ContextFlyout>
    <MenuFlyout>
      <MenuFlyoutItem Text="Favorit" Command="{Binding FavoriteCommand }" />
        <MenuFlyoutItem Text="In neuem Fenster öffnen" Command="{Binding OpenNewWindowCommand }" >
        <MenuFlyoutItem.IconImageSource>
          <FontImageSource FontFamily="FA-6-Solid-900" Glyph="{x:StaticResource IconUpRightFromSquare}" Color="{AppThemeBinding Light=Black, Dark=White}" />
        </MenuFlyoutItem.IconImageSource>
      </MenuFlyoutItem>
      <MenuFlyoutSeparator/>
      <MenuFlyoutSubItem Text="Sprecher anrufen">
        <MenuFlyoutItem Text="Geschäftlich" Command="{Binding CallSpeakerBusinessCommand }"/>
        <MenuFlyoutItem Text="Privat" Command="{Binding CallSpeakerPrivateCommand }" />
      </MenuFlyoutSubItem>
    </MenuFlyout>
  </FlyoutBase.ContextFlyout>
</Grid>

Mausinteraktionen

Auf mobilen Geräten wird primär der Finger zur Interaktion mit Apps genutzt. Neben Tipp- sind Wischgesten die häufigsten Aktionen, die man hier ausführt. Maus-Events wie MouseOverMouseEnter oder MouseMove konnten bisher unter .NET MAUI genau wie zuvor unter Xamarin.Forms aufgrund der Fokussierung auf Fingereingaben auf Mobilgeräten nicht behandelt werden.

Unter .NET 7 können Entwickler:innen nun auch die folgenden Mausgesten behandeln:

  • PointerEntered: Die Maus wird erstmalig über einem Element positioniert.

  • PointerMoved: Die Maus wird über einem Element bewegt.

  • PointerExited: Die Maus verlässt ein Element.

Zusätzlich zu den drei Gesten kann nun auch der sekundäre bzw. Rechtsklick behandelt werden. Listing 4 zeigt die Registrierung der entsprechenden Event Handler im XAML-Code, Listing 5 eine beispielhafte Behandlung in C#. Im gezeigten Beispiel wird die Farbe eines Rechtecks über eine Farbanimation verändert, sobald Benutzer:innen die Maus über das Rechteck bewegen oder sie wieder hinausbewegen.

Listing 4

<Rectangle BackgroundColor="{StaticResource Primary}"
  WidthRequest="300" 
  HeightRequest="200"
  HorizontalOptions="Center"
  VerticalOptions="Center"
  ToolTipProperties.Text="Hover und Klick">
  <Rectangle.GestureRecognizers>
    <TapGestureRecognizer Buttons="Primary" Tapped="PrimaryTapped" />
    <TapGestureRecognizer Buttons="Secondary" Tapped="SecondaryTapped" />
    <PointerGestureRecognizer 
       PointerEntered="OnPointerEntered"
       PointerExited="OnPointerExited"
       PointerMoved="OnPointerMoved" />
    </Rectangle.GestureRecognizers>
</Rectangle>
<Label x:Name="PositionLabel" Text="Pos:"/>

Listing 5

private async void SecondaryTapped(object sender, TappedEventArgs e)
{
  await DisplayAlert("Click", "Secondary", "OK");
}
 
private async void OnPointerEntered(object sender, PointerEventArgs e)
{
  var rectangle = sender as Rectangle;
  await rectangle.BackgroundColorTo(Colors.Red);
}
 
private async void OnPointerExited(object sender, PointerEventArgs e)
{
  var rectangle = sender as Rectangle;
  await rectangle.BackgroundColorTo(Color.FromArgb("#512BD4"));
}
 
private void OnPointerMoved(object sender, PointerEventArgs e)
{
  Point? windowPosition = e.GetPosition(null);
  PositionLabel.Text = 
    $"Position: x: {windowPosition.Value.X} -  y: {windowPosition.Value.Y}";
}

Wer die Änderung der Optik eines Steuerelements, sobald die Maus darüber bewegt wird, lieber deklarativ durchführt, kann dazu den neuen PointerOver VisualState des VisualStateManager nutzen. In Listing 6 sehen Sie, wie es geht. Im gezeigten Beispiel wird die Hintergrundfarbe eines Buttons auf Rot geändert, sobald die Maus über den Button bewegt wird. Eine ausführliche Dokumentation zu Visual States unter .NET MAUI gibt es unter [2].

 

 

Listing 6

<Button Text="Beweg die Maus über mich!">
  <VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="CommonStates">
      <VisualState x:Name="Normal">
        <VisualState.Setters>
          <Setter Property="BackgroundColor" Value="Blue" />
        </VisualState.Setters>
      </VisualState>
 
      <VisualState x:Name="PointerOver">
        <VisualState.Setters>
          <Setter Property="BackgroundColor" Value="Red" />
        </VisualState.Setters>
      </VisualState>
    </VisualStateGroup>
  </VisualStateManager.VisualStateGroups>
</Button>

Karten

Alle bisher in diesem Artikel vorgestellten neuen Funktionen bezogen sich auf die Entwicklung von Desktop-Apps mit .NET MAUI. Die letzte neue Funktion, die Darstellung von Karten, wird unter macOS, Android und iOS unterstützt. Unter Windows wirft die Kartenkomponente einen Laufzeitfehler, da das zugrunde liegende UI-Framework WinUI 3 keine Kartenkomponente besitzt. Auf den drei anderen Betriebssystemen wird jeweils das native Kartensteuerelement des Betriebssystems genutzt. Abbildung 2 zeigt zum Beispiel die Nutzung von Google Maps unter Android.

Mit dem Map-Element können Sie im Code auf verschiedene Weise interagieren. Listing 7 zeigt, wie Sie den gezeigten Kartenausschnitt deklarativ über die Eigenschaft MapSpan festlegen können. In Listing 8 sehen Sie, wie Sie per C#-Quellcode einen Pin zur Karte hinzufügen können.

Listing 7

<maps:Map x:Name="map">
  <x:Arguments>
    <MapSpan>
      <x:Arguments>
        <sensors:Location>
          <x:Arguments>
            <x:Double>50.5014483</x:Double>
            <x:Double>7.308297599999</x:Double>
          </x:Arguments>
        </sensors:Location>
        <x:Double>0.01</x:Double>
        <x:Double>0.01</x:Double>
      </x:Arguments>
    </MapSpan>
</x:Arguments>
Listing 8
Pin qualityBytesPin = new Pin
{
  Location = new Location(50.5014483, 7.308297599999),
  Label = "Quality Bytes GmbH",
  Address = "Bad Breisig",
  Type = PinType.Place
};
 
map.Pins.Add(qualityBytesPin);

Zusätzlich zu den beiden gezeigten Funktionen können Sie außerdem Formen wie Kreise, Linien oder Polygone auf die Karte zeichnen oder die Darstellung über die Eigenschaft MapType der Karte auf eine Straßen-, Satelliten-, oder Hybridansicht festlegen.

Abb. 2: Karten können unter Android, iOS und macOS dargestellt werden

Performance und Stabilität

Zusätzlich zur Umsetzung neuer Funktionen setzte sich das .NET-MAUI-Team intensiv mit dem Thema Performance auseinander. Unter [3] beschreibt Microsoft, welche Verbesserungen unter der Haube vorgenommen wurden, um die Performance zu optimieren. Im Fokus standen bei der Optimierung die allgemeine Rendering-Geschwindigkeit sowie das Scroll-Verhalten in Listen.

Über die Performance hinaus arbeitete Microsoft auch an der Stabilität. Im Bereich Visual-Studio-Integration konnten 80 Prozent der Bugs von Visual Studio 2022 17.3 und .NET MAUI 6 unter Visual Studio 2022 17.4 und .NET MAUI 7 behoben werden. Außerdem wurde eine beträchtliche Anzahl an GitHub-Issues geschlossen.

Dass das Team hier jedoch noch einen weiten Weg vor sich hat, zeigen die knapp 2 000 offenen Issues. Der Fairness halber muss allerdings erwähnt werden, dass nicht alle Issues Fehler sind, sondern auch Feature-Requests sowie Duplikate.

Fazit

Die Anzahl der neuen .NET-MAUI-Funktionen mag auf den ersten Blick enttäuschen, schließlich lassen sie sich an zwei Händen abzählen. Zieht man jedoch in Betracht, dass das Team nur fünfeinhalb Monate Zeit hatte und darüber hinaus an Performance und Stabilität gearbeitet hat, sieht es gar nicht mehr so schlecht aus.

Die neuen Funktionen haben sicherlich einen geringen Wow-Faktor, schließen aber wichtige Funktionslücken und machen .NET MAUI somit immer mehr zu einer validen Alternative für die Entwicklung plattformübergreifender Desktopanwendungen.

Links & Literatur

[1] https://learn.microsoft.com/de-de/dotnet/maui/fundamentals/windows?view=net-maui-7.0#position-and-size-a-window

[2] https://learn.microsoft.com/de-de/dotnet/maui/user-interface/visual-states?view=net-maui-7.0

[3] https://devblogs.microsoft.com/dotnet/dotnet-7-performance-improvements-in-dotnet-maui/

 

The post .NET 7.0 bringt Desktop-Features zu .NET MAUI appeared first on BASTA!.

]]>
Eine Einführung in die Arbeit mit Azure Cognitive Services https://basta.net/blog/azure-cognitive-services-eine-einfuehrung/ Fri, 17 Mar 2023 10:19:16 +0000 https://basta.net/?p=91459 Früher entwickelte man Machine-Learning-getriebene Systeme dadurch, dass man sich im ersten Schritt Informationen über die verschiedenen neuronalen Netzwerke beschaffte und diese danach schrittweise – meist von Hand – umsetzte. Mit den Azure Cognitive Services stellt Microsoft Entwickler:innen ein schlagkräftiges Werkzeug zur Verfügung, das diverse Aufgaben aus dem Bereich der künstlichen Intelligenz automatisiert erledigt.

The post Eine Einführung in die Arbeit mit Azure Cognitive Services appeared first on BASTA!.

]]>
Schon ob des immensen Funktionsumfangs ist es illusorisch, in einem einzigen Fachartikel eine Komplettdarstellung der Produktfamilie anzustreben. Aus diesem Grund konzentriert sich dieser Artikel darauf, Highlights und besonders interessante Anwendungsfälle unter Windows 10 zu illustrieren.

SIE LIEBEN AZURE?

Entdecken Sie die BASTA! Tracks

Im Interesse der didaktischen Ehrlichkeit sei schon hier zweierlei angemerkt: Erstens gilt, dass die Nutzung von aus der Cloud bezogenen Services in vielen Fällen langfristig teurer ist als das Selbstentwickeln. Es ist also immer empfehlenswert, die an Microsoft überwiesenen Kosten im Blick zu behalten.

Zweitens sei angemerkt, dass Microsoft die Azure Cognitive Services explizit nicht als Mittel zur Promotion der hauseigenen Windows-Plattform nutzt. So gut wie alle hier vorgestellten Operationen lassen sie sich analog auch unter anderen Betriebssystemen benutzen. Für viele APIs stehen auch REST-Interfaces beziehungsweise Python-SDKs zur Verfügung.

Erste Amtshandlung: Einrichtung des Azure-Backends

Aus der Ausrichtung der Cognitive Services als Azure-Cloud-Service folgt, dass im ersten Schritt logischerweise ein Azure-Konto eingerichtet werden muss, das die für die Authentifikation erforderlichen Connection Strings zur Verfügung stellt.

Interessant ist dabei, dass Microsoft bei den Zugriffsaccounts zwei Gruppen unterscheidet. Wem es, wie dem Autor dieser Zeilen, vor allem auf Bequemlichkeit und nicht so sehr auf Kosten ankommt, ist mit einem Account vom Typ „Cognitive Services Multi-service Account“ am besten gedient. Dabei handelt es sich um eine Catch-All-Ressource, die mit einem Zugriffsschlüssel die folgenden AI-Dienstleistungen gleichermaßen ansprechbar macht:

  • Decision: Content Moderator

  • Language: Language, Translator

  • Speech: Speech

  • Vision: Computer Vision, Custom Vision, Face

Weniger schön ist am Cognitive Services Multi-service Account lediglich, dass Microsoft seine Nutzung von der (für Neuanmeldungen) zur Verfügung stehenden kostenlosen Kontingentierung ausnimmt.

Cognitive Services Contributor erforderlich

Beachten Sie, dass die Nutzung der APIs die Cognitive-Services-Contributor-Rolle voraussetzt. Weitere Informationen zur Konfiguration finden sich unter [1] und [2].

Im nächsten Schritt suchen wir im Azure-Backend nach einer Ressource vom Typ Cognitive services multi-service account. Klicken Sie im nächsten Schritt auf den hellblauen Knopf Create Cognitive services multi-service account, um eine neue Ressource zu erzeugen. Die Zuweisung von Ressourcengruppe, Name und Co. erfolgt dabei wie gewohnt.

Im Bereich der Service Tier ist es empfehlenswert, die Option S0 auszuwählen, sie führt zur Anwendung der unter [3] bereitstehenden Preise. Im Allgemeinen gilt dabei, dass 1 000 Anfragen immer rund einen US-Dollar kosten. Die restlichen Einstellungen sind im Allgemeinen weniger relevant, weshalb wir in die Rubrik Review + Create wechseln und danach durch Anklicken des Create-Knopfes das Backend zum Erzeugen des neuen Containers animieren.

Nach dem erfolgreichen Durchlaufen des Generationsprozesses wechseln Sie im Backend in die Eigenschaften des Services, und öffnen danach die Rubrik Resource Management | Keys and Endpoint. Das Backend blendet dort den in Abbildung 1 gezeigten Dialog ein, der das Herunterladen beziehungsweise Sichtbarmachen der verschiedenen kryptographischen Schlüssel ermöglicht.

hanna_cognitive_1.tif_fmt1.jpgAbb. 1: Diese Schlüssel authentifizieren AI-Applikationen im Backend

Beachten Sie, dass das in Besitz nehmen dieser Strings dem neuen Eigentümer das Ausführen beliebiger Berechnungen zu Ihren Kosten verursacht. Wer sie in einen End-User-Facing-Dienst verpackt, begeht eine Original Sin.

Das vernünftige Managen derartiger Credentials ist allerdings ein Thema der fortgeschrittenen Kryptographie, mit dem wir uns in diesem Artikel nur insofern weiter auseinandersetzen werden, als Sie der Autor vor dem Nutzen einer Stringkonstante in einem Assembly nochmals explizit warnt. In diesem Zusammenhang sei allerdings noch auf [4] verwiesen, wo Microsoft einige (lesenswerte) Zusatzhinweise zu dieser Thematik untergebracht hat.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Ein erstes Testprojekt verarbeitet Bilder

Wie in der Einleitung festgestellt gilt, dass der Großteil der im Rahmen der Azure Cognitive Services zur Verfügung gestellten Dienste im Hintergrund über ein REST API realisiert wird: Wer ausreichend Geduld mitbringt, kann die Dienste also mit einem beliebigen System ansprechen, sofern dieses zum Absetzen von HTTP-Anfragen befähigt ist.

Das manuelle Implementieren von REST APIs ist allerdings eine erfahrungsgemäß wenig dankbare Aktivität, weshalb Microsoft je nach Popularität des jeweiligen Diensts mehr oder weniger umfangreiche SDKs für verschiedene Zielsysteme zur Verfügung stellt. Im Interesse der Bequemlichkeit wird der Autor in den folgenden Schritten auf Visual Studio 2022 setzen. Unser erstes Projekt entsteht auf Basis der Vorlage Leere App (Universelle Windows-App). Als Projektname sei der String SUSCogni1 vergeben, im Bereich der Zielversionen von Windows erweist sich das Cognitive Services API als vergleichsweise wenig anspruchsvoll.

Nach dem erfolgreichen Durchlaufen des Projektgenerators ist es empfehlenswert, die NuGet-Paketliste zu öffnen: Die Auslieferung der für die Cognitive Services benötigten Komponenten erledigt Microsoft direkt über den in .NET integrierten Paketmanager.

Im nächsten Schritt geben wir den Suchstring Microsoft.Azure.CognitiveServices.Vision.ComputerVision ein und entscheiden uns für die Installation der aktuellsten stabilen Version. Zum Zeitpunkt der Drucklegung bekommen wir die Version 7.0.1 ins Projekt integriert.

Wir werden hier einen ersten Versuch mit dem Bildanalyse-API durchführen: Die Vorgehensweise mit der Installation eines NuGet-Pakets gilt allerdings analog für verschiedene andere Systeme. Anschließend ist es empfehlenswert, einige lokale Variablen mit den für die Authentifikation notwendigen Credentials anzulegen. Die Nutzung der beiden, nach dem folgenden Schema benannten Members hat sich im Bereich des Azure-Codes fast schon als Standard etabliert:

public sealed partial class MainPage : Page {
  static string subscriptionKey = "df. . .";
  static string endpoint = "https://susexperiment.cognitiveservices.azure.com/";

Ob Sie im Feld subscriptionKey den Wert von Key1 oder von Key2 eingeben, bleibt dabei Ihnen überlassen. Den korrekten Endpoint URL finden Sie ebenfalls im Azure-Backend (Abb. 1).

Die .NET SDKs erledigen die eigentliche Kommunikation zwischen Applikation und lokalem Code normalerweise durch eine Abstraktionsklasse, die im Allgemeinen als <Service>-Client bezeichnet wird.

Wer wie wir hier mit einem UWP-Projektskelett arbeitet, sollte diese im ersten Schritt als Membervariable der Mainpage oder einer anderen Klasse anlegen:

public sealed partial class MainPage : Page {
  ComputerVisionClient myClient;

Die eigentliche Initialisierung unter Nutzung der beiden Credential-Variablen ist dann keine Raketenphysik:

public MainPage() {
  this.InitializeComponent();
  myClient =   new ComputerVisionClient(new ApiKeyServiceClientCredentials(subscriptionKey))  { Endpoint = endpoint };
}

Die Entscheidung, auf ein UWP-Projektskelett zu setzen, setzt hier ein wenig Umgreifen voraus. UWP-Applikationen dürfen von Haus aus ja nicht mit dem kompletten Dateisystem der Host-Workstation interagieren. Als Workaround bietet sich die Nutzung eines File Picker an, zu dessen Aktivierung wir im ersten Schritt in der MainPage.xaml einen neuen Button einschreiben müssen.

Die eigentliche Realisierung erfolgt dann fast schon analog zum Common-Dialog. Durch das Async-Setzen des Klick-Handlers ist sichergestellt, dass wir die Ergebnisse des Aufrufs von PickSingleFileAsync direkt in der Methode abernten können (Listing 1).

Listing 1

private async void CmdStartPicker_Click(object sender, RoutedEventArgs e) {
  var picker = new Windows.Storage.Pickers.FileOpenPicker();
  picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail;
  picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
  picker.FileTypeFilter.Add(".jpg");
  picker.FileTypeFilter.Add(".jpeg");
  Windows.Storage.StorageFile file = await picker.PickSingleFileAsync();

Sofern sich der Benutzer erfolgreich für eine Datei entschieden hat, bekommen wir diese in Form eines StorageFile-Objekts zurückgeliefert. Dieses übergeben wir danach an die Methode analyzeFile, in der wir im nächsten Schritt die eigentliche Interaktion mit den Cognitive Services unterbringen werden:

  if (file != null) {
    analyzeFile(file);
  }
}

Der Kopf von analyzeFile beginnt mit der Festlegung der zu analysierenden Features. In der Welt des Machine Learnings, Microsoft übernimmt diese Begriffe in Azure, ist ein Feature dabei eine Gruppe von Attributen, die ein ML-Algorithmus aus angelieferten Informationen zu extrahieren hat (Listing 2).

Listing 2

private async void analyzeFile(Windows.Storage.StorageFile _aFile) {
  List<VisualFeatureTypes?> features = new List<VisualFeatureTypes?>() {
    VisualFeatureTypes.Categories,
    VisualFeatureTypes.Description,
    VisualFeatureTypes.Objects
  };

Microsoft erlaubt Entwickler:innen das Deselektieren nicht benötigter Features, um Rechenleistung einzusparen und geringere Kosten zu verursachen. Für unser eher kleines Beispiel wollen wir uns auf die gezeigten drei Features beschränken. Wer mehr Funktionen nutzen möchte, übergibt einfach mehr der in VisualFeatureTypes angelegten Felder.

Die von Microsoft vorgegebenen ML-Beispiele arbeiten meistens mit im Internet frei ansprechbaren Bildern. Da wir hier allerdings ein im Dateisystem befindliches Bild verarbeiten, müssen wir dieses in ersten Schritt in einen Readable Stream umwandeln:

  var aStream = await _aFile.OpenStreamForReadAsync();
  using (Stream analyzeImageStream = aStream)
  {
    StringWriter myW = new StringWriter();
    Console.SetOut(myW);

Die von den Azure Cognitive Services zurückgegebenen Analyseergebnisse präsentabel zu machen, ist in vielen Fällen eine Wissenschaft für sich. Microsoft bietet in den Codebeispielen allerdings umfangreichen Analysecode an, der (leider) immer auf die Methode Console.WriteLine zurückgreift, um die Analyseergebnisse nach außen zu tragen.

Ein eleganter Weg, um das Abernten der in die Konsole ausgegebenen Strings zu ermöglichen, ist die Nutzung der Methode SetOut. Sie erlaubt, wie hier gezeigt, das Übergeben eines beliebigen StringWriter, der dann die diversen an Write und WriteLine übergebenen Strings aufnimmt und für die spätere Weiterverarbeitung vorhalten kann.

Im nächsten Schritt folgt bei uns jedenfalls ein Aufruf der Methode AnalyzeImageInStreamAsync, die sich um die Übertragung des Bildes in Richtung des Azure-Backends und um das Abernten der ML-Ergebnisse kümmert:

    ImageAnalysis results = await myClient.AnalyzeImageInStreamAsync(analyzeImageStream, visualFeatures: features);

Microsoft zeigt sich im Bereich der Anforderungen an die übertragenen Bilder (Abb. 2) vergleichsweise kompromissbereit.

hanna_cognitive_2.tif_fmt1.jpgAbb. 2: Microsofts AI-Dienste sind im Bereich der Eingabedaten flexibel

Wichtig ist in diesem Zusammenhang, dass die Abarbeitung der AI-Prozesse je nach Belastung die eine oder andere Sekunde in Anspruch nimmt. Beginnen wir danach mit der Auswertung der im Description-Feld angelieferten Ergebnisse (Listing 3).

Listing 3

    if (null != results.Description && null != results.Description.Captions)
    {
      Console.WriteLine("Summary:");
      foreach (var caption in results.Description.Captions)
      {
        Console.WriteLine($"{caption.Text} with confidence {caption.Confidence}");
      }
      Console.WriteLine();
    }

Unter Description versteht Microsoft dabei textuelle Informationen, die den Inhalt des hochgeladenen Bildes mehr oder weniger genau zu beschreiben versuchen. Da ML-Algorithmen so gut wie immer nur Prognosen treffen, liefert Microsoft in caption.Confidence außerdem noch immer einen numerischen Wert zurück, der über die Sicherheit des Zutreffens der jeweiligen Prognose informiert.

 

Im nächsten Schritt werten wir außerdem noch die Ergebnisse der Objektanalyse aus. Dabei handelt es sich um einen ML-Algorithmus, der das Bild als Ganzes ansieht und versucht, zu bestimmen, welche Regionen des jeweiligen Bildes von welchem Objekt berührt werden (Listing 4).

Listing 4

    if (null != results.Objects)
    {
      Console.WriteLine("Objects:");
      foreach (var obj in results.Objects)
      {
        Console.WriteLine($"{obj.ObjectProperty} with confidence {obj.Confidence} at location {obj.Rectangle.X}, " + $"{obj.Rectangle.X + obj.Rectangle.W}, {obj.Rectangle.Y}, {obj.Rectangle.Y + obj.Rectangle.H}");
      }
      Console.WriteLine();
    }
 
    TxtOut.Text = myW.ToString();
  }
}

Für die Ausgabe der von den Azure Cognitive Services errechneten Ergebnissen fügt der Autor in der Datei MainPage.xaml noch eine Textbox hinzu, die die im StringWriter-Objekt angelieferten Informationen auf den Bildschirm bringt. Mit zwei auf der Intertabac in Dortmund geschossenen Bildern präsentiert sich das Programm dann wie in Abbildung 3 und 4 gezeigt.

hanna_cognitive_3.tif_fmt1.jpgAbb. 3: Sowohl die Landewyck-Managerin …
hanna_cognitive_4.tif_fmt1.jpg
Abb. 4: … als auch die Zigarrenkiste werden problemlos erkannt

 

 

Sprachverarbeitung

Die Cognitive Services sind nicht auf die Analyse von Bitmaps und anderen Bildmaterialien beschränkt. Spätestens seitdem Microsoft das hinter Dragon stehenden Unternehmen Nuance aufgekauft hat, ist klar, dass man auch im Bereich von Sprachsamples Aktivitäten plant.

Zum Zeitpunkt der Drucklegung sind diese teilweise noch nicht für die Allgemeinheit freigegeben. Wer beispielsweise die Sprechererkennung der Cognitive Services nutzen möchte, muss Microsoft noch explizit um Freischaltung des Accounts ersuchen.

Als nächste Aufgabe wollen wir uns deshalb der Sprachsynthese zuwenden. Microsoft versteht darunter das Anliefern eines Texts an die Cognitive Services, die daraufhin eine wiedergebbare Tondatei liefern. Analog zu Dragon unterstützt Microsoft auch das Umwandeln von Sprache in Text. Das ist allerdings eine Aufgabe, der wir uns jetzt nicht zuwenden wollen.

Für die eigentliche Nutzung wollen wir ein weiteres Beispiel erzeugen, das abermals auf Basis der Vorlage Leere App (Universelle Windows-App) entsteht. Der Projektname lautet nun aber SUSCogni2. Anschließend öffnen wir die NuGet-Paketverwaltung, in der wir nun aber nach dem Paket Microsoft.CognitiveServices.Speech suchen. Installieren Sie es in einer aktuellen Version, und wechseln Sie danach in die Codedatei. Da das Windows-Betriebssystem selbst auch eine (funktionsreduzierte) TTS-Implementierung mitbringt, ist es an dieser Stelle ratsam, über eine Using-Deklaration die Nutzung der Azure Cognitive Services dateiweit vorzuschreiben:

using Microsoft.CognitiveServices.Speech;

Im nächsten Schritt ist man versucht, nach folgendem Schema die von weiter oben bekannten Credentials abermals als Membervariable in der Page anzulegen:

public sealed partial class MainPage : Page {
  static string subscriptionKey = "d. . .";
  static string endpoint = "https://susexperiment.cognitiveservices.azure.com/";
  SpeechSynthesizer mySynth;

Wer diese Anweisungen befolgt, ist allerdings auf der falschen Fährte. Ärgerlicherweise gibt es im Speech SDK mit der Methode FromEndpoint sogar eine Funktion, die das Vorhandensein einer Authentifizierungsmöglichkeit auf Basis von Endpoint und SubscriptionKey andeutet:

public MainPage() {
  this.InitializeComponent();
  var speechConfig = SpeechConfig.FromEndpoint(new Uri(endpoint), subscriptionKey);
  mySynth = new SpeechSynthesizer(speechConfig);

Das Nutzen dieses Codes führt zwar zu einem kompilierbaren Programm, zur Laufzeit wird der Azure-Server die Anfragen allerdings durch die Bank mit dem Fehlercode 404 ablehnen.

Eine funktionsfähige Implementierung erhalten Sie nur, wenn Sie nach folgendem Schema erstens einen Subscription Key und zweitens die Locale, in der Ihre Ressource angemeldet ist, übergeben:

public MainPage() {
  this.InitializeComponent();
  var speechConfig = SpeechConfig.FromSubscription(subscriptionKey, "eastus");

Im nächsten Schritt werden wir in die XAML-Datei wechseln, wo wir eine Listbox mit dem Namen LstVoices anlegen. Danach benötigen wir eine Methode, die eine Liste aller bei den Cognitive Services bekannten Sprachmodelle beantragt. Eine einfache Implementierung, die Sie direkt aus dem Konstruktor der Page aufrufen können, sieht aus wie in Listing 5.

Listing 5

async void voiceFindWorker() { 
  var myVoices = await mySynth.GetVoicesAsync();
  foreach (VoiceInfo aV in myVoices.Voices) {
    LstVoices.Items.Add(aV.Name + " " + aV.LocalName);
  }
  
}

Das von GetVoicesAsync zurückgegebene VoiceInfo-Objekt ist in Bezug auf die angelieferten Informationen eher umfangreich. Für einen ersten Versuch beschränken wir uns daher darauf, die Ergebniswerte Name und LocalName zurückzugeben. Lohn der Programmausführung ist dann das in Abbildung 5 gezeigte Ergebnis.

hanna_cognitive_5.tif_fmt1.jpgAbb. 5: In der Stimmen-Listbox wimmelt es

Im nächsten Schritt wollen wir dafür sorgen, dass unser Programm den gerade eingegebenen Text in der Sprache ausgibt, die der Benutzer in der Listbox auswählt.

Hierzu müssen wir in Schritt eins nach folgendem Schema eine Anpassung des Aufenthaltsorts der SpeechConfig-Variablen durchführen:

public sealed partial class MainPage : Page {
  . . .
  SpeechConfig mySpeechConfig;

Das API-Design des Speech API unterscheidet sich insofern vom bisher vorgestellten, als die Synthesizer-Instanz hier nicht nur die Credentials, sondern auch die jeweils zu verwendende Sprache enthält. Eine Änderung der Sprache setzt also ein Verwerfen des Synthesizer-Objekts voraus, das danach aus der SpeechConfig-Variablen neu ins Leben gerufen werden muss.

Für das eigentliche Bevölkern der SpeechConfig-Variablen benötigen wir dann lediglich den Wert von Name. Diesen speichern wir wie folgt in der Listbox:

foreach (VoiceInfo aV in myVoices.Voices)
{
  LstVoices.Items.Add(aV.Name);
}

Im nächsten Schritt können wir auch schon das eigentliche Sprechen befehligen (Listing 6).

Listing 6

private void TxtTalk_Click(object sender, RoutedEventArgs e)
{
  mySpeechConfig.SpeechSynthesisVoiceName = LstVoices.SelectedItem.ToString();
  mySynth = new SpeechSynthesizer(mySpeechConfig);
  mySynth.SpeakTextAsync(TxtWhat.Text);
}

Im Interesse der Bequemlichkeit schreibt der Autor im ersten Schritt den in der Listbox gewählten Wert in das Attribut SpeechSynthesisVoiceName, um daraufhin einen neuen SpeechSynthesizer zu erzeugen. Der Aufruf der Methode SpeakTextAsync animiert diesen dann zum Sprechen. Die Ausgabe erfolgt bei einem nicht parametrierten Aufruf direkt über das jeweilige Standardaudioausgabegerät des Hostcomputers.

An dieser Stelle ist unser Programm zur Ausführung bereit. Geben Sie einen beliebigen Text in die Textbox ein und aktivieren Sie den Knopf, um sich an der Sprachausgabe zu erfreuen. Wer einen deutschen Text eingeben möchte und beispielsweise eine englische oder französische Sprache wählt, stellt sofort fest, dass Microsoft die Intonierung umfangreich an die jeweiligen Akzente und Landessprachen angepasst hat.

Was wurde aus der Emotionserkennung?

Normalerweise ist es nur schwer vorstellbar, einen Cognitive-Services-Artikel zu verfassen, der die Emotionserkennung anhand von Gesichtsbildern nicht nutzt. Das ist allerdings gar nicht mehr möglich: Microsoft entschied im Juli 2022 nämlich, die diesbezüglichen Funktionen für neu erzeugte Azure-Cognitive-Services-Anmeldungen nicht mehr freizuschalten und sie vorhandenen Nutzern im Laufe von 2023 wegzunehmen.

Hintergrund dieser Entscheidung ist, so Microsoft, dass die Emotionserkennung nicht besonders zuverlässig funktioniert. Das ist übrigens eine Erkenntnis, die der (im Allgemeinen konservativ eingestellte) Autor aus seiner persönlichen Lebenserfahrung bestätigen kann. Gesichtsregungen bzw. die muskulären Bewegungen des Gesichts mögen zwar bei allen Personen identisch sein. Die Bedeutung oder dahinterstehende Emotionen sind allerdings auch vor dem Hintergrund von zum Beispiel Gesundheitszustand und der kulturellen Erziehung zu bewerten.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Lokales Hosting der AI

Microsoft sieht die AI-Funktionen, wie in der Einleitung des Artikels festgestellt, naturgemäß auch als Mittel zum Erwirtschaften einer Recurring Revenue. Da das Übertragen von höchstpersönlichen Lebensdaten in Zeiten von GDPR und Co. allerdings ein rechtlich durchaus schwieriges Thema ist, bietet Microsoft seit einiger Zeit auch die Möglichkeit an, die Inferenz on Premise durchzuführen.

Schon in der Einleitung sei allerdings darauf hingewiesen, dass es sich dabei um kein Mittel zur Kostenreduktion handelt: Eine lokal durchgeführte Inferenz wird von Microsoft genauso verrechnet, wie wenn die Inferenz unter Nutzung des Azure-Cloud-Backends und der dort befindlichen Rechenressourcen erfolgen würde.

Als Werkzeug der Wahl hat sich Microsoft dabei für Docker-Container entschieden: Das lokale Hosting der Intelligenz erfolgt also durch Ausführung von Docker-Containern. Microsoft empfiehlt dabei die Nutzung von Azure Kubernetes oder Docker Compose, geht aber auch auf andere Laufzeitumgebungen ein.

Wichtig ist in diesem Zusammenhang außerdem, dass ein einmal aktivierter Container (normalerweise) permanent nach Hause telefoniert. Microsoft macht das in den FAQs (Abb. 6) an mehreren Stellen explizit klar.

hanna_cognitive_6.tif_fmt1.jpg
Abb. 6: Ein On-Premise-Container muss permanent Informationen an den Zentralserver liefern

Davon ausgenommen sind nur Großunternehmen, die mit Microsoft einen explizit anderslautenden Vertrag abschließen. In diesem Fall kann auch ein komplett offline arbeitender Container zur Verfügung gestellt werden. Das ist allerdings teuer, Microsoft berechnet ihn nämlich wie eine fix provisionierte Instanz.

An dieser Stelle sollte noch erwähnt werden, dass Microsoft nicht alle in den Cognitive Services zur Verfügung stehenden Dienste auch als Docker-Container anbietet. Zum Zeitpunkt der Drucklegung werden vor allem die folgenden Dienste angeboten:

  • Anomaly Detector

  • Computer Vision

  • Face

  • Form Recognizer

  • Language Understanding (LUIS)

  • Speech Service API

  • Language Service – Sentiment Analysis

  • Language Service – Text Analytics for Health

  • Language Service – Language Detection

  • Language Service – Key Phrase Extraction

Fazit und Ausblick

Die Nutzung der Cognitive Services ermöglicht Entwickler:innen das schnelle und unbürokratische Realisieren verschiedener Aufgaben der AI, die ansonsten sehr zeitintensiv sein können. Insbesondere das erfolgreiche Parametrieren eines AI-Modells ist eine Aufgabe, die man erfahrungsgemäß nur allzu leicht unterschätzt. In diesem Sinne: Gut AI!

The post Eine Einführung in die Arbeit mit Azure Cognitive Services appeared first on BASTA!.

]]>
Angulars neues Standalone API https://basta.net/blog/angulars-neues-standalone-api/ Tue, 07 Feb 2023 10:03:32 +0000 https://basta.net/?p=91201 In der Vergangenheit führte die Nutzung der Angular-Module oftmals zu Problemen bei der Featureentwicklung des Angular-Frameworks. Aber es gibt eine Alternative: Standalone Components. Wie die Angular-Entwicklung nun ohne Angular-Module aussehen kann und was das für eine bestehende Architektur bedeutet, erfahren Sie hier.

The post Angulars neues Standalone API appeared first on BASTA!.

]]>
Möchte man heutzutage eine Webseite entwickeln, kommt man um die drei großen JavaScript-Frameworks React, Vue und Angular nicht herum. Viele Faktoren entscheiden hierbei über die Wahl des Frameworks. Neben den offensichtlichen Faktoren Dokumentation, Verbreitung und Features ist ebenso wichtig, dass der Einstieg in die Entwicklung mit dem Framework leicht und schnell zu schaffen ist. Gerade die Höhe der Einstiegshürde wurde an Angular oft kritisiert.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

So verlangt Angular beispielsweise die Implementierung eines Angular-Moduls. Innerhalb eines Angular-Moduls werden Angular-Bausteine (z. B. Komponenten) deklariert oder es werden andere Module importiert, deren Funktionalität benötigt wird. Am meisten für Verwirrung sorgt hierbei die Unterscheidung zwischen Angular- und ECMAScript-Modul. Gerade Anfänger:innen, die soeben erst Grundkenntnisse in JavaScript bzw. TypeScript erlangt haben, ist meist nicht klar, wozu ein Angular-Modul benötigt wird.

Selbst langjährige Angular-Entwickler:innen wünschen sich immer wieder einen Weg, die Verwendung von Angular-Modulen optional zu gestalten. Zu viel Boilerplate-Code und die Verleitung zur Entwicklung von zu großen sogenannten Shared-Modulen sind hierbei zwei der Hauptgründe dafür, dass sich die Entwicklung mit Angular-Modulen oft schwierig gestaltet.

Abhilfe hat man sich hierbei durch die Implementierung des Single-Component-Angular-Module- oder auch SCAM-Patterns geschaffen. Dabei wird jede einzelne Komponente, Direktive oder Angular Pipe in einem einzigen alleinstehenden Angular-Modul deklariert. Importiert werden lediglich die Abhängigkeiten, die die einzelne Komponente, Pipe oder Direktive benötigt (Abb. 1).

Abb. 1: SCAM-Pattern: Ein Modul deklariert nur eine einzige Komponente

Dadurch können größere Shared-Module aufgespalten und die Abhängigkeiten innerhalb der eigenen Featuremodule weitaus feingranularer importiert werden (Abb. 2).

Abb. 2: Aktuelle Angular-Version 15.0.0-rc4

So ist beispielsweise die Angular-Material-Bibliothek [1] eine der bekanntesten Umsetzungen dieses Patterns. Für jede UI-Komponente gibt es ein eigenes Modul: MatButtonModul, MatDatePickerModule oder MatTableModule, um nur einige Beispiele zu nennen.

Schnell fragt man sich, warum der Mehraufwand eines Moduls überhaupt notwendig ist, und so sah man mit großer Freude, dass im Oktober 2021 ein RFC [2] des Angular-Teams veröffentlicht wurde, in dem es hieß, dass Angular-Module mit Hilfe des sogenannten Standalone API von nun an optional sein sollen.

Das Angular-Team erwähnte selbst, dass die verpflichtende Nutzung der Angular-Module oftmals zu Problemen bei der Featureentwicklung des Angular-Frameworks beitrug. Doch warum gab es Angular-Module dann überhaupt? Wie sieht die Angular-Entwicklung nun ohne Angular-Module aus und was bedeutet das für eine bestehende Architektur?

Genau diese Fragen möchte ich hier beantworten. Nachdem wir uns mit dem neuen Standalone API vertraut gemacht haben, werden wir tiefer einsteigen in die neue Architektur und in die Entwicklung mit Standalone Components.

Hinweis: Der Text arbeitet mit Angular Version 15.0.0-rc.4. Bis zur Veröffentlichung ist Angular Version 15 bereits erschienen. Daher könnte es kleinere Abweichungen innerhalb der gezeigten Codebeispiele geben.

Warum gibt es überhaupt Angular-Module?

Die Antwort auf diese Frage führt uns zu den Wurzeln von Angular: 2010 wurde AngularJS erstmals releast. Zu dieser Zeit gab es keinerlei Möglichkeit, Code, der zusammengehörte, zu gruppieren. Das Gruppieren aber führt nicht nur zu einer besseren Wartbarkeit des Codes, sondern ermöglicht, dass Funktionalität, die an mehreren Stellen benötigt wird, wiederverwendet werden kann. Für Letzteres benötigt der Compiler einen Kontext, um die Sichtbarkeit der einzelnen Codebausteine zu erkennen. Da es zu dieser Zeit noch kein eigenes Modulkonzept in JavaScript gab, wurden in AngularJS hierfür Angular-Module eingeführt (Listing 1).

Listing 1

angular.module('myModule', []).
  value('a', 123).
  factory('a', function() { return 123; }).
  directive('directiveName', ...).
  filter('filterName', ...);

Innerhalb eines Angular-Moduls deklarierte man unter anderem Direktiven, Controller oder Services. Mit der Veröffentlichung von Angular im Jahre 2016 wurden zwar die meisten Bausteine des Frameworks neu geschrieben, allerdings wurden Teile des alten Compilers übernommen. Das führte dazu, dass Angular weiterhin darauf angewiesen war, mit Angular-Modulen zu arbeiten (Listing 2).

Listing 2

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}


Tatsächlich war die Angular-Community nie wirklich glücklich mit dieser Entscheidung und auch aus dem Angular-Team hörte man immer wieder Stimmen der Unzufriedenheit. Denn mit der Einführung von ECMAScript 2015 wurde nun das Konzept der ECMAScript-Module [3] vorgestellt. Das heißt, JavaScript hatte von nun an einen gewissermaßen natürlichen Weg, um Code zu modularisieren.

SIE WOLLEN MEHR INPUT ZUM THEMA WEB DEVELOPMENT?

Entdecken Sie den BASTA! Web Development Track

Die Kombination aus beiden Modulkonzepten in einem Framework verwirrte besonders Neueinsteiger:innen und so arbeitete das Angular-Team an der Entwicklung eines neuen Compilers: des Ivy-Compilers. Der bereits in Angular Version 8 eingeführte Compiler ermöglichte nun, dass jede Komponente ihren ganz eigenen Compilerkontext hatte. Ein Angular-Modul wird also nicht mehr benötigt. Der Rückwärtskompatibilität wegen musste das Angular-Team aber noch lange Zeit mit dem älteren Compiler, der View Engine, arbeiten und war vorerst weiterhin auf die Nutzung der Angular-Module angewiesen. Doch mit dem Entfernen der View Engine seit Angular 13 war es endlich so weit: Angular-Module waren für die Entwicklung mit Angular nicht mehr notwendig und das neue Standalone API wurde als sogenannte Developer Preview [4] in Version 14 vorgestellt.

Standalone Components

Das darunterliegende Konzept ist relativ einfach und schnell erklärt: Von nun an werden sämtliche Abhängigkeiten, die eine Komponente benötigt, direkt in das neue imports-Property innerhalb des Component-Decorators geschrieben (Listing 3).

Listing 3

@Component({
  selector: 'app-root',
  standalone: true,
  template: '<h1 *ngIf="title">{{ title }}</h1>>',
  imports: [ NgIf ],
  styleUrls: [ './app.component.scss' ],
})
export class AppComponent {...}

 

Darüber hinaus wird noch ein neues Property standalone gesetzt, um dem Compiler klarzumachen, dass es sich hierbei um eine Standalone Component handelt.

Was uns ebenso auffällt, ist, dass für die Verwendung der NgIf-Direktive nicht mehr das gesamte Common Module importiert werden muss, sondern lediglich die strukturelle Direktive selbst.

Das Angular-Team hat zusätzlich daran gearbeitet, die Bausteine ihrer eigenen Module als Standalone zur Verfügung zu stellen. Sie können dann, wie in Listing 3 gezeigt, innerhalb des imports-Property eingefügt werden. So gibt es beispielsweise die DatePipeAsyncPipeNgForOf oder das bereits gezeigte NgIf aus dem Common Module als Standalone-Variante, aber auch das für die Navigation benötigte RouterOutlet oder die RouterLink-Direktive aus dem Router Module. Doch was ist, wenn wir eine weitere Angular-Komponente erstellen möchten und sie in unsere AppComponent einfügen wollen? Normalerweise konnten wir die Komponente innerhalb desselben Angular-Moduls deklarieren, in dem die AppComponent deklariert war. So wurde sichergestellt, dass die neue Komponente für die AppComponent sichtbar ist. Mit Standalone Components sieht das jetzt natürlich ein wenig anders aus.

 

Zuerst legen wir eine neue Komponente an:

> ng generate component header –standalone

Setzen wir das neue –standalone-Flag, wird eine Komponente generiert, die bereits das standalone-Property gesetzt hat.

Fügen wir die HeaderComponent nun in unsere AppComponent ein, erhalten wir vorerst die uns vertraute Fehlermeldung aus Abbildung 3.

Abb. 3: Die AppComponent kennt noch nicht die HeaderComponent

Interessanterweise wird uns allerdings vorgeschlagen, die HeaderComponent zu importieren. Tun wir das, so können wir sehen, dass die HeaderComponent nun ebenfalls innerhalb des imports-Arrays der AppComponent eingefügt wurde (Listing 4).

Listing 4

@Component({
  selector: 'app-root',
  standalone: true,
  template: '<h1 *ngIf="title">{{ title }}</h1>>',
  imports: [ NgIf, HeaderComponent ],
  styleUrls: [ './app.component.scss' ],
})
export class AppComponent {...}

Daraus folgt, dass Standalone Components sich ähnlich wie Angular-Module verhalten. Benötige ich eine Standalone Component in einer anderen Komponente, muss ich sie lediglich importieren. Voraussetzung ist hierbei, dass beide Komponenten das standalone-Property besitzen.

Wir können darüber hinaus nicht nur Komponenten als standalone deklarieren: Der Pipe- und der Directive-Decorator besitzen ebenfalls die neue standalone-Property.

Die Vorstellung, alle abhängigen Komponenten, Pipes, Direktiven etc. immer wieder in unsere Standalone Component zu importieren, klingt zunächst anstrengend. Zwar müssen wir das zurzeit noch manuell mit ein wenig Hilfe unserer Entwicklungsumgebung erledigen, doch ist geplant, dass in naher Zukunft anhand eines Autoimports zu lösen – ähnlich wie bereits TypeScript-Module in unsere Angular-Anwendung automatisch importiert werden, sobald sie in einem Code genutzt werden sollen.

Da wir aus unserer AppComponent eine Standalone Component kreiert haben, stellt sich uns nun die Frage, wie unsere gesamte Applikation eigentlich gebootstrappt wird, da wir das AppModule nicht mehr benötigen.

Bootstrapping einer Angular-Applikation

Bisher benötigte Angular immer ein sogenanntes AppModule, anhand dessen die gesamte Applikation mit Hilfe der bootstrapModule-Funktion gebootstrappt wurde. Innerhalb des AppModules definierten wir darüber hinaus unsere AppComponent als sogenannte Root-Komponente (Listing 5).

Listing 5

//main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
 
platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
Listing 6

//main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component;
 
bootstrapApplication(AppComponent)
  .catch(err => console.error(err));

Mit dem neuen Standalone API können wir eine einzelne Komponente bootstrappen (Listing 6). Dafür stellt uns Angular seit Version 14 die bootstrapApplication-Funktion zur Verfügung. Dort übergeben wir nur noch unsere Root-Komponente, die allerdings bereits als Standalone Component vorliegen muss.

Wie wir sehen, wird das AppModule auch hier nicht mehr benötigt. Möchten wir allerdings Services für die gesamte Applikation providen, stand uns hierfür immer das providers-Array innerhalb des NgModules-Decorators zur Verfügung. Gewiss sind treeshakeable Providers – das heißt, der Service provided sich selbst – immer zu bevorzugen, allerdings benötigen wir oftmals komplexere Dependency-Injection-Logik, die wir dann innerhalb des providers-Arrays klarer definieren können. Ein einfaches Beispiel hierfür sind Injection Tokens wie der APP_INITIALIZER. Aber auch hierfür hat das neue Standalone API eine Lösung: Sämtliche Provider werden von nun an innerhalb der bootstrapApplication-Funktion deklariert.

 

Neben der Root Component wird eine sogenannte ApplicationConfig mitübergeben. Dort können wir – mit kleinen Anpassungen – weiterhin das bekannte providers-Array definieren, um unserer Applikation Injection Tokens und Services zur Verfügung zu stellen.

Neben unseren eigenen Services wollen wir oftmals andere Module importieren, die dann ihrerseits Services anbieten. Einige der bekanntesten Vertreter sind hierbei das RouterModule, HttpClientModule oder auch das StoreModule aus der Redux-Bibliothek NgRx. Mit Hilfe der neuen importProvidersFrom-Funktion werden diese Services uns weiterhin zur Verfügung gestellt, indem wir einen oder mehrere ModuleWithProviders als Parameter übergeben (Listing 7).

Listing 7

//main.ts
bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(StoreModule.forRoot({...})),
    {
      provide: Window,
      useValue: window,
    },
  ],
})

Das Beste daran ist, dass hierbei wirklich nur die Provider des jeweiligen Moduls importiert werden. Übergibt man beispielsweise das RouterModule, wird nicht der gesamte Modulcode mit allen Direktiven importiert, sondern eben nur Services, zum Beispiel der RouterService.

Routing und Lazy Loading einer Angular-Applikation

Das nächste große Thema, mit dem wir uns beschäftigen müssen, ist die neue Art und Weise, wie wir unsere Routen innerhalb der Applikation registrieren. Bisher haben wir uns hierfür ein AppRoutingModule angelegt und die Routenkonfiguration innerhalb der forRoot-Funktion des Routing-Moduls übergeben.

Listing 8

//routes.ts
export const APP_ROUTES: Routes = [
  {
    path: '',
    redirectTo: '/books',
    pathMatch: 'full',
  },
  {
    path: 'books',
    component: BookComponent,
  },
]

Das Konzept mit dem Standalone API ist tatsächlich sehr ähnlich. Zuerst definieren wir unsere Routen in einer separaten Datei, ohne jedoch hierfür ein Extramodul anzulegen (Listing 8). Hierbei exportieren wir lediglich die APP_ROUTES-Konstante. Für die Registrierung dieser Routen bietet uns das Standalone API eine provideRouter-Funktion an. Diese müssen wir innerhalb des neuen providers-Arrays in unserer main.ts aufrufen (Listing 9).

Listing 9

// main.ts
bootstrapApplication(AppComponent, {
  providers: [
  ...
 
    provideRouter(APP_ROUTES),
  ]
}

Die provideRouter-Funktion ist hierbei lediglich eine spezielle Variante der importsProvidersFrom-Funktion. Tatsächlich ist es sogar so, dass in Version 14 die Routen noch wie folgt registriert werden mussten:

importProvidersFrom(RouterModule.forRoot(APP_ROUTES))

Während das eine sehr generelle Art und Weise ist, Services bereitzustellen, ist es denkbar, dass in Zukunft mehrere solcher spezieller Provider-Funktionen zur Verfügung stehen. StoreModule.forRoot({}) könnte beispielsweise in Zukunft lediglich eine provideStore-Funktion sein. Auch für den HttpClient gibt es bereits eine eigene Variante, dazu aber später mehr.

Wie bereits erwähnt, werden an dieser Stelle lediglich die Services des Moduls importiert. Um die Navigation vollständig zu implementieren, wird noch das RouterOutlet benötigt sowie die RouterLink-Direktive. Auch sie werden als Standalone-Bausteine angeboten, sodass wir sie lediglich innerhalb der AppComponent oder jeder anderen Komponente, in der wir die Direktiven nutzen wollen, importieren (Listing 10).

Listing 10

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RouterOutlet,
    RouterLink,
  ],
  ...
})
export class AppComponent {}

Nachdem wir verstanden haben, wie wir mit dem neuen Standalone API Routen registrieren können, stellt sich direkt die Frage nach Lazy Loading. Lazy Loading gilt als eine der Best Practices, wenn es darum geht, die Größe des initialen JavaScript Bundles zu verringern. Hierbei wird die gesamte Applikation in sogenannte Featuremodule aufgespalten, die nur dann nachgeladen werden, wenn sie benötigt werden, also wenn der Nutzer in eine Ansicht wechselt, die den Code eines Featuremoduls zur Anzeige benötigt. Alle Komponenten, die wir dazu brauchen, sind in diesem Featuremodul deklariert. Wie funktioniert nun also das Prinzip des Lazy Loading ohne Module?

Prämisse ist wie immer, dass sämtliche Komponenten, die als Bundle dynamisch nachgeladen werden sollen, als Standalone Components vorliegen. Zuerst legen wir eine Konfigurationsdatei an, in der wir die Kindrouten definieren (Listing 11). Diese exportieren wir ebenfalls. Der Trick ist nun, dass wir nur diese Routen lazy loaden anstatt eines ganzen Moduls. Das klingt erst einmal seltsam, aber sehen wir uns das Ganze einmal an (Listing 12).

Listing 11

//books.routes.ts
export const BOOK_ROUTES: Routes = [
{
path: '',
component: BookComponent,
},
{
path: 'new',
component: BookNewComponent,
},
{
path: ':id',
component: BookDetailComponent,
},
]
Listing 12

//routes.ts
export const APP_ROUTES: Routes = [
…
{
path: 'books',
loadChildren: () => import('./features/book/routes')
.then(m => m.BOOK_ROUTES),
},
];

Der Angular-Compiler versteht, dass wir alle Komponenten innerhalb der BOOK_ROUTES gemeinsam als Bundle ausliefern wollen. Sobald der Nutzer nun eine Route besucht, die innerhalb der BOOK_ROUTES definiert ist, wird das gesamte Bundle nachgeladen, wie wir es auch vom Lazy Loading von Modulen kennen (Abb. 4).

Abb. 4: Das BOOK_ROUTES Bundle wird dynamisch nachgeladen

Vielleicht kommt nun die Frage auf, wie wir in Zukunft unsere Ordner innerhalb der Anwendung strukturieren sollen. Das Anlegen eines Moduls hat uns standardmäßig einen Ordner angelegt – dieser fehlt uns jetzt. Dennoch empfehle ich, Komponenten, die zusammengehören beziehungsweise zusammen ausgeliefert werden, innerhalb eines Ordners zu gruppieren (Abb. 5). So behält man weiterhin die Übersicht, welche Komponenten zu einem Feature gehören, auch wenn sie nicht mehr über ein Featuremodul zusammen deklariert werden.

Abb. 5: Ordner für Komponenten-Bundles

Bevor ich zeige, wie wir einzelne Komponenten lazy loaden können, möchte ich noch einmal kurz auf die Services zurückkommen. Wir haben zwar bereits gelernt, wie wir Services für die gesamte Applikation providen können, jedoch nicht, wie wir einen Service nur für die Komponenten eines spezifischen Features bereitstellen. Hier konnten wir immer auf das providers-Array des zugehörigen Featuremoduls zurückgreifen. Dieses providers-Array ist nun Teil der Routenkonfiguration (Abb. 6).

Abb. 6: Das neue Route-Interface

Listing 13

//routes.ts
export const APP_ROUTES: Routes = [
  ...
  {
    path: 'books',
    providers: [BookApiService]
    loadChildren: () => import('./features/book/routes')
      .then(m => m.BOOK_ROUTES),
  },
];

Soll nun also ein Service nur für die Komponenten der BOOK_ROUTES bereitgestellt werden, kann er direkt innerhalb der Routenkonfiguration an die Route geschrieben werden (Listing 13). Der BookApiService kann daraufhin nur von Komponenten innerhalb der books-Route injiziert werden.

Ein neues und ebenfalls lang ersehntes Feature ist das Lazy Loading einzelner Komponenten. Praktisch war es bereits in vorherigen Angular-Versionen möglich. Die tatsächliche Implementierung gestaltete sich allerdings oftmals sehr umständlich und enthielt viel Boilerplate-Code. Seit Version 14 können wir Komponenten ganz einfach innerhalb der Routenkonfiguration dynamisch nachladen (Listing 14). Wie zu vermuten, wird beim Aktivieren der books-Route die BookComponent dynamisch nachgeladen (Abb. 7).

Listing 14

export const APP_ROUTES: Routes = [
  ...
  {
    path: 'books',
    loadComponent: () => import('./features/book/book/book.component')
      .then(m => m.BookComponent),
  }
];

Abb. 7: Die BookComponent wird dynamisch nachgeladen

Wie wir gesehen haben, können wir innerhalb unserer Angular-Applikation auch vollständig ohne Angular-Module navigieren. Selbst das Lazy Loading von Bundles ist uns nicht abhandengekommen. Die Funktion provideRouter [5] bietet uns zusätzlich weitere Möglichkeiten zur Routerkonfiguration an. So können wir mit withPreloading(PreloadAllModules) eine Strategie festlegen, wann genau eine Komponente oder ein Bundle von Komponenten dynamisch nachgeladen werden soll. Sämtliche Optionen, die wir bisher innerhalb der forRoot-Funktion gesetzt haben, können von nun an innerhalb withRouterConfig übergeben werden.

Das neue HttpClient API

Als Teil des neuen Standalone API führt Angular 15 eine neue Methode zur Verwendung des HttpClient API ein, die keine HttpClientModule erfordert. Wie bereits erwähnt, erhalten wir auch für das Bereitstellen des HttpClient eine eigene Funktion (Listing 15). Innerhalb der Funktion provideHttpClient können zusätzliche Konfigurationen des HttpClient Service übergeben werden, beispielsweise der Support von JSONP (JSON mit Padding) oder Optionen für Cross-site Request Forgery (XSRF).

Listing 15

// main.ts
import { provideHttpClient, withJsonpSupport } from '@angular/common/http';
 
bootstrapApplication(AppComponent, {
  providers: [
    …
    provideHttpClient(withJsonpSupport()),
  ],
})

Die Verwendung des HttpClient empfiehlt sich immer, da er uns bei der Kommunikation mit einem HTTP-Backend einiges an Arbeit abnimmt. Er setzt automatisch application.json als MIME-Type und parst die Response-Daten in den angegebenen TypeScript-Datentyp. Darüber hinaus steht uns die Nutzung von sogenannten HTTP-Interceptors zur Verfügung. Wie der Name schon sagt, fangen sie den HTTP Request ab, bevor er an das Backend gesendet wird. Dadurch können wir beispielsweise bei allen HTTP Requests den authorization-Header setzen, ohne es bei allen Service-Aufrufen manuell implementieren zu müssen. Diese Interceptors müssen ebenfalls wieder innerhalb der bootstrapApplication mit provide bereitgestellt werden (Listing 16).

Listing 16

// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    ...
    { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
    provideHttpClient(withInterceptorsFromDi()),
  ],
})

Angular Inteceptors sind nichts Neues, ihre Registrierung innerhalb des Standalone API allerdings schon. Neben dem Bereitstellen des bereits bekannten Injection Token HTTP_INTERCEPTORS muss ebenfalls die Funktion withInterceptorsFromDi aufgerufen werden. Seit Angular 15 wird die Verwendung von Interceptors durch eine neue Featurefunktion namens withInterceptors() vereinfacht. Sie akzeptiert eine Liste von Interceptor-Funktionen, die wir verwenden möchten (Listing 17). Beide Varianten können auch gleichzeitig genutzt werden; wie zuvor ist lediglich die Reihenfolge der Registrierung der Interceptors wichtig.

Listing 17

// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    …
    provideHttpClient(withInterceptors([
      (req, next) => {
        return next(req);
      },
    ])),
  ],
})

Sicher ist die Entwicklung einer Angular-Appliktion mit dem neuen Standalone API für langjährige Angular-Entwickler:innen an einigen Stellen gewöhnungsbedürftig. Fängt man ein neues Angular-Projekt an, ist es allerdings sinnvoll, direkt auf Standalone Components zu setzen. Die Größe der Bundles, die an den Webbrowser ausgeliefert werden, kann sich dadurch stark verringern. Als Neueinsteiger:in in die Angular-Welt ist es darüber hinaus bei Weitem einfacher, nicht mehr mit zwei unterschiedlichen Modulkonzepten hantieren zu müssen: Es gibt nur noch ECMAScript-Module.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Bestehende große Anwendungen auf Standalone Components zu migrieren, mag sehr aufwendig erscheinen. Es empfiehlt sich daher zuerst, mit sämtlichen Bausteinen anzufangen, die wir innerhalb unserer Shared-Module deklariert haben. Ebenso lohnt es sich, simple UI-Komponenten bereits auf Standalone zu migrieren, also solche, die selbst keine weiteren Kindkomponenten oder größere Abhängigkeiten haben – traditionelle Dumb Components.

Ich hoffe, ich konnte Ihnen einen guten Einblick in die Entwicklung mit dem neuen Standalone API geben. Ein vollständiges Beispiel einer Standalone-Angular-Anwendung kann in meinem GitHub-Account eingesehen werden [6].

The post Angulars neues Standalone API appeared first on BASTA!.

]]>
Jahresrückblick 2022 https://basta.net/blog/jahresrueckblick-2022/ Wed, 28 Dec 2022 13:22:20 +0000 https://basta.net/?p=91058 Das Jahr 2022 neigt sich langsam dem Ende zu, es wird kälter und winterlicher. Nach zwei sehr anstrengenden Jahren, die von Pandemiebedingungen geprägt waren, ist 2022 wieder ein bisschen mehr Alltag eingekehrt. Wir haben einige unserer Expert:innen gefragt, was das vergangene Jahr für sie aus beruflicher Sicht interessant gemacht hat, was ihre persönlichen High- und Lowlights waren und welche Entwicklungen sie sich für die Zukunft wünschen. Die Antworten können Sie hier nachlesen.

The post Jahresrückblick 2022 appeared first on BASTA!.

]]>
Redaktion: 2022 ist wieder ein wenig Normalität eingekehrt. Viele Konferenzen finden hybrid oder vor Ort statt und im Büro sieht man die Kolleg:innen wieder: Was hat das Jahr 2022 für dich besonders gemacht?

Manfred Steyer: Das Besondere war, dass wir endlich wieder Vor-Ort-Konferenzen hatten. Workshops machen einige Kunden nach wie vor sehr gerne remote und das klappt auch wunderbar. Wir bekommen hierzu wirklich tolle Rückmeldungen. Konferenzen gehen hingegen über die reine Wissensvermittlung hinaus. Da geht es auch um den „Flur-Track“, bei dem man Leute trifft und sich austauscht.

Tam Hanna: Wegen unseres Standorts in Ungarn hatten wir mit dem Coronavirus keine Probleme. Die Lage war auch während der Pandemie normal. Für mich persönlich, ich bin Elektroniker, ist es trotzdem von Vorteil, dass viele Konferenzen hybrid stattfinden: Aus dem hauseigenen Labor und mit einer Zigarre lässt es sich bequemer zusehen.

Veikko Krypczyk: Es ist richtig, das Leben auf Konferenzen und Schulungen ist wieder zurückgekehrt. Ich habe 2022 an den Magdeburger Developer Days teilgenommen, einer sorgfältig organisierten Communitykonferenz, die den großen Entwicklerkonferenzen in Fragen des technischen Niveaus in Nichts nachsteht. Die hybride Arbeitswelt ist wohl gekommen, um zu bleiben, und das ist auch gut so. Damit kann man je nach Situation das Beste aus beiden Welten, das heißt präsent vor Ort und flexibel online, miteinander verbinden.

Martina Kraus: Mein ganz persönliches Highlight war die Organisation der Angular-Konferenz in Deutschland – die NG-DE. Nach drei Jahren konnten wir sie endlich erneut in vollem Umfang stattfinden lassen und hatten 500 Teilnehmer:innen. Es tat so gut, mal wieder Entwickler:innen und die deutsche Angular-Community vor Ort bei einer Konferenz begrüßen zu dürfen. Nichtsdestotrotz bin ich weiterhin Fan von Hybrid- und Onlinekonferenzen, einfach weil es mir als Speakerin die Möglichkeit gibt, auf einer Konferenz einen Talk zu halten, ganz ohne die ggf. lange Reisezeit.

Rainer Stropek: Eine der schönsten Erfahrungen des vergangenen Jahres war eine Konferenzrundreise im Sommer, bei der ich in einer Woche mehrere Vorträge in drei verschiedenen Ländern kombinieren konnte. Da war von .NET bis Rust alles dabei. Auch wenn ich den größten Teil meiner Vortrags- und Beratungstätigkeit über Webmeetings erledige, war es wieder einmal toll, physische Konferenzen zu erleben. Ich hoffe, dass das im nächsten Jahr vermehrt möglich sein wird.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Redaktion: Wie würdest du die vergangenen rund 12 Monate für die Microsoft-/JavaScript-/IT-Welt beurteilen? Welche Entwicklungen würdest du positiv einschätzen, welche negativ? Gab es für dich persönliche Highlights?

Steyer: In der Webwelt tauchen gerade ein paar neue interessante Frameworks auf. Beispiele sind Astro, qwik, SolidJS oder Marko. Diese Frameworks zeichnen sich unter anderem durch eine wunderbare Startperformance aus. JavaScript Bundles werden so spät wie möglich bzw. im Idealfall gar nicht geladen. Genau das ist für öffentliche Portale, bei denen es um Conversion geht, wichtig. Man merkt auch, dass Server und Client wieder mehr zusammenrücken und der Code auf beiden Seiten stärker aufeinander abgestimmt wird.

Hanna: Negativ schätze ich das ganze Dilemma um Windows 11 ein: Microsoft legt sich hier einen Badwill zu, der das gesamte Ökosystem in Zukunft viel kosten wird. Hierzu zwei Gedanken: Manche Länder haben die TPM-2.0-Frage durch ihre nationalen TPM-Verbote gelöst. Windows 11 läuft, wenn auch inoffiziell, auf Tausenden von Systemen wie meinem Lenovo T430 problemlos. Es stünde Microsoft gut an, hier eine Kehrtwende zu machen. Warum der im Allgemeinen pragmatische Satya Nadella das bisher nicht angeregt hat, ist für mich unverständlich. Die Probleme um den Android-Emulator können es nicht sein, weil man diesen analog zu Spielen nur auf kompatiblen Maschinen anbieten könnte.

Doch kommen wir zum Positiven. Neben der erfolgreichen Weiterentwicklung von Bryan Costanichs Meadow F7 gibt es mit dem neu aufgelegten .NET-Nano-Framework eine weitere Möglichkeit, um .NET-Technologien auf der letzten Meile zu verwenden. Besonders faszinierend finde ich in diesem Zusammenhang auch die Weiterentwicklung von IntelliSense in Visual Studio 2022. Stellenweise liefert das System geradezu grenzgeniale Vorschläge.

Krypczyk: Aus dem Hause Microsoft haben im Jahr 2022 die Technologien WinUI 3 und .NET MAUI das Licht der Welt erblickt. WinUI 3 ist angetreten, um die Möglichkeiten der Desktopanwendungen in grafischer Sicht deutlich zu verbessern, und ermöglicht frische und moderne Anwendungen. .NET MAUI wird Xamarin ablösen und macht es C#- und .NET-Entwickler:innen möglich, fast alle Plattformen zu erreichen.

Kraus: Dieses Jahr hat sich für mich wie ein Jahr mit vielen Veränderungen in den Technologien angefühlt: In Angular wurde das lang ersehnte Standalone API eingeführt und Reactive Forms endlich typisiert. Aber auch in der Microsoft-Welt hat sich mit dem Veröffentlichen von .NET Core 7 einiges bei der Performance und dem Minimal API getan. Blazor gewinnt immer mehr an Popularität – auch wenn der Einsatz noch recht umstritten ist. Mein persönliches Highlight ist aber tatsächlich Svelte. Gut – Svelte gibt es nun schon seit einiger Zeit, es ist also nicht wirklich neu. Allerdings hatte ich das Gefühl, dass es 2022 nochmal einen – vollkommen berechtigten – Popularitätssprung bei Svelte gegeben hat. Mit SvelteKit ist es mittlerweile auch sehr viel einfacher, eine Svelte-App aufzusetzen.

Stropek: Unspektakulär würde ich sagen, wobei das nichts Schlechtes ist. Die Microsoft-Produkte und -Technologien, die ich verwende, haben sich solide und stabil weiterentwickelt. Es gab keine weltbewegenden Neuerungen, aber eine Menge hilfreicher Ergänzungen. Von größerer Bedeutung sind aus meiner Sicht die Schritte, die Microsoft dieses Jahr mit .NET in Richtung WebAssembly gemacht hat. Diese Technologie wird aus meiner Sicht noch großen Einfluss auf .NET und die Azure-Cloud haben.

Das Tool, das im Jahr 2022 auf mich den größten Eindruck gemacht hat, ist GitHub Copilot, also AI-assisted Coding. Copilot ist noch weit davon entfernt, perfekt zu sein. Schon jetzt zeigt das Werkzeug aber, welches Potenzial AI für Coding hat und wie stark diese Kategorie von Coding-Werkzeugen unsere Arbeit in den nächsten Jahren prägen wird.

 


Redaktion: Das Jahr 2022 hat sowohl für Microsoft als auch für JavaScript einige Neuerungen und Ankündigungen gebracht, aber ein Blick nach vorne ist immer spannend. Was würdest du dir persönlich für die Zukunft wünschen? Wo besteht Verbesserungsbedarf beziehungsweise: Was fehlt dir?

Hanna: Meiner Meinung nach spielt Microsoft, ausgenommen mit Windows 11, eine sehr saubere Strategie. Wenn man nicht in übermäßigen Aktionismus verfällt, sehe ich keinen Grund, warum das nächste Jahr für das Microsoft-Ökosystem nicht exzellent ausfallen sollte.

Krypczyk: Ich denke, es ist Zeit, dass die neuen Technologien an Reife gewinnen. Anlaufschwierigkeiten, im Moment noch fehlende Features und der eine oder andere Bug müssen noch raus, dann haben wir eine neue und solide technische Basis für alle Zielsysteme.

Kraus: Ich bin immer ein großer Fan von Zusammenarbeit. Und auch wenn ich die neuesten Frameworks für das Frontend wie Svelte, SolidJS und Qwik superspannend finde, würde ich mir einfach wünschen, dass es weniger kleinere Bewegungen gibt. In der Frontend-Welt ist man sehr schnell überfordert mit den ganzen Technologien. Möchte man heutzutage einen JavaScript Builder bzw. Bundler einsetzen, gibt es neben dem Platzhirsch webpack mittlerweile auch noch esbuild, Snowpack, Vite und Turbopack – da blickt doch niemand mehr durch.

Stropek: Die Azure-Cloud wird immer erwachsener und damit einen Tick langweiliger. Für Großkunden ist das etwas sehr Positives. Dort spielen Dinge wie IT-Governance, Data Sovereignty und Multi-Cloud-Management eine große Rolle und ich finde es gut, dass Microsoft Azure einen Fokus darauflegt, um den Einsatz der Cloud in großen Organisationen voranzutreiben beziehungsweise ihn möglich zu machen.

Als Entwickler würde ich mir aber mehr grundlegendere Innovationen wünschen. Mehr Edge Cloud wie bei Cloudflare, mehr auf Entwickler:innen ausgerichtete Datendienste wie bei Firebase, bessere Azure APIs für Entwicklungsplattformen abseits von .NET wie bei Go oder Rust, solche Dinge wären schön. Ich sehe ein kleines bisschen die Gefahr, dass vor lauter Fokus auf den Bedarf von Enterprise Businesses die Entwicklungsinnovationen ins Hintertreffen geraten. Bisher hat Microsoft ganz gut eine Balance geschafft und ich hoffe, das bleibt so.

Redaktion: Ein Blick in die Glaskugel: Was wird uns das Jahr 2023 bringen? Auf was freust du dich?

Steyer: Es wird spannend sein, zu sehen, wie die etablierten JavaScript-Frameworks React, Angular und Vue, auf die vorhin erwähnten neuen Frameworks und deren Innovationen reagieren. Die haben ja die komfortable Situation, die Konzepte, die sich bewähren, zu übernehmen. React ist da schon sehr weit und Angular möchte bis Frühjahr 2023 nachziehen.

Hanna: Ich komme aus dem Hardwarebereich. Die Frage ist, wie sich weltpolitische Ereignisse auswirken. SMO, Handelskrieg mit China und Chipkrise haben dafür gesorgt, dass überall neue Halbleiterwerke entstehen. Das kann langfristig nicht gut gehen. Insider bei Herstellern von Halbleiterproduktionsequipment berichten, dass man in den nächsten Monaten mit starkem Konkurrenzkampf rechnet. Gerade im Embedded-Bereich gilt, dass es kleinere Anbieter besonders hart trifft. Fraglich ist deshalb, inwiefern Microsoft gewillt sein wird, seinen hauseigenen Hardware-Embedded-Partnern unter die Arme zu greifen. Sonst gilt ebenfalls, dass die Weltpolitik ein wichtiger Einflussfaktor ist. Inflation und Rezession führen immer auch zu weniger Umsatz im IT-Bereich.

Krypczyk: Speziell, was die Tools angeht, blicke ich auf Visual Studio für macOS. Auch für diese IDE sind weitere Updates angekündigt, zum Beispiel eine Unterstützung von .NET MAUI. Entwickler:innen, die auf Windows und macOS unterwegs sind, wird es freuen, dass auch dieses Tool sich stetig weiterentwickelt. Im Allgemeinen hoffe ich, 2023 wieder etwas Zeit zu finden, um private Entwicklungsprojekte voranzutreiben. Die machen nicht nur viel Spaß, sie bieten auch ein großes Lernpotenzial.

Kraus: Ich freue mich sehr auf die Arbeit mit dem neuen .NET Core 7 SDK. Im November 2022 wurde es veröffentlicht und vorgestellt. Es beinhaltet einige spannende Features wie beispielsweise die Unterstützung von HTTP/3. Man merkt auch, dass es der .NET-Welt sehr guttat, sich der Community zu öffnen und ihr Feedback in die Runtime und das SDK miteinfließen zu lassen. Auch wenn mein Herz immer noch für Angular schlägt, finde ich es spannend, dass sich nun auch in Blazor einiges tut und man viele Features, die man beispielsweise aus Angular kennt, auch in Blazor findet. Darüber hinaus freue ich mich sehr auf die Entwicklungen im Frontend-Bereich. Es heißt, dass sich das Angular-Team gerade die Frameworks SolidJS und Qwik anschaut, um deren Konzepte ggf. in Angular zu integrieren. Meiner Meinung nach wird aber Svelte nächstes Jahr noch mehr Verwendung finden – also definitiv ein Framework, das in Betracht zu ziehen sich lohnt.

Stropek: Mit einem Wort: WebAssembly. Ich habe große Hoffnungen, dass Wasm ähnlich große Auswirkungen auf DevOps haben wird, wie es die Containertechnologie hatte. Ich meine damit nicht primär Wasm am Client. Dieses Thema ist durch. Es ist gekommen, um zu bleiben. Meine Hoffnung für 2023 ist, dass Wasm vermehrt serverseitig und in der Cloud eine Rolle spielen wird. Ich werde daher neben .NET weiterhin einen Fokus auf Rust haben, da diese Plattform momentan in Sachen Wasm die Nase vorn hat, auch wenn .NET, Go und JavaScript kräftig aufholen.

Die Fragen stellten Patricia Stübig-Schimanski und Janine Jochum-Frenster.

The post Jahresrückblick 2022 appeared first on BASTA!.

]]>
Bessere Angular Architecturen mit Angular Libraries | Session https://basta.net/blog/bessere-angular-architecturen-mit-angular-libraries/ Wed, 23 Nov 2022 09:55:37 +0000 https://basta.net/?p=90935 Wie werden Angular Libraries erstellt, wofür ist das Angular Package Format gut und wie wird Code aus einer bestehenden Anwendung in eine Angular Library verschoben? In dieser Session bekommen Sie Antworten.

The post Bessere Angular Architecturen mit Angular Libraries | Session appeared first on BASTA!.

]]>
Angular bietet viele Möglichkeiten, wenn es um die Trennung und Architektur Ihrer Anwendung geht. Es gibt oft Code, den Sie nicht nur innerhalb Ihrer Anwendung wiederverwenden, sondern auch anderen Anwendungen in Ihrem Unternehmen oder über Paketmanager wie npm über das Internet zur Verfügung stellen wollen. Hier kommen Angulars Bibliotheken ins Spiel.

In diesem Vortrag geht Fabian Gosebrink darauf ein, wie Angular Libraries erstellt werden, wozu das Angular Package Format dient und wie Sie Code aus einer bestehenden Anwendung in eine Angular Library zur Wiederverwendung in mehreren Anwendungen verschieben können, um den Code über mehrere Anwendungen hinweg wiederzuverwenden. Dadurch wird die Wartbarkeit und die Architektur von Angular Anwendungen zum Kinderspiel.

 

 

Wollen Sie stets auf dem neusten Stand bleiben und keine weiteren Keynotes oder andere Videos verpassen? Dann melden Sie sich für unseren Newsletter an und wir informieren Sie, sobald es etwas neues in den Bereichen .NET,Windows & Open Innovations gibt.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

The post Bessere Angular Architecturen mit Angular Libraries | Session appeared first on BASTA!.

]]>
Datenbankanwendungen mit Azure SQL Server und Delphi realisieren https://basta.net/blog/datenbankanwendungen-mit-azure-sql-server-und-delphi-realisieren/ Wed, 09 Nov 2022 09:11:28 +0000 https://basta.net/?p=90742 Raten Sie mal, mit welcher Art von Software viel Geld verdient wird. Richtig: Datenbankanwendungen, die für die Digitalisierung in Unternehmen sorgen. Dabei herrscht der Trend vor, die Datenhaltung in die Cloud auszulagern. Das kann man auch für bestehende Applikationen mit einer sanften Migration erreichen. Ein Beispiel mit Azure und Delphi.

The post Datenbankanwendungen mit Azure SQL Server und Delphi realisieren appeared first on BASTA!.

]]>
Seien wir als Entwickler:innen doch ehrlich: Viele Applikationen für Unternehmen sind klassische Datenbankanwendungen, mit dem Ziel, die digitale Transformation voranzutreiben. Der Autor bezeichnet diese Art von Aufträgen auch gern als „Brot- und Buttergeschäft“. Dabei geht es im Wesentlichen um vergleichbare Anforderungen, in deren Mittelpunkt eine datenbankgetriebene Client-Anwendung steht. Datenbankmodell und Verarbeitungslogik sind zu entwickeln und müssen regelmäßig an sich ändernde Anforderungen angepasst werden. Typische Beispiele sind das Verwaltungsprogramm der Aufträge einer beliebigen Branche oder die Planungssoftware für die individualisierte Projektsteuerung. Diese Art von Software mag technisch wenig spannend klingen, sie erfüllt jedoch genau den Zweck, dem IT in einem Großteil der Unternehmen dienen soll.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Bei der Entscheidung für oder gegen ein solches Projekt sind stets auch die Alternativen zu betrachten. Sie lauten Standardsoftware oder die Entscheidung für ein System, das in einem Konfigurationsprozess an unterschiedliche Anforderungen angepasst werden kann. Die individuelle Softwareentwicklung bringt dabei den Vorteil einer guten Anpassbarkeit an den Geschäftsprozess mit. Sie muss sich jedoch in Fragen der Kosten und zeitlichen Umsetzung mit den genannten Alternativen messen, d. h., eine möglichst effiziente, kostengünstige und dennoch technisch aktuelle Lösung ist gefragt.

Für neue Applikationen ist der passende Technologiestack auszuwählen und für bestehende Anwendungen ist fortlaufend zu prüfen, ob und welche Teile bei einer Weiterentwicklung ggf. migriert werden sollen. Beispielhaft wollen wir den Wechsel des Datenbanksystems von einem lokal betriebenen Datenbankserver zu einer in der Cloud gehosteten Datenbank betrachten.

Technologische Entscheidungen müssen dabei stets vor dem Hintergrund der konkreten betrieblichen Anforderungen betrachtet werden. Daher widmet sich der kommende Abschnitt zunächst der Frage, welche Rolle Software heute in Unternehmen einnimmt.

Software als Innovationstreiber und die Wahl der Technologie

In immer mehr Unternehmen wird der Einsatz von Informationstechnologie zu einem Innovationstreiber. Unternehmen, denen es gelingt, ihre Geschäftsmodelle umfassend zu modernisieren oder sogar komplett zu transformieren, haben eine gute Chance, sich im harten Wettbewerb Vorteile zu erarbeiten. Ein einfaches Beispiel: Eine innovative Plattform zur Abwicklung aller relevanten administrativen Vorgänge in einem Produktionsbetrieb sorgt für eine Verschlankung und Beschleunigung der Prozesse. Der Einkauf von Vorprodukten wird bedarfsgerecht erledigt und Rechnungen werden ohne Verzögerung gestellt. Statt unzähliger Tabellen (Excel) und Dokumente (Word) werden alle relevanten Geschäftsprozesse in einem umfassenden Softwaresystem zusammengefasst. Peter Lieber, ehemaliger Präsident des Verbands Österreichischer Software Industrie (VÖSI), fasst es in einem Zitat [1] etwas provokant wie folgt zusammen: „Software ist der Innovationstreiber – meistens sogar gegen die IT. IT ist oft konservativ und will verhindern, dass zu viel Innovation die bestehenden Systeme gefährdet.“ Damit Software die Rolle des Innovationstreibers erfüllen kann, müssen einige Voraussetzungen gegeben sein (Abb. 1):

  • Fachliche Anforderungen: In einer sorgfältigen Anforderungsanalyse sind die Anforderungen an das umzusetzende System genau zu erheben. Die Güte einer solchen Analyse ist entscheidend für den Erfolg der Software. Individualsoftware wird auf die Belange der Kund:innen zugeschnitten und sollte Standardsoftware überlegen sein.

  • Moderne und ausgereifte Technologie: Die eingesetzte Technologie muss zum einem aktuell sein und sich zum anderen auch bewährt haben. Eine ausgereifte und etablierte Technologie ist in vielen Fällen entscheidend, um das Ziel der Digitalisierung möglichst mühelos und mit einem effizienten Entwicklungszyklus zu erreichen.

  • Geräteneutralität: Um die Anwendungen auf den unterschiedlichsten Geräten nutzen zu können, sind meist Webapplikationen oder Cross-Platform-Apps gefragt. Beide Anwendungsarten bieten eine Reihe von Vorteilen. Webanwendungen benötigen nur einen Browser auf Seiten des Anwenders. Cross-Platform-Apps haben den Vorteil, auch auf benötigte Hardware- und Systemkomponenten zugreifen zu können.

  • Moderne grafische Benutzeroberflächen und eine ansprechende User Experience: Auch bei Businessapplikationen werden heute hohe Ansprüche an das User Interface gestellt. Die gewählte Technologie muss in der Lage sein, eine solche grafische Oberfläche zu realisieren. Das User Interface muss im Hinblick auf das Design und die Bedienung sorgfältig gestaltet werden.

  • Nutzung diverser Datenhaltungstechnologien: Die Unternehmensdaten werden in der Regel auf Datenbankservern gehalten. Im Zuge einer weiteren Flexibilisierung werden diese heute häufig in die Cloud ausgelagert, sofern dem keine betrieblichen Belange entgegenstehen.

  • Effiziente Umsetzung: IT-Entwicklungsprojekte stehen stets unter einem gewissen wirtschaftlichen Druck. Vor dem Hintergrund steigender Kosten muss es gelingen, das Projekt in einem vorgegebenen Budget und der gewünschten Zeit umzusetzen. Es gilt, das magische Viereck des IT-Projektmanagements aus Kosten, Zeit, Qualität und Quantität in den Griff zu bekommen. Dieses Ziel kann besser durch etablierte Technologien und einen passenden Werkzeugeinsatz erreicht werden.

  • Gute Wartbarkeit: Die Lebensdauer einer Applikation hängt im Wesentlichen davon ab, wie gut sie über die geplante Nutzungsdauer an sich stetig ändernde Anforderungen angepasst werden kann. Maßgeblich ist auch hier unter anderem die Beständigkeit der Technologie.

Vor diesem Hintergrund die passende Technologie auszuwählen ist mitunter ein komplexer Vorgang. Die Vielfalt der Optionen macht den Entscheidungsprozess nicht einfach. Fragt man die Entwickler:innen, dann brennen sie für jede neue Technologie. Das ist die notwendige Neugier, um auch über viele Jahre den Spaß am Job zu behalten. Ebenso ist es ein Garant dafür, dass technische Innovationen vorangetrieben werden. Noch sehr neue technische Ansätze sind jedoch in der Regel nur eingeschränkt geeignet, um ein Softwareprojekt unter den gegebenen Restriktionen erfolgreich umzusetzen. Typische Kinderkrankheiten und fehlende Erfahrungen sprechen dagegen. Andererseits muss man auch stets mit der Zeit gehen und veraltete Technologien sollten in einem neuen Projekt auch keinen Platz haben.

Bewährt hat sich ein gemischtes Vorgehen: Die eingesetzten Technologien werden immer wieder auf den Prüfstand gestellt, evaluiert und schrittweise durch neue Komponenten ersetzt. Gelingt dieses Vorgehen, dann kommt es zu keinen größeren Systembrüchen. Man kann technologische Updates in die Lösung integrieren und dennoch einen effizienten Entwicklungszyklus beibehalten.

 

Ein Softwaresystem besteht normalerweise aus mehreren Komponenten. Als Entwickler:in oder Softwarearchitekt:in hat man dabei die Wahl und die Qual, das passende System zu bestimmen. Neben den Anforderungen der Kund:innen, müssen dabei auch die technischen Möglichkeiten des Teams, die anfallenden Kosten, die verfügbare Zeit und die Erfahrung berücksichtigt werden. Anhand der Anforderungen können die unterschiedlichen technischen Optionen ermittelt und bewertet werden. Ein typischer Schritt bei einer Erneuerung der technischen Basis ist die Verlagerung von Teilen der Applikation in die Cloud. Relativ einfach – wir bezeichnen es als sanfte Migration – ist das für das System der Datenhaltung zu realisieren, wie der kommende Abschnitt zeigt. Dabei können die anderen Bestandteile des Softwaresystems beibehalten werden und die in Administration und Wartung aufwendige Datenbank wird aus dem Softwaresystem ausgelagert.

 

Migration der Datenhaltung in die Cloud

Unternehmensapplikationen müssen in den meisten Fällen auf Datenbanken zugreifen. Dabei wird zwischen relationalen und NoSQL-Datenbanken unterschieden. Relationale Datenbanken machen weiterhin einen Großteil der Datenhaltung für Unternehmensapplikationen aus, da sich Unternehmensanforderungen im Allgemeinen sehr gut über zueinander in Beziehung stehende Tabellen abbilden lassen. Oft verwendet wird zum Beispiel Microsoft SQL Server. Bezüglich der physischen Anordnung der Datenbanken lassen sich grob die folgenden drei Kategorien differenzieren:

  • Lokale Datenbank: Eingebettet in die Applikation werden die Daten lokal auf dem Rechner des Softwaresystems abgelegt. Das ist heute die Ausnahme und nur für kleinere Applikationen geeignet.

  • Datenbankserver: Es wird eine klassische Client-Server-Struktur betrieben. Die Technologie ist in vielen Softwareapplikationen seit vielen Jahren Standard und etabliert.

  • Cloud-gehostete Datenbanken: Die Infrastruktur für die Datenbank wird in die Cloud verlagert. Dabei gibt es unterschiedliche Abstufungen, beginnend beim Betrieb auf einer virtuellen Maschine (VM) mit einem Serverbetriebssystem und einer installierten Datenbank bis hin zur Nutzung als virtuelle SQL-Datenbank.

Je nach gewähltem „Betriebsmodell“ (Abb. 2) für den Datenbankserver in der Cloud handelt es sich um Infrastructure as a Service (IaaS), zum Beispiel einen SQL-Server in einer virtuellen Maschine, oder um Platform as a Service (PaaS), zum Beispiel eine virtualisierte SQL-Datenbank, wobei man sich über den technologischen Unterbau keine Gedanken mehr machen muss. Mit anderen Worten: Beim Betrieb des Datenbankservers als virtuelle Maschine ist man weiterhin für die Administration des Servers zuständig, während man die Verantwortung für die Hardware vollständig abgegeben hat. Bei der Nutzung einer virtuellen Datenbank kann diese einfach nach Konfiguration genutzt werden.

 

Die Verlagerung der Datenbank in die Cloud ist nicht nur ein Trend, sondern bringt auch eine Reihe von Vorteilen:

  • Kosten: Geringere Kosten bzw. gute Kalkulierbarkeit durch eine Verlagerung der Infrastruktur zum Serviceanbieter

  • Verwaltung: Es verringert sich der Aufwand für die Einrichtung, den Betrieb und die Wartung der Infrastruktur je nach Betriebsmodell

  • Service-Level: Cloud-Services bieten eine hohe Verfügbarkeit von 99,95 Prozent oder mehr. Diese Werte können mit einer eigenständig betriebenen Infrastruktur nur mit großem Personal- und Sachaufwand erreicht werden.

  • Flexibilität: Die Leistung kann schnell und ohne große Verzögerungen mit direkten Auswirkungen auf die Kosten nach oben oder auch nach unten skaliert werden.

Natürlich gibt es auch Nachteile bei der Nutzung von Cloud-Services. Beispielsweise ist die Einhaltung der Datenschutzvorschriften (DSGVO) sicherzustellen. Da die Daten nunmehr von einem Dritten, dem Cloud-Dienstleister, vorgehalten werden, ist dieser Schritt sorgfältig mit dem Kunden gemeinsam zu planen und zu entscheiden.

Wie kann die Verlagerung der Datenbank konkret in der Praxis aussehen? Für Softwaresysteme, die bisher eine SQL-Datenbank auf dem eigenen Server genutzt haben, kann man eine Migration auf eine in der Cloud gehostete Datenbank erwägen. Am Beispiel des häufig eingesetzten Microsoft SQL Servers soll dieses Szenario skizziert werden. Über das Portal unter [3] kann man beide Datenbanken mit ihren zentralen Eigenschaften vergleichen. Die Eigenschaften der beiden Datenbankvarianten sind nahezu identisch. Ein Blick in die Dokumentation von Azure SQL [2] bestätigt das. Feingranular wird dabei nochmals zwischen den Cloud-Betriebsarten unterschieden. Die höchste Komptabilität im Vergleich zu einem on premises betrieben SQL-Server bietet dabei eine in Azure gehostete Virtuelle Maschine mit einem installierten SQL-Server. Bei einer Nutzung dieser Cloud-Betriebsart spricht man von einer „Lift & Shift“-Migration, die mit nur wenigen oder ganz ohne Änderungen auskommt. Bei der Konzeption eines vollständig neuen Softwaresystems ist man hier noch weniger eingeschränkt, da ein möglicher Anpassungsaufwand – bedingt durch die Cloud-Migration – in diesem Fall entfällt.

 

 

 

Fallbeispiel mit Delphi und einem Azure SQL Server

Gemäß dem beschriebenen Ziel einer effektiven Entwicklung soll hier beispielhaft die Verwendung des Azure SQL Server mit nativen, auf dem Client ausgeführten Applikationen beschrieben werden. Als Entwicklungsumgebung wird Delphi bzw. RAD Studio [4] verwendet. Mit dieser integrierten Entwicklungsumgebung, aktuell in Version 11.2 vorliegend, ist es möglich, Cross-Platform-Anwendungen für die Systeme Windows, macOS, Linux (Desktop), iOS und Android (Mobile) sowie über ein weiteres Framework (TMS WebCore [5]) auch Webapplikationen zu erstellen. Ein Vorzug dieser Entwicklungsumgebung ist eine komponentenbasierte Entwicklung mit einem grafischen Designer. Damit ist ein effizienter Entwicklungszyklus mit einer schnellen Bereitstellung des Softwaresystems für den Kunden möglich. Bisher wurden solche Systeme primär als klassische Client-SQL-Systeme konzipiert (Abb. 3).

Das Ziel besteht nun in einer Verlagerung der Datenbank in die Cloud (Abb. 4). Geeignet ist beispielsweise der Azure SQL Server. Dazu muss die Schnittstelle zwischen Anwendung und Datenbank angepasst werden. Als generischer Datenbanktreiber kommt in Delphi-Anwendungen die Komponente FireDAC zum Einsatz. Sie bietet über native Datenbanktreiber einen universellen Zugriff auf unterschiedliche Datenbanken, zum Beispiel Oracle, MS SQL und MySQL und viele mehr. FireDAC ist damit eine generische Abstraktionsschicht zwischen dem Anwendungssystem und der Datenbank (Abb. 5).

Statt einer Verbindung zu einem lokal verwalteten Datenbankserver wird eine Verbindung zum Cloud-Service mit der Datenbank in Azure hergestellt. Um sie zu betreiben und aus einer Delphi-Applikation zu adressieren, sind die folgenden Schritte notwendig:

  1. Anlegen einer SQL-Server-Instanz in Azure: Dazu ist der Name der Datenbank festzulegen, ein Server auszuwählen bzw. (wenn noch nicht vorhanden) neu zu erstellen und das Abrechnungsmodell über die Zuweisung einer Ressourcengruppe zu wählen (Abb. 6).

  2. Verbindungsdaten: Die Verbindungsdaten sind abzurufen: Servername, Name der Datenbank, Benutzername und Passwort.

  3. Verbindung konfigurieren: Der in Azure gehostete SQL-Server ist aus Sicht des Datenbanktreibers (FireDAC) mit einem lokal betriebenen SQL-Server identisch. Die Verbindungsdaten sind anzupassen. In Delphi erfolgt das über den Datenexplorer. Als Datenbanktreiber ist MSSQL auszuwählen. Die Verbindungsdaten (Schritt 2) sind zur Konfiguration einzugeben (Abb. 7).

  4. Import von Daten: Vorhandende Daten sind ggf. von einer lokal betriebenen Serverinstanz zum in der Cloud gehosteten SQL-Server zu migrieren.

Damit ist der Weg zur Nutzung einer SQL-Datenbank, die über Azure bereitgestellt wird, skizziert. Weitere technische Details finden sich im Blogbeitrag unter [7]. Wie bereits erwähnt, ist das Vorgehen sowohl für bestehende Anwendungen als auch für die Neuentwicklung geeignet. Der Technologiestack ist damit auf Unternehmensapplikationen ausgerichtet. Unsere beschriebenen Anforderungen für das „Brot- und Buttergeschäft“ der Anwendungsentwicklung können auf diese Weise gut umgesetzt werden und es ist eine Konzentration auf die Fachanwendung möglich. Sie wird im Beispiel mit Delphi realisiert. Die Vorzüge einer integrierten Entwicklungsumgebung können dabei genutzt werden. Die aufwendige Administration und der Betrieb des Datenbankservers werden in die Cloud ausgelagert.

 

Fazit und Ausblick

Betriebliche Anwendungen bestehen oft viele Jahre. Bei der Auswahl der passenden Technologie sind neben den Anforderungen der Kund:innen auch die Aspekte Wirtschaftlichkeit der Entwicklung und Wartbarkeit zu berücksichtigen. In diesem Artikel haben wir gezeigt, wie man die Datenhaltung vom lokal betriebenen Datenbankserver in die Cloud verlagert. Das Vorgehen kann sowohl für bestehende Applikationen (teilweise oder sanfte Migration) oder für Neuentwicklungen von Interesse sein.

The post Datenbankanwendungen mit Azure SQL Server und Delphi realisieren appeared first on BASTA!.

]]>
Wo lang? Bauch oder Kopf? (Technologie-)Entscheidungen richtig treffen | Keynote https://basta.net/blog/keynote-video-bauch-oder-kopf-technologie-entscheidungen-richtig-treffen/ Thu, 03 Nov 2022 14:23:03 +0000 https://basta.net/?p=90714 Welche Technologie ist die Richtige? Auf welchen Hersteller soll ich setzen? Wie baue ich die Architektur meiner App am besten auf? In dieser Keynote bekommen Sie Antworten.

The post Wo lang? Bauch oder Kopf? (Technologie-)Entscheidungen richtig treffen | Keynote appeared first on BASTA!.

]]>
Soll es die rote oder die blaue Pille sein? Entscheidungen, wie im Film Matrix, verfolgen uns den ganzen Tag. Laut Psychologen treffen wir 20.000 Entscheidungen über den Tag verteilt. Auch im Softwarealltag verfolgen uns Entscheidungen, angefangen bei Technologien und Herstellern über die passende Architektur und vieles mehr. Durch eine immer größer werdende Menge an Möglichkeiten ist das Entwickler:innenleben in den vergangenen Jahren nicht einfacher geworden – Wege und Möglichkeiten werden immer unübersichtlicher. Thomas Althammer spricht in dieser inspirierenden Keynote auf der BASTA! 2022, über einfache Methoden für bessere Entscheidungen.

 

 

Wollen Sie stets auf dem neusten Stand bleiben und keine weiteren Keynotes oder andere Videos verpassen? Dann melden Sie sich für unseren Newsletter an und wir informieren Sie, sobald es etwas neues in den Bereichen .NET,Windows & Open Innovations gibt.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

The post Wo lang? Bauch oder Kopf? (Technologie-)Entscheidungen richtig treffen | Keynote appeared first on BASTA!.

]]>
Spaß mit Mustern – Pattern Matching in C# https://basta.net/blog/spass-mit-mustern-pattern-matching-in-c/ Mon, 05 Sep 2022 14:53:34 +0000 https://basta.net/?p=90121 Über viele C#-Sprachversionen hinweg hat Microsoft tüchtig an Features für das Pattern Matching gearbeitet. Zum aktuellen Stand ist es Zeit für einen Rückblick und eine Einschätzung. Auf der BASTA! Spring gibt’s auch einen Vortrag zu dem Thema, wenn ihr Interesse habt.

The post Spaß mit Mustern – Pattern Matching in C# appeared first on BASTA!.

]]>
C#-Version 7.0 wurde mit Visual Studio 2017 öffentlich verfügbar, also tatsächlich im Jahre 2017 – das war ja nicht immer so offensichtlich. Zum ersten Mal stand da „Pattern Matching“ auf der Liste der Neuheiten, oder gar „Mustervergleich“, wenn man diese Liste auf Deutsch las. Ich tat das nicht, was mich im Nachhinein glücklich stimmt, denn so musste ich mich nicht über die Neuheit „Erweiterte Ausdruckskörpermember“ wundern [1].

In C# 7 wurde Pattern Matching erstmals möglich, und zwar mit dem recht einfachen Typvergleich. Etwa so:

void ProcessItem(Item item) {
  if (item is Hammer hammer) {
    // jetzt hämmern
  }
  else {
    // kann nicht gehämmert werden
  }
}

Mit C# 7.1 wurde dieses neue Feature noch ein wenig mit der Unterstützung von Generics aufgebohrt, und außer in if-Ausdrücken konnte es auch in switch verwendet werden. Trotzdem ließ sich die Featureankündigung „Pattern Matching“ etwas großspurig an, denn bis dahin gab es tatsächlich nur sehr wenig Möglichkeiten, wenn auch durchaus wichtige.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

In C# 8 ging es allerdings mit Macht weiter, mit vielen Neuerungen für Liebhaber von Mustern. Zunächst konntet ihr nun switch auch als Ausdruck verwenden, was einer kompakten Syntax sehr zuträglich war.

int CalcResult(int input) {
  var result = input switch {
    1 => 2,
    2 => 3,
    _ => throw new ArgumentException("Weiter geht's nicht")
  };
}

Das war natürlich ein sehr funktionaler Schritt, der die imperative Struktur von switch, die bereits ähnlich in der Sprache C existierte, durch eine moderne funktionale Variante ergänzte. Interessanterweise entschied man sich dagegen, switch zum Bestandteil einer Funktionsdeklaration zu machen, wie das etwa in F# geht.

let calcResult =
    function | 1 -> 2
             | 2 -> 3
             | _ => raise (System.ArgumentException("Weiter geht's nicht"))

Richtig, bei dieser merkwürdigen Syntax gibt es gar keinen sichtbaren Eingabeparameter. Das ist nicht weiter schlimm, denn direkter Zugriff auf den Wert ist nicht nötig, und auch tatsächlich nicht möglich – offenbar wollte Microsoft die C#-Programmierer nicht mit solchen extremen Formen verwirren. Immerhin ist die Form in C# ansonsten durchaus angenehm zu verwenden und sehr nah an dem, was sich in F# machen lässt.

Wichtige neue Mustertypen seit C# 8.0

Zusätzlich zum switch-Ausdruck gab es in C# 8.0 außerdem neue Muster, und die in dieser C#-Version eingeführten Neuheiten waren womöglich die bedeutsamsten in der Familie von Musterfeatures bis zum heutigen Tag. Zunächst gab es das Property-Pattern, mit dem sich einzelne Eigenschaftswerte prüfen lassen:

bool IsJune(DateTime dt) => dt is { Month: 6 };

Das Schlüsselwort is ergibt in diesem Zusammenhang nicht immer den meisten Sinn – besser wäre (englisch) „matches“ oder etwas Ähnliches. An dieser Stelle greife ich einmal etwas voraus: In C# 9.0 wurden zusätzlich relationale und logische Operatoren verfügbar, die mit anderen Patterns kombiniert werden können. So könnt ihr nun diese Varianten verwenden:

bool IsPastMidYear(DateTime dt) => dt is { Month: >= 7 };
bool IsThisYearOrLast(DateTime dt) => dt is { Year: 2022 or 2021 };
bool IsSummer(DateTime dt) => dt is
  { Month: 7 or 8 } or
  { Month: 6, Day: >= 21 } or 
  { Month: 9, Day: < 21 };

Zur Zeit der Einführung von C# 8.0 wurden in der Liste von Neuheiten noch separat das Tuple Pattern und das Positional Pattern aufgeführt. Mittlerweile listet die Dokumentation allerdings nur noch das Positional Pattern, vermutlich weil man bei Microsoft gemerkt hat, dass die beiden Varianten ähnlich sind bzw. eng zusammengehören. Das Positional Pattern lässt sich nämlich auf jeden Typ anwenden, der sich mit Hilfe einer Deconstruct-Methode in seine Einzelteile zerlegen lässt – und das Tupel (also der eingebaute Typ Tuple, der Klarheit halber) ist genau so ein Typ. Ihr wisst sicherlich, dass der Typ dekonstruierbar ist, etwa so:

(string, string) personInfo = ("Oli", "Sturm");
var (firstName, lastName) = personInfo;
// firstName ist jetzt "Oli" und lastName ist "Sturm"

Das ist also dasselbe Verhalten, das sich für einen beliebigen Typ mit der Implementation einer Deconstruct-Methode auch erreichen lässt. Allerdings lässt sich der Begriff „Tuple Pattern“ noch immer verwenden, wenn es um einen Einsatzfall geht, wo für das Muster zunächst eine Struktur hergestellt werden muss. Im Beispiel in Listing 1 werden zwei separate Parameter auf diese Weise für ein Muster zusammengefasst.

 

static OrderValue OrderValueCategory(
int itemCount, double itemPrice) => (itemCount, itemPrice) switch
{
  ( < 0, _) => throw new ArgumentException("Positive itemCounts please!"),
  (_, < 0) => throw new ArgumentException("Positive itemPrices please!"),
  ( >= 100, _) => OrderValue.ValuableDueToHighCount,
  (_, >= 1000) => OrderValue.ValuableDueToHighItemPrice,
  var (c, p) when c * p > 1000 => OrderValue.ValuableDueToHighTotal,
  _ => OrderValue.NotValuable
};

In dieser Syntax lässt sich die Quelle für den Match als Tupel interpretieren, daher passt hier der Name. Im Beispiel sind noch einige andere Details zu sehen, die ich bisher nicht angesprochen habe. Der Unterstrich dient als Platzhalter für einen Teil des Resultats, der im Muster irrelevant ist (Microsoft nennt das „Discard Pattern“). Im vorletzten Muster ist zu sehen, wie ein Muster auch dazu verwendet werden kann, Werte für die weitere Nutzung zu extrahieren. In diesem Fall wurde die etwas verwirrende Form var (c, p) verwendet, die gleichbedeutend mit (var c, var p) ist – die Wahl ist dem Programmierer überlassen. Auf diese Methode der Werteextraktion komme ich später noch einmal zurück.

In allgemeinerer Form könnt ihr das Positional Pattern auch für komplexere Typen anwenden, die bereits existieren. Im folgenden Beispiel gibt es zur Kapselung der Werte itemCount und itemPrice einen eigenen Typ Order und dieser wird im Match direkt verwendet – die Logik der Muster bleibt allerdings exakt identisch, da der Typ mit seiner Deconstruct-Methode die Werte in derselben Struktur extern verfügbar macht, die das Tupel im ersten Beispiel auch hatte (Listing 2).

class Order {
  public int ItemCount { get; set; } public double ItemPrice { get; set; }
  public void Deconstruct(out int itemCount, out double itemPrice) {
    itemCount = ItemCount; 
    itemPrice = ItemPrice;
  }
}
 
static OrderValue OrderValueCategory(Order o) => 
  o switch 
{
  ( < 0, _) => throw new ArgumentException("Positive itemCounts please!"),
  (_, < 0) => throw new ArgumentException("Positive itemPrices please!"),
  ( >= 100, _) => OrderValue.ValuableDueToHighCount,
  (_, >= 1000) => OrderValue.ValuableDueToHighItemPrice,
  var (c, p) when c * p > 1000 => OrderValue.ValuableDueToHighTotal,
  _ => OrderValue.NotValuable
};

Muster müssen vollständig sein

Es ist übrigens auch möglich, beim Positional Pattern die Namen der Parameter anzugeben. So könnte ich etwa schreiben:

...
(itemCount: >= 100, itemPrice: _) => OrderValue.ValuableDueToHighCount,
...

Diese Syntax halte ich persönlich allerdings nur aus einem Grund für erwähnenswert: Sie funktioniert nicht so, wie mancher Programmierer instinktiv annimmt. Viele vermuten nämlich, dass mit der Verwendung eines oder mehrerer Namen nicht das gesamte Muster angegeben werden muss, also im Beispiel etwa der itemPrice weggelassen werden darf. Das stimmt allerdings nicht, der Compiler weist dann auf das fehlende Element hin. So dient also die Angabe der Namen höchstens der Illustration – das sei euch überlassen, aber für mich persönlich ist so etwas Platzverschwendung und Mehrarbeit bei der Wartung. Dabei meine ich wohlgemerkt nicht die Tatsache, dass das Muster vollständig sein muss – das finde ich gut!

Da ich selbst Pattern Matching vor allem in funktionalen Programmiersprachen seit vielen Jahren einsetze, fällt mir beim Lesen von Dokumentation und Beispielen auf den Microsoft-Webseiten vor allem auf, dass diese sich grundsätzlich im Bereich geschäftlicher Objekte und Daten bewegen. Das finde ich interessant, da ich in der funktionalen Programmierung die Verwendung von Pattern Matching als algorithmische Struktur gewöhnt bin. Als Beispiel hier eine mögliche F#-Implementation der Standardfunktion fold (analog zu reduce in anderen Sprachen, oder Aggregate in LINQ) mit Hilfe von Pattern Matching:

let rec fold f s = function
  | [] -> s
  | x :: xs -> fold f (f s x) xs

Wenn ihr versucht, über die Logik dieses Codes nachzudenken, fällt euch bestimmt schnell auf, was derzeit in C# noch fehlt: Muster für die Handhabung von Listen. Man kann argumentieren, dass diese nicht sinnvoll sind in der Sprache – vielleicht wird sich das ändern, wenn es in Zukunft Discriminated Unions gibt und vielleicht auch bessere Unterstützung für Tail Recursion. Ich möchte hier nicht näher auf das F#-Beispiel eingehen – gern könnt ihr mich kontaktieren, wenn ich damit Interesse geweckt habe.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Auch in C# lassen sich Muster algorithmisch nutzen, wenn es nicht gerade um Listenverarbeitung geht. Ich habe ein komplexes Beispiel zu diesem Thema, das aus meiner Arbeit an einem Buch zu funktionaler Programmierung in C# entstand. Nachdem ich in 2007 begonnen hatte, auf Veranstaltungen über dieses Thema vorzutragen, arbeitete ich an einer Sammlung hilfreicher Funktionen, die ich FCSlib nannte [2]. Mein Buch zum Thema erschien 2011 und die Codebasis enthielt eine Implementation des rot-schwarzen Binärbaums in funktionaler Art, die von Chris Okasaki in seinem Buch „Purely Functional Data Structures“ publiziert worden war. Der originale Code lag in ML und Haskell vor und ich hatte ihn für C# umgeschrieben.

Funktionales Balancieren mit Haskell

Besonders interessant ist im Zusammenhang mit Pattern Matching die Funktion balance. Diese stellt im Binärbaum das Gleichgewicht wieder her – wie das genau geschieht, sei eurem eigenen Studium eines schwarz-roten Baums überlassen. Ich empfehle sehr, Chris Okasakis Buch zu lesen. Seine Implementation der Funktion in Haskell basiert auf diesen beiden Datentypen:

data Color = R | B
data RedBlackSet a = E | T Color (RedBlackSet a) a (RedBlackSet a)

Das sind Discriminated Unions, die es leider bisher in C# nicht gibt. Damit lassen sich sehr einfach Datentypen beschreiben. Die Farbe darf etwa oder B sein – Red oder Black. Der Typ RedBlackSet (mit dem generischen Subtyp a) kann entweder E sein (Empty, also leer), oder ein Wert T (Tree), dem dann eine Farbe zugewiesen wird, sowie ein linker und ein rechter Ast mit einem Wert vom Typ ain der Mitte. Fertig. Beeindruckend, oder? Aber das nur am Rande.

Die Funktion balance selbst besteht aus fünf überladenen Teilen. Hier sind zwei davon:

balance B (T R (T R a x b) y c) z d = T R (T B a x b) y (T B c z d) 
balance B (T R a x (T R b y c)) z d = T R (T B a x b) y (T B c z d)
...

In Haskell ist es üblich, dass unterschiedliche Teile eines Mustervergleichs einfach als Varianten einer Funktion geschrieben sind. Das geschieht hier, daher unterscheiden sich die beiden Funktionsteile anscheinend in der Signatur, aber nicht im Namen. Der Teil zwischen balance und dem Gleichheitszeichen stellt ein Pattern dar. Es wird „gematcht“, wenn der Baum B (also schwarz) ist, und dann wird der linke Teilbaum mit dem Ausdruck in Klammern verglichen – der muss T sein (muss also etwas enthalten, nicht E für den leeren Baum), und außerdem R für rot, wiederum einen Unterbaum R haben usw., und falls dieses Pattern zutreffen sollte, wird letztlich auf der rechten Seite der Zuweisung ein neuer Baum mit einer etwas veränderten Struktur erzeugt.

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

Wie gesagt, auf den Algorithmus zur Ausbalancierung des Baums kommt es hier nicht an. Die Patterns sind allerdings interessant, da geschehen mehrere Dinge:

  1. Die Struktur, die an die Funktion übergeben wurde, wird mit der im Pattern verglichen.

  2. Dies geschieht auch mit untergeordneten Teilstrukturen, nicht nur auf der obersten Ebene.

  3. Wenn ein Muster passt, werden Informationen für die weitere Verarbeitung aus der Datenstruktur entnommen – so ergeben sich die Werte axbycz und d, die auf der rechten Seite zur Erzeugung der neuen Struktur verwendet werden.

In der C#-Syntax von vor zehn Jahren ließ sich dieser Vorgang nicht syntaktisch elegant abbilden. Mein Code von damals war also extrem lang und ausführlich – in Listing 3 findet sich ein kleiner Auszug zur Illustration.

private static RedBlackTree<T> Balance(Color nodeColor, RedBlackTree<T> left, T value, RedBlackTree<T> right) {
  if (nodeColor == RedBlackTree<T>.Color.Black) {
    if (!(left.IsEmpty) &&
      left.NodeColor == RedBlackTree<T>.Color.Red &&
      !(left.Left.IsEmpty) &&
      left.Left.NodeColor == RedBlackTree<T>.Color.Red)
      return new RedBlackTree<T>(Color.Red, 
        new RedBlackTree<T>(Color.Black, 
          left.Left.Left, left.Left.Value, left.Left.Right),
        left.Value, 
        new RedBlackTree<T>(Color.Black, 
          left.Right, value, right));
...

Diese Zeilen entsprechen übrigens der ersten Zeile aus dem Haskell-Beispiel, falls ihr zum Verständnis noch einmal zurückblicken möchtet. Nun kommt aber die richtig tolle Neuigkeit: Mit der Unterstützung für Pattern Matching in heutigen C#-Versionen lässt sich die gesamte Funktion wie in Listing 4 schreiben. Die verwendeten Strukturen habe ich alle zuvor bereits angesprochen – das Tuple Pattern sowie das Positional Pattern, basierend auf einer Deconstruct-Methode im Typ RedBlackTree<T>.

private static RedBlackTree<T> Balance(Color nodeColor, RedBlackTree<T> left, T? value, RedBlackTree<T> right) { 
  const Color R = Color.Red;
  const Color B = Color.Black;
  RedBlackTree<T> TT(Color c, RedBlackTree<T> l, T? v, RedBlackTree<T> r) => new(c, l, v, r);
  return (nodeColor, left, value, right) switch
  {
    (B, (R, (R, var a, var x, var b), var y, var c), var z, var d) => 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)),
    (B, (R, var a, var x, (R, var b, var y, var c)), var z, var d) => 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)),
    (B, var a, var x, (R, (R, var b, var y, var c), var z, var d)) => 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)),
    (B, var a, var x, (R, var b, var y, (R, var c, var z, var d))) => 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)),
    (var color, var a, var x, var b) => TT(color, a, x, b)
  };
}

Dazu gibt es mehrere Dinge zu sagen. Erstens, es ist immer noch nicht alles so wie in Haskell. Da gibt es einige Unterschiede. Z. B. beinhaltet das Pattern Matching in C# nicht die Unterscheidung T oder E aus Haskell – enthält der Teilbaum überhaupt etwas oder nicht? Das wäre in C# unverhältnismäßig schwieriger, daher habe ich den Baum einfach mit einem Platzhalter für eine „leere Farbe“ programmiert, sodass und B bereits nur „volle“ Elemente finden. In C# sind außerdem Hilfsdefinitionen nötig, um die Werte R und B bzw. die kurze Notation zur Erzeugung eines neuen Baumes nutzbar zu machen.

Schließlich gibt es noch Kuriositäten im Detail – um etwa R und B für den Match verwenden zu dürfen, müssen diese const deklariert sein. Zusätzlich (oder trotzdem!) müssen aber auch die später verwendbaren Variablen jeweils var vorangestellt haben – warum? Das ist in C# wohl eine Frage der Konsistenz, aber es macht jeden Match-Ausdruck in diesem Beispiel doppelt so lang, wie er sein müsste, und trägt zur Signifikanz des Ausdrucks nichts bei.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Zweitens und abschließend stellt sich natürlich, wie auch in meinem Buch vor über zehn Jahren, die Frage: Sollte man das so machen? Was würdet ihr von dem Kollegen halten, der solchen Code schreibt und eincheckt? Nun, die Vorgaben dazu sollten natürlich jedem Team überlassen sein, und ich habe keinen Zweifel, dass manche C#-Programmierer eher ungern im Alltag solchen Code lesen würden. Allerdings finde ich auch die umgekehrte Sicht interessant. Der Code im Beispiel spiegelt letztlich einen komplexen Algorithmus wider. Insbesondere stellt er ein direktes Abbild der originalen Variante dar und es lässt sich in dieser Form direkt ein Vergleich anstellen, um etwa zu gewährleisten, dass der ursprüngliche Algorithmus korrekt übertragen worden ist – das wäre in der alten Implementation sehr schwierig gewesen. Sollte ein Original in ähnlichem Zusammenhang einmal geändert oder aktualisiert werden müssen, lässt sich die Änderung entsprechend einfach nach C# übertragen. Fühlt sich ein durchschnittlicher C#-Entwickler angehalten, an diesem Code schnell mal eine verdachtsbasierte Veränderung durchzuführen? Wohl kaum, bzw. hoffentlich nicht! Das ist schließlich gut so – und nebenbei ist das in der alten Form des Codes auch nicht anders. Insofern spricht doch vielleicht gar nichts dagegen, komplexe logische Zusammenhänge in syntaktisch kompakten Formen wie im Beispiel zu schreiben. Vielleicht ist das nicht absolut alltagstauglich, aber wir arbeiten ja auch nicht tagein, tagaus an den Innereien von rot-schwarzen Binärbäumen!

The post Spaß mit Mustern – Pattern Matching in C# appeared first on BASTA!.

]]>
Mehr Performance für Komponenten https://basta.net/blog/mehr-performance-fuer-komponenten/ Mon, 25 Jul 2022 11:19:48 +0000 https://basta.net/?p=89917 Stockendes UI, keine Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld, das sind nur einige Beispiele alltäglicher Probleme, die bei der Nutzung von Clientanwendungen im Allgemeinen und Webanwendungen im Speziellen immer wieder auftreten können. In diesem Artikel schauen wir uns an, wie wir komponentenbasierte UIs in Blazor WebAssembly optimieren können, um dadurch eine für die Benutzer zufriedenstellende Geschwindigkeit und ein flüssiges UI zu bekommen.

The post Mehr Performance für Komponenten appeared first on BASTA!.

]]>
In der Entwicklung von Webanwendungen und so auch in der Entwicklung von Blazor WebAssembly SPAs ist es wichtig, die Laufzeitperformance der Anwendung immer im Auge zu behalten und wenn nötig zu optimieren. Daher schauen wir uns in diesem Artikel die Optimierungsmöglichkeiten einer Blazor-Komponente etwas genauer an.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Renderlebenszyklus einer Komponente

die Demoanwendung läuft mit dem .NET SDK 6.0.201, ASP.NET Core Blazor WebAssembly 6.0.4 und MudBlazor 6.0.9. Der Beispielcode für diesen Artikel findet sich unter [1]. Bevor wir aber mit der Optimierung von Komponenten beginnen können, ist es wichtig zu verstehen, welchen Lebenszyklus eine Komponente beim Rendern durchläuft und wie dieser ausgelöst werden kann.

Lebenszyklus

Der Lebenszyklus einer Blazor-Komponente beginnt, wenn sie auf der Seite gerendert wird. Sie wird zum ersten Mal sichtbar.

 


Abb. 1: Blazor-WebAssembly-Komponenten-Rendering-Lebenszyklus

 

In Abbildung 1 sehen wir die verschiedenen Schritte, die durchlaufen werden, sobald das Rendering einer Komponente angestoßen wird. Im ersten Schritt wird geprüft, ob es sich um das initiale Rendern der Komponente handelt oder die Komponente andernfalls neu gerendert werden soll. Das wird anhand von zwei Optionen geprüft:

  • Innerhalb der Basisklasse einer Blazor-Komponente, der ComponentBase-Klasse [2], wird geprüft, ob es sich um das erste Rendern der Komponente handelt.

  • Handelt es sich nicht um das erste Rendering, wird die Methode ShouldRender(), die im nächsten Abschnitt näher erläutert wird, der Basisklasse aufgerufen. Die Methode gibt einen bool-Wert zurück, der angibt, ob der Renderprozess durchgeführt werden soll. Der Defaultrückgabewert ist true.

Trifft keine der beiden Optionen zu, wird der Renderprozess beendet (2a in Abb. 1). Andernfalls wird ein neuer Render Tree erstellt (2b). Der Render Tree beschreibt die HTML-Elemente des HTML-Dokuments, die in der Blazor-Welt aktualisiert werden sollen. Danach wird dieser an das Document Object Model (DOM) weitergeben. Anhand des Render Trees werden die Änderungen im DOM aktualisiert. Ist das Update abgeschlossen, werden schließlich die Methoden OnAfterRender und OnAfterRenderAsync aufgerufen und der Prozess ist beendet.

Hinweis: Der Render Tree beinhaltet nur die Änderungen der Komponenten, die sich im Vergleich zum aktuell gerenderten Zustand im DOM geändert haben. Dadurch wird nicht bei jedem Renderprozess das DOM-Element vollständig ersetzt, es werden nur die aktuellen Änderungen im DOM aktualisiert.

Weitere Informationen und Details über den Lebenszyklus von Blazor-Komponenten finden sich bei meinem Kollegen Pawel Gerr [3] in seinem englischen Artikel „Blazor Components Deep Dive – Lifecycle Is Not Always Straightforward“ [4].

Was kann ein Re-Rendering auslösen?

Nachdem wir gesehen haben, welchen Prozess eine Komponente beim Rendern durchläuft, schauen wir uns jetzt an, was den Renderprozess einer Komponente auslösen kann:

  • Das erste Rendern findet, wie zu erwarten, beim Initialisieren einer Blazor-Komponente statt.

  • Das zweite Szenario, bei der die Komponente neu gerendert wird, ist die Änderung von Parametern. Dabei wird zwischen eigenen Parametern und Parametern der übergeordneten Komponente unterschieden:

    • SetParametersAsync: Diese Methode wird aufgerufen, sobald Parameter des übergeordneten Elements geändert wurden oder ein Route-Parameter in den URL gesetzt wird.

    • OnParametersSet/OnParametersSetAsync: Diese Methoden werden aufgerufen, sobald sich die Parameter der Komponente ändern oder die übergeordnete Komponente neu gerendert wird.

  • Wird ein DOM-Event ausgelöst, beispielsweise durch ein onclick-Event, wird die Komponente ebenfalls neu gerendert. Aber nicht nur die Komponente, bei der das Event ausgelöst wurde, sondern auch alle untergeordneten Elemente.

  • Zuletzt kann der Prozess über die Methode StateHasChanged aus der Basisklasse ComponentBase der Renderprozess angestoßen werden. Die Methode StateHasChanged() benachrichtigt die Komponente, dass sich der aktuelle Status geändert hat. Das führt dann dazu, dass die Komponente neu gerendert wird.

 

 

 

Optimierung des Renderprozesses

Betrachtet man die unterschiedlichen Szenarien, wird ersichtlich, dass der Renderprozess sehr oft getriggert werden kann. Doch ist das wirklich immer notwendig? Im weiteren Verlauf dieses Artikels schauen wir uns mögliche Optionen an, durch die wir den Renderprozess einer Komponente optimieren können.

ShouldRender überschreiben

Die erste Option, die wir nutzen können, um den Renderprozess zu unterbinden, ist, die Methode ShouldRender zu überschreiben. Wie wir in Abbildung 1 gesehen haben, wird in Schritt 2, sobald es sich nicht um das initiale Rendering handelt und die Methode ShouldRender ein false zurückgibt, der Renderprozess beendet. Die Methode ShouldRender ist eine Methode der Basisklasse ComponentBase. Der Defaultrückgabewert der Methode ist true. Infolgedessen wird bei jedem Rendervorgang der Prozess durchgeführt. Nehmen wir beispielsweise ein Formular. Wird in einem Formular ein Feld geändert, wird dadurch der Renderprozess der Komponenten angestoßen. Das führt dazu, dass zusätzlich auch all die Komponenten neu gerendert werden, die sich im Formular befinden. In einem Formular ist es natürlich wichtig, zu beachten, ob das aktuelle Feld Abhängigkeiten zu anderen Feldern hat. Hat das Feld keine Abhängigkeiten, muss auch nicht bei jeder Änderung des Felds neu gerendert werden. Um das zu vermeiden, können wir die ShouldRender-Methode überschreiben (Listing 1).

Listing 1
// CustomInputText.razor.cs
private int _valueHashCode;
 
protected override bool ShouldRender()
{
  var lastHashCode = _valueHashCode;
  _valueHashCode = Value?.GetHashCode() ?? 0;
  return _valueHashCode != lastHashCode;
}

In Listing 1 sehen wir, dass anhand des HashCode die Property Value der Komponente geprüft wird, ob sich diese geändert hat. Ist das nicht der Fall, gibt die Methode false zurück und der Renderprozess wird beendet. Sollte die Komponente keine Properties haben, die sich ändern, kann hier natürlich auch immer direkt false zurückgegeben werden.

Das ist eine Möglichkeit, den Renderprozess frühzeitig zu beenden, wenn es nicht zwingend notwendig ist, die Komponente neu zu rendern. Im nächsten Abschnitt schauen wir uns an, wie wir beim Auslösen eines Events den Renderprozess optimieren können.

IHandleEvent implementieren

Wird ein Event ausgelöst, wird die Methode StateHasChanged der ComponentBase-Klasse aufgerufen. Das führt dazu, dass der Renderprozess der Komponente angestoßen wird. Das hat zwar den Vorteil, dass der Entwickler die Methode StateHasChanged nicht selbst aufrufen muss, wenn ein Event angestoßen wird. Jedoch hat es den Nachteil, dass die Komponente auch ohne jegliche Änderungen neu gerendert wird. Um in diesen Prozess einzugreifen, kann das Interface IHandleEvent implementiert werden. Über das Interface wird die Methode HandleEventAsync implementiert, die bei einem Event der Komponente aufgerufen wird.

Listing 2
// HandleEventInputText.razor.cs
 
public partial class HandleEventInputText : ComponentBase, IHandleEvent
{
  private bool _preventRender;
 
  public Task HandleEventAsync(EventCallbackWorkItem item, object? arg)
  {
    try
    {
      var task = item.InvokeAsync(arg);
      var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled;
 
      if (!_preventRender)
      {
        StateHasChanged();
      }
 
      return shouldAwaitTask
             ? CallStateHasChangedOnAsyncCompletion(task, _supressRender)
             : Task.CompletedTask;
    }
    finally
    {
      _preventRender = false;
     }
  }
 
  private async Task CallStateHasChangedOnAsyncCompletion(Task task, bool preventRender)
  {
    try
    {
      await task;
    }
    catch
    {
      if (task.IsCanceled)
      {
        return;
      }
 
      throw;
    }
 
    if (!preventRender)
    {
      StateHasChanged();
    }
  }
 
  void PreventRender()
  {
    _preventRender = true;
  }
}
 
<label>
  @Label <input id="@Id" class="form-control @CssClass" type="text" placeholder="Override ShouldRender" />
</label>

In Listing 2 sehen wir die Klasse HandleEventInputText, die von der Basisklasse ComponentBase ableitet und das Interface IHandleEvent implementiert. In der Methode HandleEventAsync wird anhand der Property _preventRender geprüft, ob die Komponente gerendert werden soll oder nicht. Dadurch haben wir in der Komponente die Möglichkeit, bei einem Event die Methode PreventRender aufzurufen, wie wir zuvor im Codebeispiel beim Event oninput sehen konnten. Infolgedessen bekommt die Property _preventRender den Wert true zugewiesen. Solange die Property den Wert true besitzt, wird der Renderprozess nicht angestoßen.

Event Utilities

Eine weitere Variante, das erneute Rendern bei Events zu vermeiden, ist der Einsatz der Hilfsklasse EventUtil [5], die von Microsoft empfohlen wird (Listing 3).

Listing 3
public static class EventUtil
{
  public static Action AsNonRenderingEventHandler(Action callback)
    => new SyncReceiver(callback).Invoke;
  public static Action AsNonRenderingEventHandler(Action callback)
    => new SyncReceiver(callback).Invoke;
  public static Func AsNonRenderingEventHandler(Func callback)
    => new AsyncReceiver(callback).Invoke;
  public static Func<TValue, Task> AsNonRenderingEventHandler(Func<TValue, Task> callback)
    => new AsyncReceiver(callback).Invoke;
 
  private record SyncReceiver(Action callback)
    : ReceiverBase { public void Invoke() => callback(); }
  private record SyncReceiver(Action callback)
    : ReceiverBase { public void Invoke(T arg) => callback(arg); }
  private record AsyncReceiver(Func callback)
    : ReceiverBase { public Task Invoke() => callback(); }
  private record AsyncReceiver(Func<T, Task> callback)
    : ReceiverBase { public Task Invoke(T arg) => callback(arg); }
 
  private record ReceiverBase : IHandleEvent
  {
    public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => item.InvokeAsync(arg);
  }
}

Die Hilfsklasse stellt die Methode AsNonRenderingEventHandler bereit. Die Methode kann genutzt werden, um die Func oder Action zwar auszuführen, aber kein erneutes Rendering des Event Handlers auszulösen.

Listing 4

  Beim Klick auf diesen Button wird kein neues Rendering ausgelöst
</MudButton>

In Listing 4 ist ein Button zu sehen, der einen OnClick Event Handler mit Hilfe der Methode AsNonRenderingEventHandler aufruft. Ähnlich wie im vorherigen Abschnitt wird auch hier das IHandleEvent-Interface eingesetzt, wie es beim Datentyp ReceiverBase zu sehen ist. Der Unterschied zum vorherigen Ansatz liegt darin, dass das Rendering direkt vermieden wird und vorab nicht geprüft werden kann, ob gerendert werden soll oder nicht. Im nächsten Abschnitt schauen wir uns an, wie wir mit dem Einsatz von JavaScript ein DOM Event verzögern können, um dadurch die Anzahl der Rendervorgänge zu reduzieren.

 

SIE LIEBEN UI?

Entdecken Sie die BASTA! Tracks

Debounce DOM Event

Hier vorab ein kleiner Hinweis: In diesem Abschnitt wird JavaScript-Interop eingesetzt. Da wir uns in diesem Artikel aber auf die Optimierung von Komponenten fokussieren, gehe ich nicht weiter auf JS-Interop ein. Mehr Informationen hierzu finden sich unter [6].

Ein aus der JavaScript-Welt bekanntes Verfahren, um das Anstoßen von Events zu verzögern, ist das Debouncing. Beim Debounce-Verfahren wird der Event Callback erst dann ausgeführt, wenn nach einer bestimmten Zeitspanne keine neuen Events mehr geworfen werden. Das heißt, wenn wir z. B. in einem Textfeld tippen, wird das Event oninput erst dann gefeuert, sobald nicht mehr getippt wird und eine selbst gesetzte Zeitspanne abgelaufen ist. Dies hat den Vorteil, dass das Event nur einmal ausgeführt wird. (Abb. 2 und 3).

 

Abb. 2: Rendering-Anzahl ohne Debouncing

Abb. 3: Rendering-Anzahl mit Debouncing

 

Um das Debounce-Verfahren in einer Blazor-WebAssembly-Komponente einzusetzen, müssen wir sowohl im C#-Code als auch im JavaScript-Code Methoden implementieren.

C#-/Razor-Code

Betrachten wir zunächst den C#-Code in Listing 5. In der Methode OnAfterRenderAsync wird beim initialen Rendering mit Hilfe von JS-Interop die Funktion onDebounceInput in JavaScript aufgerufen. Zum Aufrufen werden zwei Referenzen erwartet:

  • Die erste Referenz ist eine Objektreferenz der Komponente selbst, die mit Hilfe der Klasse DotNetObjectReference erstellt werden kann. Hinweis: Wird ein Disposeable-Objekt in einer Komponente genutzt, muss das Interface IDisposable implementiert werden. In der Methode Dispose müssen dann alle Objekte zerstört werden.

  • Die zweite Referenz verweist auf das HTML-Objekt. Im Codebeispiel wird hierfür im HTML-Code das Attribut @ref genutzt, um eine ElementReference im C#-Code zu erstellen.

Die anderen beiden Parameter, um die JavaScript-Methode aufzurufen, sind natürlich der Name der Methode und das Intervall für das Verzögern des Events. Weiter schauen wir uns die HandleOnInput-Methode näher an. Diese wird vom JavaScript-Code aufgerufen, sobald das Event geworfen wurde. Damit der JavaScript-Code die Methode aufrufen kann, wird das JSInvokable-Attribut benötigt. In der Methode selbst wird lediglich die Methode StateHasChanged aufgerufen, sobald sich der Wert der Property Value geändert hat.

Listing 5
// DebounceTextArea.razor.cs
 
public partial class DebounceTextArea : IDisposable
{
  //... Code above
 
  [Inject] public IJSRuntime JS { get; set; }
 
  private IJSObjectReference _module;
  private ElementReference _textareaElement;
  private DotNetObjectReference _selfReference;
 
  // JavaScript-Datei laden
  protected override async Task OnInitializedAsync()
  {
    _module = await JS.InvokeAsync("import","./Components/FormFields/DebounceTextArea.razor.js");
    await base.OnInitializedAsync();
  }
 
  // JavaScript Event registrieren
  protected override async Task OnAfterRenderAsync(bool firstRender)
  {
    if (firstRender)
    {
      _selfReference = DotNetObjectReference.Create(this);
 
      // Event wird nach 500 ms geworfen
      var minInterval = 500;
 
      // JavaScript-Code aufrufen mit dem HTML-Element,
      // einer ObjectReferenz und dem Timeout bis das Event geworfen wird
      await _module.InvokeVoidAsync("onDebounceInput", _textareaElement, _selfReference, minInterval);
    }
 
    Console.WriteLine("Debounced TextArea: After Render called.");
  }
 
 
  // Methode die vom JavaScript-Code aufgerufen werden kann. 
  // Als Parameter wird der aktuelle Wert der TextArea übergeben.
  [JSInvokable]
  public void HandleOnInput(string value)
  {
    Console.WriteLine($"TextChanged {Value}. JS Value {value}");
    if (Value != value)
    {
      StateHasChanged();
    }
  }
 
  //... Code below
 
  public void Dispose() => _selfReference?.Dispose();
}
 
<label>
  @Label <textarea @ref="_textareaElement" placeholder="Debounce input" class="form-control @CssClass" id="@Id" @bind="CurrentValue">
</label>

 

 

 

 

JavaScript-Code

Um die Methode HandleOnInput, die wir im vorigen Abschnitt gesehen haben, aufrufen zu können, müssen wir die JavaScript-Funktion onDebounceInput hinzufügen. Diese wird dann aus dem C#-Code via JS-Interop aufgerufen.

Listing 6
// DebounceTextArea.razor.js
export function onDebounceInput(elem, component, interval) {
  elem.addEventListener('input', debounce(e => {
    component.invokeMethodAsync('HandleOnInput', e.target.value);
  }, interval));
}
 
function debounce(func, timeout = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => { func.apply(this, args); }, timeout);
  };
}

Wie wir in Listing 6 sehen, wird im JavaScript-Code die Funktion onDebounceInput implementiert. Diese finden wir auch im C#-Code wieder, wenn wir nochmal auf die Methode OnAfterRenderAsync schauen. Hier wird, nachdem alle Parameter definiert wurden, die Funktion mit der folgenden Codezeile aufgerufen:

// DebounceTextArea.razor.cs
await JS.InvokeVoidAsync("onDebounceInput", _textareaElement, _selfReference, minInterval);

Nachdem die C#-Methode InvokeVoidAsync aufgerufen wurde, wird via JS-Interop die JavaScript-Funktion onDebounceInput aufgerufen. Die Funktion registriert einen EventListener der auf das Event input hört. Mit Hilfe der debounce-Funktion, wird der Aufruf der C#-Methode HandleOnInput so lange verzögert, bis das Intervall abgelaufen ist, das startet, sobald nicht mehr im Textfeld getippt wird.

Bevor wir uns nun im nächsten Abschnitt mit dem Darstellen von Listen beschäftigen, hier noch ein Hinweis: Das Überschreiben von ShouldRender oder das Implementieren des Interface IHandleEvent kann mit dem Debounce-Verfahren auch kombiniert werden.

 

Listenoptimierung mit Virtualize

Nachdem wir Möglichkeiten gesehen haben, um den Renderprozess von Komponenten in Blazor WebAssembly zu optimieren, schauen wir uns jetzt an, was passiert, wenn wir eine Liste rendern. Als Beispiel nutzen wir hier eine Liste, in der wir einzelne Einträge selektieren können (Abb. 4).

 

Abb. 4: Virtualisierung der Liste

Wie wir im vorherigen Abschnitt des Artikels schon gesehen haben, wird bei einem Event wie zum Beispiel bei einem OnClick der Renderprozess angestoßen. Infolgedessen haben wir bei einer Liste das Problem, dass bei jedem Klick auf ein Listenelement alle Einträge neu gerendert werden. Zusätzlich müssen bei einem for-Loop von Anfang an alle Einträge in das DOM geladen werden. Zur Optimierung kann die Virtualize-Komponente genutzt werden, die von Blazor WebAssembly zur Verfügung gestellt wird. Die Komponente bewirkt, dass nur die Komponenten geladen werden, die sich im Sichtbereich der Anwendung befinden. Das hat den großen Vorteil, dass, auch wenn über das API die Daten nicht nach und nach geladen werden können, nicht alle Einträge direkt dem DOM hinzugefügt werden. Zusätzlich kann über den Parameter OverscanCount vor und nach dem Sichtbereich eine gewisse Anzahl an Einträgen im DOM vorgeladen werden. Daher ist eine wichtige Voraussetzung zum Einsetzten der Virtualize-Komponente, dass nicht alle Einträge einer Liste im DOM gerendert sein müssen.

Listing 7
<Virtualize Context="contribution" ItemsProvider="@LoadContributions" OverscanCount="10">
  <ItemContent>
    <ContributionCard Contribution="@contribution">
  </ItemContent>
  <Placeholder>
    <PlaceholderCard>
  </Placeholder>
</Virtualize>
 
// Contributions.razor.cs
 
public partial class Contributions
{
  [Inject] public ContributionService ContributionService { get; set; }
 
  private async ValueTask<ItemsProviderResult> LoadContributions(
    ItemsProviderRequest request)
  {
    var maxCount = 200;
    var numConfs = Math.Min(request.Count, maxCount - request.StartIndex);
    var contributions = await ContributionService.GetContributionsAsync(request.StartIndex, numConfs, request.CancellationToken);
    return new ItemsProviderResult(contributions, maxCount);
  }
}

In Listing 7 sehen wir die Virtualize-Komponente. Die Liste an Personen wird mit dem ItemProvider geladen. Damit beim Scrollen der Liste keine zu große Verzögerung auftritt, werden mit dem OverscanCount vor und nach dem Sichtbereich 10 Einträge vorgehalten. Der ItemProvider bietet die Möglichkeit, die Daten via Lazy Loading nachzuladen. Wir sehen in Listing 7 die Methode LoadContributions, die immer dann aufgerufen wird, wenn neue Einträge geladen werden müssen. Können die Daten nicht schnell genug nachgeladen werden, beispielsweise durch ein langsames API oder eine schlechte Internetverbindung, bietet die Virtualize-Komponente die Möglichkeit, einen Placeholder einzusetzen. Dieser wird so lange angezeigt, bis die nächsten Einträge geladen wurden.

Durch den Einsatz der Virtualize-Komponente haben wir also die Möglichkeit, die Performance zu optimieren. Jedoch kann dies nicht immer mit den Optimierungen der vorherigen Abschnitte verbunden werden. Das liegt daran, dass Komponenten, die nicht im Sichtbereich liegen oder vorgeladen wurden, aus dem DOM entfernt wurden. Wenn diese wieder geladen werden, handelt es sich um das Initialisieren der Komponente, was automatisch zu einem Renderprozess führt.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Fazit

In diesem Artikel haben wir mögliche Varianten gesehen, den Renderprozess von Blazor-WebAssembly-Komponenten zu verbessern. Schon mit kleinen Anpassungen können viele Rendervorgänge vermieden werden, was sich positiv auf die Performance der ganzen Anwendung auswirken kann. Wichtig dabei ist zu beachten, wann welche Komponente gerendert werden muss und wann nicht.

Beim Überschreiben der Methode ShouldRender und dem Implementieren des Interface IHandleEvent konnten wir sehen, dass bereits viele Rendervorgänge eingespart wurden. Doch auch wenn sich ein Rendering nicht vermeiden ließ, konnten wir durch den Einsatz von JavaScript bei einem Event die Anzahl der Rendervorgänge minimieren.

Mit Hilfe der Virtualize-Komponente konnten viele Funktionen eingesetzt werden, wie beispielsweise Lazy Loading oder eine Ladeanimation als Placeholder. Das trägt nicht nur zur besseren Performance bei, sondern auch zu einer erhöhten Usability der Anwendung. Daher lässt sich abschließend sagen, dass die Performance deutlich gesteigert werden kann, wenn man beim Entwickeln von Komponenten darauf achtet, so selten wie möglich zu (re-)rendern.

 

Links & Literatur

[1] https://github.com/thinktecture-labs/article-blazor-component-performance

[2] https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs

[3] https://www.thinktecture.com/de/pawel-gerr

[4] https://www.thinktecture.com/de/blazor/blazor-components-lifecycle-is-not-always-straightforward/

[5] https://gist.github.com/SteveSandersonMS/8a19d8e992f127bb2d2a315ec6c5a373

[6] https://docs.microsoft.com/de-de/aspnet/core/blazor/call-javascript-from-dotnet?view=aspnetcore-6.0

The post Mehr Performance für Komponenten appeared first on BASTA!.

]]>
Einführung in Azure Arc https://basta.net/blog/einfuehrung-in-azure-arc/ Tue, 14 Jun 2022 08:46:21 +0000 https://basta.net/?p=87248 Azure Arc bietet eine Reihe von Tools zur zentralen Verwaltung von Infrastruktur auf Azure, die außerhalb von Azure betrieben wird. Dadurch können gewohnte Dienste wie Azure Policy oder Azure Monitor verwendet und zusätzlich erstmals Dienste wie Azure App Service oder Managed-SQL-Instanzen on premises oder bei einem anderen Cloud-Provider betrieben werden.

The post Einführung in Azure Arc appeared first on BASTA!.

]]>
Cloud-Dienste sind aus der modernen IT-Welt nicht mehr wegzudenken. Allerdings gibt es noch immer viele Firmen und Projekte, die auf On-Premises-Infrastruktur setzen. Das kann unterschiedliche Gründe haben, sei es Aufgrund rechtlicher Bestimmungen, Datenschutz, oder um bereits bestehende Infrastruktur zu nutzen. Eine weitere Komplexität für die Verwaltung der IT-Infrastruktur bringt die Nutzung einer Multi-Cloud-Strategie. Dabei werden verschiedene Cloud-Provider, zum Beispiel Azure und AWS, genutzt, um Projekte zu realisieren. Das kann so sein, um die Ausfallsicherheit zu erhöhen oder weil ein benötigter Service nur bei einem bestimmten Anbieter vorhanden ist.

Die Implementierung einer Hybrid-Cloud- oder Multi-Cloud-Strategie wird schnell komplex und die Verwaltung aller verwendeter Dienste wird zu einer großen Herausforderung für die Administratoren.

Hierfür bietet Microsoft mit Azure Arc eine am Markt einzigartige Lösung an. Azure Arc ist eine Management-as-a-Service-Plattform, mit der Administratoren Infrastruktur mit Hilfe von Azure verwalten können. Dabei spielt es keine Rolle, ob diese on premises oder bei einem anderen Cloud-Anbieter gehostet sind. Sobald Azure Arc eingerichtet ist, kann die Infrastruktur auf die gleiche Weise verwaltet werden, als würde diese auf Azure laufen. Das ermöglicht auch die Verwendung von Azure-spezifischen Diensten wie zum Beispiel das Vergeben von Tags oder den Einsatz von Azure Policy. Azure Arc bietet dafür eine einheitliche Übersicht (englisch: Pane of Glass), in der alle Dienste dargestellt und verwaltet werden können.

 

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Übersicht über die Azure Arc Services

Mit Hilfe von Azure Arc können eine Vielzahl unterschiedlichster Services verwaltet werden. Diese Services können den folgenden Kategorien zugeordnet werden:

  • Azure-Arc-fähige Server

  • Kubernetes mit Azure-Arc-Unterstützung

  • Azure-Arc-fähige Anwendungsdienste

  • Azure-Arc-fähige Datendienste

Abbildung 1 zeigt grafisch, wie Ressourcen von verschiedenen Anbietern und Orten zentral mit Azure Diensten verwaltet werden können.


Abb. 1: Übersicht über den Aufbau von Azure Arc

In den folgenden Abschnitten werden wir die Services der einzelnen Kategorien präsentieren und auf die darin enthaltenen Dienste eingehen.

 

Azure-Arc-fähige Server

Die Verwaltung von Servern, sowohl virtuelle Maschinen als auch physische Server, ist das älteste und am besten ausgereifte Feature von Azure Arc. Azure Arc erlaubt Administratoren, Server so zu verwalten, als würden sie auf Azure laufen. Dabei ist es egal, ob diese Server im eigenen Rechenzentrum oder bei einem anderen Cloud-Provider, zum Beispiel AWS, gehostet werden. Außerdem können Linux- und Windows-Server gleichermaßen verwaltet werden.

Um Server mit Azure Arc verwalten zu können, muss ein Skript auf dem jeweiligen Server ausgeführt werden, das den Azure Connected Machine Agent herunterlädt und konfiguriert. Dieser Agent stellt eine Verbindung mit Azure Arc her und verwaltet die verschlüsselte Kommunikation zwischen Azure und dem Server. Nachdem der Agent eingerichtet ist, ist der Server im Azure Arc Service sichtbar und kann genauso wie Azure VMs verwaltet werden. Dadurch können alle Azure Services zur Verwaltung von VMs verwendet werden. Dabei spielt es keine Rolle, ob der Server über das Internet erreichbar ist oder nicht. Der Server muss nur eine ausgehende Verbindung über Port 443 (HTTPS) mit dem Internet aufbauen können.

Eines der nützlichsten Features zur Verwaltung von Servern ist das Updatemanagement. Dieser Service ist komplett gratis und gibt Administratoren eine Übersicht über alle installierten Updates auf den von Azure Arc verwalteten Servern. Außerdem können auf diesen Servern mittels Azure Updates installiert werden. Dadurch können alle Azure-Arc-Server und Azure VMs mit den gleichen Tools verwaltet werden, wodurch der Verwaltungsaufwand deutlich gesenkt werden kann.

Neben der Verwaltung von Updates kann mit Hilfe von Azure Arc auch die Konfiguration der Server verwaltet und angepasst werden. Zusätzlich zur Verwaltung der Konfiguration kann eine Übersicht über die installierte Software pro Server erstellt werden. Das ist vor allem in Kombination mit der Verfolgung von Änderungen sehr nützlich. Dieses Feature zeichnet alle Änderungen der installierten Software, Windows- und Linux-Systemdateien oder Registry-Einträgen auf. Dadurch können Administratoren jederzeit sehen, welche Software bzw. welche Systemeinstellungen geändert wurden. Neben der Softwareverwaltung kann auch der Benutzerzugriff mit der gewohnten RBAC (Role-based Access Control) konfiguriert und überwacht werden.

In die gleiche Richtung wie die Verfolgung von Änderungen geht Azure Monitor. Mit Hilfe dieses Dienstes können die Azure Arc Server überwacht werden. Der Azure Monitor Agent auf dem Server sendet Informationen über die Ressourcenauslastung oder über den Zustand des Servers. Diese Informationen können dann mit Hilfe von Azure Monitor übersichtlich dargestellt werden und im Fall eines Problems kann eine zuständige Person oder das Team benachrichtigt werden.

Die letzten zwei Features, die wir hier kurz vorstellen möchten, sind Microsoft Defender für Cloud und Azure Policy. Beide Tools helfen, die Sicherheit der Server zu erhöhen. Der Microsoft Defender für Cloud überwacht den Sicherheitsstatus der Server und schützt vor Cyberangriffen. Mit Hilfe von Azure Policy können Administratoren Regeln vorgeben, die die Server einhalten müssen. Diese Regeln können zum Beispiel die Passwortkomplexität beinhalten oder auch, dass jeder Server Auditlogs erstellen muss.

Die Managementoberfläche für Azure Arc stellt Microsoft kostenlos zu Verfügung. Für die Verwendung von Azure Policy und der Überwachung der Änderung müssen 6 Dollar (in etwa 5,40 Euro) pro Server und Monat bezahlt werden.

 

Kubernetes mit Azure-Arc-Unterstützung

Neben der Verwaltung von Servern kann mit Azure Arc auch ein Kubernetes-Cluster verwaltet werden. Hierbei ist es wieder egal, wo dieser Cluster gehostet wird, on premises oder bei einem anderen Cloud-Anbieter. Außerdem wird jeder CNCF-zertifizierte Kubernetes-Cluster [1] unterstützt.

Um einen Kubernetes-Cluster mit Azure Arc verwalten zu können, muss man den Azure-Arc-Client mittels Azure-CLI-Befehl installieren. Außerdem benötigt man für die Installation die connectedk8s-Azure-CLI-Erweiterung. Der folgende Befehl zeigt, wie sie installiert und anschließend zur Installation des Azure Arc Agents genutzt wird. Die Azure-CLI-Befehle müssen an dem Kubernetes-Cluster ausgeführt werden, der zu Azure Arc hinzugefügt werden soll.

az extension add --name connectedk8s
az connectedk8s connect --name  --resource-group <RESOURCE GROUP>

 

Die Installation erstellt den neuen Namespace azure-arc und installiert dort den Azure Arc Agent und weitere Komponenten wie zum Beispiel einen Config Agent und Azure Active Directory Proxy. Abbildung 2 zeigt alle installierten Komponenten im azure-arc Namespace.

Abb. 2: Übersicht der Komponenten im azure-arc Namespace

 

Nachdem der Agent installiert ist, kann man den Kubernetes-Cluster im Service Kubernetes | Azure Arc im Azure Portal sehen. Dort sieht man die installierte Kubernetes-Version des Clusters, die Distribution sowie alle Ressourcen, die im Cluster laufen. Abbildung 3 zeigt den azure-arc Namespace und alle Pods des Namespace.

Abb. 3: Übersicht Azure Arc im Azure Portal

 

 

 

Außerdem sieht man auf der linken Seite von Abbildung 3, dass es die Möglichkeit gibt, Erweiterungen (Extensions) und Policies zu installieren sowie mit Hilfe von GitOps automatisierte Deployments beziehungsweise Continuous Deployments (CD) zu konfigurieren.Die Erweiterungen sind zum Beispiel Container Insights, Azure Key Vault als Secrets Provider und Microsoft Defender für Cloud. Container Insights sammelt Metriken der Pods im Cluster and sendet diese an Azure Monitor. Dort können sie grafisch dargestellt werden, um eine Übersicht über die Auslastung der Ressourcen zu bekommen. Azure Key Vault als Secrets Provider ist eine sehr interessante Erweiterung. Dieses Add-on erlaubt es, Kubernetes Secrets direkt im Azure Key Vault zu speichern. Dadurch sind die Secrets verschlüsselt und sicher gespeichert. Außerdem hat man so alle Azure Key Vault Features, wie zum Beispiel die Zugriffskontrolle und Auditierung der Zugriffe und Änderungen.Der Azure Arc Agent kommuniziert mit Azure über eine HTTPS-Verbindung. Dadurch ist die Verbindung verschlüsselt; zusätzlich braucht es keine offenen Ports auf der Firewall (außer Port 443 natürlich, der allerdings meistens nach außen offen ist). Da der Azure Arc Agent die Verbindung herstellt, muss nur die ausgehende Verbindung erlaubt werden und alle eingehenden Verbindungen können blockiert sein.

GitOps erlaubt es, automatisierte Deployments zu konfigurieren. Dafür wird ein Flux Agent im Cluster installiert, der über das Azure CLI oder das Azure Portal konfiguriert werden kann. Er prüft ein konfiguriertes Git-Repository auf Änderungen, und falls der Agent Änderungen feststellt, werden diese in den Cluster geladen und installiert.

Die Managementoberfläche für Azure Arc stellt Microsoft kostenlos zu Verfügung. Die Verwaltung der Kubernetes-Konfiguration ist für die ersten sechs vCPUs kostenlos und kostet für jede weitere vCPU 2 Dollar (in etwa 1,80 Euro) pro vCPU pro Monat.

Im nächsten Teil der Serie wird ein Proof-of-Concept eines Kunden vorgestellt. Dieser hat einen lokalen K3s-Cluster, der nicht über das Internet erreichbar ist. Dadurch war es ursprünglich nicht möglich, Anpassungen oder Deployments von außerhalb des Netzwerks, etwa von Azure DevOps aus, vorzunehmen. Mit Azure Arc war es möglich, weiterhin alle eingehenden Verbindungen zu blockieren und trotzdem sichere Deployments und Anpassungen außerhalb des Netzwerks durchzuführen.

 

Azure-Arc-fähige Anwendungsdienste

Anfang dieses Jahres hat Microsoft die Preview der Azure-Arc-fähigen Anwendungsdienste (Azure Arc-enabled Application Services) veröffentlicht. Mit diesem Dienst ist es erstmals möglich, bestimmte Azure Services wie zum Beispiel Azure App Service oder Azure Logic Apps außerhalb von Azure zu betreiben. Folgende Services sind aktuell verfügbar:

  • Azure App Service

  • Azure Functions

  • Azure Logic Apps

  • Azure API Management

  • Azure Event Grid

Als Grundlage für diese Services wird ein Kubernetes-Cluster benötigt, der mit Azure Arc verbunden ist. Auf diesem Cluster kann dann eine Erweiterung für den gewünschten Service installiert werden. Wichtig zu beachten ist hierbei, dass diese Services sich in einer früher Phase der Preview befinden und viele Features der normalen Services noch nicht implementiert sind. Zum Beispiel können Azure App Services derzeit nur installiert werden, wenn sich die Azure-Arc-Instanz in East US oder West Europe befindet. Zusätzlich fehlt die Integration mit dem Azure Key Vault und die Möglichkeit, Managed Identities zu verwenden.

Als Benutzer eines dieser Dienste merkt man nicht, ob er auf Azure oder mits Azure Arc außerhalb betrieben wird. Eine neue Instanz eines Azure App Service kann wie gewohnt über das Azure Portal oder das Azure CLI erstellt werden. Zusätzlich zu den Azure-Regionen kann man selbst erstellte Regionen auswählen, die die Installation mittels Azure Arc darstellen. Wenn eine Azure-Arc-Region ausgewählt wurde, installiert Azure Arc einen neuen Namespace für den App Service im Kubernetes-Cluster und der Benutzer kann den Service wie gewohnt verwenden. Das bedeutet, dass der App Service mit einem öffentlichen URL und via HTTPS erreicht und bei Bedarf hinsichtlich der CPU- oder RAM-Auslastung skaliert werden kann.

Für die Verwaltung des App-Service-Diensts im Kubernetes-Cluster installiert Azure Arc die App-Service-Erweiterung. Zusätzlich kann optional KEDA (Kubernetes Event-driven Architecture [2]) installiert werden, um anhand von Events die Anwendung zu skalieren.

Da alle Dienste noch in der Preview sind, können sie derzeit kostenlos verwendet werden. Microsoft hat noch keine endgültigen Preise veröffentlicht.

Die Azure-Arc-fähigen Anwendungsdienste werden im dritten Teil dieser Serie detaillierter betrachtet.

 

SIE LIEBEN AZURE?

Entdecken Sie die BASTA! Tracks

 

Azure-Arc-fähige Datendienste

Ein Grund, weshalb viele Organisationen sich gegen eine Cloud-Lösung entscheiden, ist die Datenhaltung auf fremder Infrastruktur. Mit Azure Arc besteht nun die Möglichkeit, die Datenhaltung auf der gewünschten Infrastruktur zu betreiben und trotzdem von den Vorteilen eines von der Cloud verwalteten Service zu profitieren. Zu den Vorteilen zählen automatische Updates, flexible Skalierung und eine zentralisierte Verwaltung der Datenbank. Durch automatisierte Updates müssen sich Administratoren nicht mehr darum kümmern. Zusätzlich können Datenbanken automatisch auf eine neue Version aktualisiert werden. Durch flexible Skalierung können die zur Verfügung stehenden Ressourcen dynamisch nach dem aktuellen Bedarf skaliert werden. Neben der Skalierung der Ressourcen können weitere Lesereplikate ohne Ausfallzeit hinzugefügt werden. Das kann automatisch mittels Skripten oder auch manuell erfolgen. Wie bei den anderen Azure-Arc-fähigen Services ist es auch hier möglich, die Datendienste durch Azure zentral und bequem mit den gewohnten Tools wie dem Azure Portal, Azure Data Studio oder Azure CLI zu verwalten.

Somit können Kunden Azure SQL Managed Instance und Azure PostgreSQL Hyperscale außerhalb von Azure betreiben. Im Gegensatz zu Azure-Arc-fähigen Anwendungsdiensten ist diese Kategorie ausgereifter und es stehen 16 Regionen, verteilt über die USA, Europa und Asien, zur Verfügung.

Wie schon bei den Azure-Arc-fähigen Anwendungsdiensten, beruhen auch die Azure-Arc-fähigen Datendienste auf der Grundlage von Kubernetes. Beide Datendienste, Azure SQL mit Azure-Arc-Unterstützung sowie PostgreSQL Hyperscale mit Azure-Arc-Unterstützung, laufen als containerisierte Applikationen. Um nun die erwähnten Vorteile von verwalteten Datendienste zu ermöglichen, erstellt und verwendet Azure Arc den Azure Arc Data Controller, der ebenfalls in einem Container im Kubernetes-Cluster läuft. Mittels Azure Arc Data Controller werden Verwaltungsfunktionalitäten wie Monitoring, Logging, Patching/Upgrading, Skalierung und Back-up ermöglicht. Weiter dient es dazu, den Austausch von Azure mit Identity Management, RBAC und Azure Policy sowie das Senden von Telemetriedaten zu gewährleisten. Dafür bietet Azure Arc mit der direkten und indirekten Verbindung zwischen Azure und Azure-Arc-fähigen Datendienste zwei Konnektivitätsmodi an. So kann mit der indirekten Verbindung die Menge an Daten, die an Azure gesendet werden, eingeschränkt werden. Jedoch bietet diese Option weniger Funktionalitäten, zum Beispiel nur noch halbautomatisierte Updates.

Abb. 4: Architektur des Azure-Arc-fähigen Datendiensts [3]

Um den Azure-Arc-fähigen Daten-Service zu verwenden, muss der Azure Arc Data Controller installiert werden. Abbildung 4 zeigt die Architektur und verdeutlicht nochmals, dass ein Kubernetes-Cluster als Basis für diesen Service benötigt wird. Die Installation kann entweder per Kommandozeile mittels Azure CLI oder über eine Benutzeroberfläche erfolgen.

Listing 1 zeigt alle benötigten Befehle, um den Azure Arc Data Controller auf dem Cluster zu installieren. Während der Installation muss der Administrator den Benutzernamen und das Passwort für den Adminbenutzer des Monitorings eingeben.

Listing 1
az provider register -n 'Microsoft.Kubernetes'
az provider register -n 'Microsoft.ExtendedLocation'
az extension add --name arcdata 
az extension add --name k8s-extension
az extension add --name customlocation
 
az customlocation create 
  --cluster-extension-ids <CLUSTER_EXTENSION_IDS> `
  --host-resource-id <KUBERNETES_RESOURCE_ID> `
  --name <CUSTOM_LOCATION_NAME > `
  --namespace <CUSTOM_LOCATION_NAMESPACE> `
  --resource-group <RESOURCE_GROUP> `
 
az arcdata dc create `
  --name <DATA_CONTROLLER_NAME> `
  --resource-group <RESOURCE_GROUP> `
  --cluster-name <CLUSTER_NAME> `
  --k8s-namespace <K8s_DATA_CONTROLLER_NAMESPACE> `
  --custom-location <CUSTOM_LOCATION_NAME> `
  --location <METADATA_LOCATION> `
  --connectivity-mode direct

 

Der Code in Listing 1 registriert zunächst die benötigten Provider mit Azure und installiert dann drei Azure-CLI-Erweiterungen. Anschließend wird eine Custom Location im Kubernetes-Cluster installiert. Dabei muss eine mit Abstand separierte Liste der IDs der installierten Erweiterungen angegeben werden und dann die ID des Kubernetes-Clusters. Die folgenden Parameter sollten selbsterklärend sein. Microsoft führt derzeit recht oft Anpassungen an den Parametern durch. Daher sollte vor der Verwendung die offizielle Dokumentation überprüft werden [4].

Nachdem die Custom Location erstellt ist, kann der Data Controller installiert werden. Dazu werden der Name der zuvor erstellten Custom Location und Informationen zum Kubernetes-Cluster benötigt, zum Beispiel der Name und in welchem Namespace der Data Controller installiert werden soll. Der Location Parameter gibt dabei die Region an, in der die Metadaten des Controllers gespeichert werden. Auch hier sollte die offizielle Dokumentation vor der Installation überprüft werden [5]. Die Installation kann je nach Konfiguration, Netzwerkgeschwindigkeit und der Anzahl an Nodes im Cluster eine längere Zeit in Anspruch nehmen.

Folgende Codezeile zeigt, wie man mit dem Kubernetes CLI kubectl überprüfen kann, ob die Installation abgeschlossen ist.

kubectl get pods --namespace <K8s_NAMESPACE>

Sobald die Installation erfolgreich abgeschlossen ist, kann mit der Provision einer Datenbankinstanz gestartet werden. Hierfür wird der in Listing 2 dargestellte Befehl ausgeführt. Um einen Datenbankadministrator anzulegen, wird hier während der Installation nach einem Benutzernamen und Passwort gefragt.

Listing 2
az sql mi-arc create `
  --name <DB_NAME> `
  --resource-group <RESOURCE_GROUP> `
  --admin-login-secret <SECRET_NAME_ADMIN_CREDENTIALS_KUBERNETES> `
  --custom-location <CUSTOM_LOCATION> `
  --location <METADATA_LOCATION>

Der location-Parameter gibt wie zuvor die Region an, in der die Metadaten der Datenbank in Azure gespeichert werden sollen. admin-login-secret konfiguriert den Namen eines Secrets in Kubernetes, in dem die Anmeldedaten des Administratoraccounts gespeichert sind. Weiterhin gibt es noch eine Unzahl an optionalen Parametern, die aus der offiziellen Dokumentation ersichtlich sind [6].

Sobald die damit erstellten Pods gestartet wurden, sind diese im Portal zu sehen. Abbildung 5 zeigt das Azure Portal, in dem der externe Endpunkt (die IP-Adresse und der Port) der Datenbank angezeigt wird. Mit diesem Endpunkt und den Anmeldedaten kann nun mittels Azure Data Studio oder auch SQL Server Management Studio auf die Datenbank zugegriffen werden.

Abb. 5: Sichtbarkeit der SQL-Instanz nach der erfolgreichen Installation

 

1 vCore/Monat Pay as you go 1 Jahr reserviert 3 Jahre reserviert
Lizenz inklusive ca. 138 Euro ca. 102 Euro ca. 84 Euro
Azure Hybrid Benefit ca. 72 Euro ca. 36 Euro ca. 18 Euro

Tabelle 1: Preismodell für Azure-Arc-fähige SQL-Instanzen

 

 

 

Azure Arc Jumpstart

Für Interessierte, die Azure Arc gerne ausprobieren möchten, allerdings von all den benötigten Voraussetzung wie zum Beispiel dem Aufsetzen eines Kubernetes-Clusters abgeschreckt sind, bietet Microsoft mit Azure Arc Jumpstart [7] eine Möglichkeit, Azure Arc schnell auszuprobieren und kennenzulernen. Azure Arc Jumpstart bietet eine Vielzahl von Szenarien mit einer detaillierten Beschreibung und den benötigten Skripten, um die notwendigen Ressourcen zu erstellen und zu konfigurieren. Die erstellten Ressourcen laufen in einer Sandbox, um das sichere Ausprobieren zu gewährleisten.

 

Fazit

Mit Azure Arc bietet Microsoft eine am Markt einzigartige Management-as-a-Service-Plattform, die es Administratoren erlaubt, Infrastrukturen mit den gewohnten Azure-Diensten zu verwalten. Dabei spielt es keine Rolle, ob diese Infrastruktur on premises oder bei einem anderen Cloud-Anbieter gehostet ist. Zusätzlich können mit Azure Arc Azure-Dienste wie Azure-App-Service- oder Azure-Managed-SQL-Instanzen außerhalb von Azure betrieben werden.

Teil 2 dieser Serie wird zeigen, wie ein On-Premises-K3s-Cluster mit Azure Arc verwaltet werden kann und wie mit Hilfe der GitOps-Erweiterung automatisierte Deployments konfiguriert werden können.

 

Links & Literatur

[1] https://www.cncf.io/certification/software-conformance/

[2] https://entwickler.de/kubernetes/ein-level-up-fur-die-skalierung-in-k8s

[3] https://techcommunity.microsoft.com/t5/azure-arc-blog/azure-arc-enabled-data-services-overview/ba-p/3254379

[4] https://docs.microsoft.com/en-us/cli/azure/customlocation?view=azure-cli-latest#az-customlocation-create

[5] https://docs.microsoft.com/en-us/cli/azure/arcdata/dc?view=azure-cli-latest#az-arcdata-dc-create

[6] https://docs.microsoft.com/en-us/cli/azure/sql/mi-arc?view=azure-cli-latest#az-sql-mi-arc-create

[7] https://azurearcjumpstart.io

The post Einführung in Azure Arc appeared first on BASTA!.

]]>
Controls für WinUI 3 – funktionell und ansprechend im Design https://basta.net/blog/moderne-bausteine-fuer-windows-apps/ Wed, 20 Apr 2022 09:12:50 +0000 https://basta.net/?p=86034 Windows wird an allen Ecken und Enden renoviert. Die neue Version Windows 11, das Windows App SDK und WinUI 3 sind die relevanten Stichwörter. Auch bei der Gestaltung des User Interface muss man sich neu orientieren. Zum Einsatz kommen frische Controls, die ein grafisch ansprechendes Design und eine hohe User Experience bieten.

The post Controls für WinUI 3 – funktionell und ansprechend im Design appeared first on BASTA!.

]]>
Sehr lange Zeit hat sich im Bereich der Desktopanwendungen recht wenig getan. Zur Gestaltung der Benutzeroberflächen gab es die Systeme Windows Forms (WinForms) und Windows Presentation Foundation (WPF). Ein gewisse Sonderrolle haben stets die Apps für die Universal Windows Platform (UWP) eingenommen, die auf der Grafikschnittstelle WinUI 2 basieren. Sonderrolle deshalb, weil diese Apps zwar auch auf Desktoprechnern unter Windows ausgeführt werden können, jedoch in vielerlei Hinsicht eingeschränkt sind. Beispielsweise ist nur ein begrenzter Systemzugriff auf die Hardware des Rechners möglich. Ihre Nutzung hat sich daher im Bereich der Desktopanwendungen nur sehr zögerlich durchgesetzt. Andererseits sind alle Innovationen in Bezug auf Design und User Experience fast ausschließlich für UWP/WinUI 2 umgesetzt worden. Hier gab es gegenüber den Technologien WinForms und WPF echte Innovationen. Ansprechende Controls, Farbverläufe, Schatten, Animationen usw. konnten für Apps der UWP ohne größere Hürden angewendet werden. Mit WinUI 3 und dem App SDK wird nun bekanntermaßen ein Systemwechsel eingeleitet. Die neue Grafikbibliothek WinUI 3 wird vom Applikations- und Programmiermodell der Anwendung entkoppelt und kann nun mit unterschiedlichen App-Typen, d. h. mit Apps für die UWP und klassischen Desktopapplikationen, genutzt werden. Auch andere Technologien zur Erstellung von Windows-Applikationen, die durch diverse Werkzeughersteller angeboten werden, sollen künftig WinUI 3 als Grafikschnittstelle nutzen können.

Leser des Windows Developer sind hier sehr gut informiert. Sowohl über die Features der genannten grafischen Benutzerschnittstellen als auch über die laufende Entwicklung des App SDK und über das WinUI 3 wurde hier schon aus unterschiedlicher Perspektive berichtet (WD 10.20, 7.21und 12.21). In diesem Artikel werfen wir einen konzentrierten Blick auf die Arbeit mit den Controls von WinUI 3. Die Basis für die Gestaltung moderner grafischer Oberflächen ist auch bei WinUI 3 der Einsatz von fertigen Komponenten. Eine Basisauswahl, die sukzessive durch Microsoft erweitert wird, ist bereits verfügbar und erlaubt schon heute das Erstellen von ansprechenden Benutzeroberflächen. Je mehr dieses System in den Fokus der Entwickler rückt, desto umfassender wird auch die Unterstützung mit Controls von Drittanbietern ausfallen.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Entwickler, die nun zunehmend von WinForms und WPF zur Grafikschnittstelle WinUI 3 wechseln und vielleicht mit dem technischen Vorgänger WinUI 2 (UWP) nicht viel gearbeitet haben, werden über die Funktionsweise und das Design einiger Controls vielleicht überrascht sein. Natürlich gibt es immer noch Basis-Controls wie einen Button, ein statisches Textfeld (Label) oder ein Texteingabefeld. Aber es gibt auch deutlich umfassendere visuelle Controls, die einen ganz anderen Ansatz zur Gestaltung des User Interface bzw. die Umsetzung einer vollständig veränderten User Experience ermöglichen. Wer bereits Apps für die mobilen Plattformen entwickelt hat, der wird hier viele Ähnlichkeiten vorfinden. Die Grafikschnittstelle WinUI 3 erfordert zum einem eine Konzentration auf das Layout und zum anderen eine Fokussierung auf den Inhalt. Content First ist auch hier ein wichtiges Prinzip, d. h., dass die Navigation zwischen den Seiten und der Aufbau einer Seite mehr dem Vorgehen auf mobilen Geräten gleichen als der Entwicklung klassischer Desktopanwendungen. Mit anderen Worten: Moderne Desktopanwendungen sind heute auch das Ergebnis eines umfassenden Designprozesses. Mit langweiligen Formularen, vielleicht noch in einem mausgrauen Farbton gestaltet, haben diese heute nichts mehr gemeinsam.

Hinweis: Es gilt ganz besonders das Prinzip „Probieren geht über Studieren“. Deshalb finden Sie im Kasten „Apps für WinUI 3 erstellen“ einige Hinweise, um das Gerüst für eine Desktopanwendung mit WinUI 3 zu generieren.

 

Apps für WinUI 3 erstellen

Installieren Sie die aktuelle Version von Visual Studio 2022. Spielen Sie alle Updates zur Entwicklungsumgebung ein. Installieren Sie die folgenden Workloads: .NET-Desktopentwicklung, Desktop development with C++ und Entwicklung für die universelle Windows-Plattform. Ebenso ist es notwendig, dass Sie das Windows-App-SDK installieren. Die aktuelle Version finden Sie unter [1], wenn Sie im Store für die Extensions von Visual Studio nicht fündig werden. Mit der Windows-App-SDK-Extension wandern neue Projektvorlagen für Visual Studio auf Ihren Rechner.

Als Projektvorlage wählen Sie dann Leere App, verpackt (WinUI 3 in Desktop). Ausgangspunkt für unsere Experimente ist die Datei MainWindow.xaml. Hier erfolgt die Deklaration der Benutzerschnittstelle. Starten Sie die App ein erstes Mal, danach sind Sie bereit für Ihre Erkundungstour. Einen Designer gibt es (noch) nicht, vielmehr wird Hot Reload als interaktive Hilfe für das Erstellen des User Interface verwendet.

Controls für WinUI 3

Starten wir mit einem ersten Überblick über die verfügbaren Controls, die es bereits heute für WinUI 3 gibt. Sie finden die Auflistung in Abbildung 1.


Abb. 1: Übersicht über die Controls von WinUI 3 [1]

Es empfiehlt sich, hier die englische Seite der Dokumentation aufzurufen und zu verwenden. Die Namen der Controls sind dann mit den Klassennamen identisch, dagegen wirken die Übersetzungen teilweise etwas sperrig. Viele Basis-Controls findet man in nahezu allen Bibliotheken zum Erstellen von modernen Benutzeroberflächen, d. h., was ein Button oder ein Textfeld ist, muss man dem erfahrenen Entwickler nicht mehr erläutern. Dennoch werden Sie ggf. überrascht sein, welche Variantenvielfalt es beispielsweise bei den Buttons gibt [2]:

  • Button: Die klassische Schaltfläche, die eine sofortige Aktion bei einem Mausklick auslöst; man kann das Click-Ereignis oder eine Command-Bindung verwenden

  • RepeatButton: Eine Schaltfläche, die im gedrückten Zustand fortlaufend ein Click-Ereignis auslöst

  • HyperlinkButton: Eine Schaltfläche, die wie ein Link formatiert ist und für die Navigation verwendet wird

  • DropDownButton: Eine Schaltfläche mit der Möglichkeit zum Auswählen weiterer Optionen.

  • SplitButton: Eine Schaltfläche mit zwei Seiten; eine Seite initiiert eine Aktion, während die andere Seite ein Menü öffnet

  • ToggleSplitButton: Eine Schaltfläche für zwei Zustände und mit zwei Seiten; über eine Seite wird zwischen aktivierten und deaktivierten Zuständen umgeschaltet; über die andere Seite wird ein Menü geöffnet

  • ToggleButton: Eine Schaltfläche für zwei Zustände, d. h. einen Wechsel zwischen aktiviert und deaktiviert

Andere Controls muss man sich dagegen etwas genauer ansehen, damit man die Funktionsweise erkennt und sich ihre Verwendung vorstellen kann. Die folgenden Controls lohnen aus dieser Perspektive einen zweiten Blick. Wir haben ein paar Basisinformationen zusammengestellt:

  • AnimatedIcon: Dieses Steuerelement gibt animierte Bilder als Reaktion auf Benutzerinteraktion und Änderungen des visuellen Zustands wieder. Animierte Symbole ziehen die Aufmerksamkeit auf eine Komponente, zum Beispiel die höchstwahrscheinlich als nächstes zu betätigende Schaltfläche.

  • AnimatedVisualPlayer: Eine Bibliothek zum nativen Rendern von Adobe-AfterEffects-Animationen in den Anwendungen. Über das Control können flüssig ablaufende 60-fps-Animationen und auflösungsunabhängige Vektorgrafiken dargestellt werden. Informationen zur Bibliothek findet man unter [3] mit dem Stichwort „Lotti“ und auf der Projektwebseite unter [4]. Letztendlich haben wir über das Control die Möglichkeit, auf sehr einfache Art und Weise, professionell erstellte Animationen in unsere App aufzunehmen und sie an passender Stelle zum Leben zu erwecken.

  • BreadcrumbBar: Das Control stellt den direkten Pfad von Seiten oder Ordnern zum aktuellen Speicherort bereit. Man kann auf diese Weise den Navigationspfad bis zum aktuellen Element jederzeit nachverfolgen. Ebenso können die Nutzerinnen und Nutzer direkt zu einem bestimmten Ort der App navigieren. Das Control bietet sich an, wenn man eine sehr tiefe Navigationsstruktur in der App hat und dem Anwender eine Orientierung geben möchte.

  • ContactCard: Ein Control, das zur Darstellung von Kontaktinformationen wie Name, Telefonnummer und Adresse genutzt werden kann. Eine solche Darstellung kann man auch aus Einzelelementen zusammensetzen. Dieses Control kann man verwenden, wenn man zusätzliche Informationen zu einem Kontakt, zum Beispiel einen angemeldeten Benutzer, anzeigen möchte. Da immer mehr Apps eine Anmeldung zum Beispiel für die Datenspeicherung voraussetzen, ergibt es Sinn, auf ein solches fertiges Element zurückzugreifen.

  • FlipView: Man verwendet dieses Control zum „Durchblättern“ von Bildern oder anderen Elementen. Bei Geräten mit Touchscreen erfolgt die Navigation durch die Sammlung mit einer Wischbewegung über ein Element. Bei Verwendung mit einer Maus werden beim Zeigen mit der Maus Navigationsschaltflächen angezeigt. Bei Verwendung einer Tastatur erfolgt die Navigation durch die Elemente mit Hilfe der Pfeiltasten.

  • Flyouts Das ist ein ausblendbarer Container, der beliebige andere visuelle Controls als Inhalt anzeigen kann. Wann sollte ein Flyout verwendet werden? Es ist zum Beispiel vorgesehen für: das Erfassen von zusätzlichen Informationen, bevor eine Aktion abgeschlossen werden kann; das Anzeigen von Informationen, die nur vorübergehend relevant sind, oder für das Anzeigen weiterer Informationen, beispielsweise von Details oder ausführlicheren Beschreibungen eines Elements auf der Seite. Ein Flyout sollte nicht als Ersatz für ein QuickInfo (kurze Information zu einem Control) oder Kontextmenü verwendet werden.

  • ParallaxView: Hierbei handelt es sich um einen visuellen Effekt zum Darstellen von Vorder- und Hintergrund in 2D-Abbildungen. Auf diese Weise soll ein Gefühl von Tiefe, Perspektive und Bewegung erzeugt werden. Die ParallaxView ist mit der Einführung des Fluent Design als Element der Gestaltung von Benutzeroberflächen in Windows hinzugekommen. Beispielsweise verwendet man diesen visuellen Effekt, um eine Liste von Einzelelementen vertikal vor einem Hintergrundbild zu verschieben. Dabei scrollt die Liste deutlich schneller als das Hintergrundbild. Es entsteht der Eindruck von Tiefe. Sie können es vergleichen mit einem Blick aus dem Fenster eines fahrenden Zuges. Die naheliegende Landschaft rauscht in hoher Geschwindigkeit am Auge des Betrachters vorbei, während die Häuser und Bäume in der Ferne sich nur langsam in die gleiche Richtung bewegen. Microsoft gibt in seinen Guidelines den Hinweis, den Effekt bewusst, gezielt und sparsam anzuwenden, d. h., „… eine übermäßige Nutzung kann die Auswirkungen verringern“ [5].

  • PersonPicture: Ähnlich wie das Control ContactCard handelt es sich beim Steuerelement PersonPicture um ein Element zur Anzeige von Nutzerdaten. Konkret: Es wird ein Avatar für die betreffende Person angezeigt, wenn dieses verfügbar ist. Andernfalls werden die Initialen der Person oder eine allgemeine Glyphe (Platzhalter) eingeblendet. Beispielsweise kann man dieses Control für folgende Einsatzzwecke gebrauchen: Zur Anzeige des aktuellen Benutzers, zur Anzeige von Kontakten in einem Adressbuch, zur Anzeige des Absenders einer Nachricht oder zur Anzeige eines Social-Media-Kontakts. Ein solches Element könnte man auch aus Basis-Controls selbst zusammenbauen. Die Arbeit wird dadurch jedoch erheblich beschleunigt. die Anzeige von Personendaten ist in allen Apps identisch und hat damit für den Nutzer einen hohen Wiedererkennungseffekt.

  • PipsPager: Zunächst, was sind Pips? Pips stellen eine Einheit numerischer Werte dar, die meist als Punkte angezeigt werden. Eine alternative Anzeige (Bindestriche, Quadrate, …) ist jedoch möglich. Jeder Punkt stellt dabei eine Seite dar. Der Benutzer kann einen Punkt auswählen und damit direkt zur betreffenden Seite gehen. PipsPager ist damit eine Form der Navigation. Ein PipsPager kann horizontal oder vertikal angewendet werden. In der Praxis findet man meistens die horizontale Verwendung. Das Control kann man gut mit einem FlipView-Control kombinieren (Abb. 2), um neben der Wischbewegung (Touchscreen), der Weiterschaltung mit der Maus bzw. der Nutzung von Pfeilen am linken und rechten Bildrand eine weitere Option der Navigation zu bieten. Mittels PipsPager kann man schneller zu Inhalten springen, was gerade bei einer großen Anzahl von Einzelelementen eine sinnvolle Option ist.

  • Swipe: Es handelt sich um Wischbefehle für das schnelle Auslösen von Aktionen. Sie wirken wie ein Kontextmenü. Man bietet sie üblicherweise für eine größere Gruppe von Elementen an, wenn der gleiche Vorgang wiederholt werden soll. Wischgestenbasierte Befehle sollten für ein bis drei Aktionen angewendet werden. Typische Aktionen sind: Löschen, Markieren, Archivieren, Speichern oder Antworten.

  • TeachingTip: Über dieses Control werden Kontextinformationen bereitgestellt. Beispielsweise kann man auf diese Weise auf neue Funktionen in der App hinweisen. Standardmäßig wird das Control einem anderen Control zugeordnet. Die Content-Eigenschaft nimmt die Inhalte auf. Mehrere Inhalte können dabei in einem Stack-Layout untereinander angeordnet werden. Auch eine Bildlaufleiste ist möglich. Grundsätzlich sind die Inhalte in einem solchen Tippfenster jedoch zu begrenzen. Weitergehende Informationen können zum Beispiel über einen Link adressiert werden.

  • TwoPaneView: Mit Hilfe dieses Controls kann man die darzustellenden Inhalte auf zwei unterschiedliche Bereiche aufteilen, beispielsweise eine Listen- und eine Detailansicht. Die Ansicht funktioniert grundsätzlich auf allen Geräten, sie ist aber auf die Nutzung der Vorteile von Dual-Screen-Geräten optimiert. Dual-Screen-Geräte trennen die Benutzeroberfläche sauber in zwei Teile. Es wird in der Dokumentation explizit drauf hingewiesen, dass ein Dual-Screen-Gerät nicht mit einem Desktopgerät mit zwei Monitoren identisch ist. Es handelt sich um unterschiedliche Anwendungsszenarien. Dual-Screen-Geräte gibt es von unterschiedlichen Herstellern. Typisch ist die Möglichkeit, die Bildschirme unterschiedlich zueinander anzuordnen und mit anderen Inhalten zu belegen (Abb. 3).

 

 

Abb. 2: PipsPager erlaubt eine zügige Navigation zwischen den Elementen [6]


Abb. 3: Anordnung der Bildschirme bei Dual-Screen-Geräten [7]

 

Eine Vielzahl der Controls existiert bereits für die aktuelle Version der WinUI 2. Auch für diese Version des Grafikframeworks gibt es noch Aktualisierungen. Anders formuliert: WinUI 2 und WinUI 3 entwickeln sich hier parallel, d. h., WinUI 3 adaptiert hier nach und nach Controls aus der Vorgängerversion.

Die Beschreibung der Controls und die Dokumentation sind der eine Aspekt. Einen besseren visuellen Überblick – im Sinne „Was gibt es denn überhaupt?“ – bekommt man, wenn man sich die App WinUI 3 Controls Gallery installiert. Diese gibt es über den Microsoft Store. Hier können wir leichtgewichtig die Controls in Aktion ausprobieren, beispielsweise Varianten der Animation (Abb. 4). Sehr gut: Direkt in der App sind die relevanten Quellcodeausschnitte in Form von XAML-Code notiert. Diesen können wir dann mittels Copy and Paste in das eigene Projekt übernehmen und anpassen.


Abb. 4: Die App WinUI 3 Controls Gallery zeigt die Controls in Aktion – hier eine Animation

 

SIE LIEBEN UI?

Entdecken Sie die BASTA! Tracks

Fragen des Designs

Bei der Vorstellung der o. g. Controls haben Sie es schon gemerkt: Sie unterscheiden sich in Fragen des Designs, des Layouts und der gesamten User Experience erheblich davon, was man bisher mit klassischen Desktopanwendungen verwendet hat. Mobile und Desktop verschmelzen an dieser Stelle. Wie sollten nun Desktopanwendungen gestaltet werden? Man könnte es nach dem gleichen Muster machen, wie man es bisher auch mit WinForms oder WPF umgesetzt hat. Das wäre jedoch altbacken und man würde das Potenzial der Möglichkeiten nicht nutzen. Mit Blick auf die Designhinweise und die Optionen der Controls ist das Spektrum in Fragen des Designs deutlich breiter. Bereits bei der Erstellung von visuellen Prototypen, d. h. vor der technischen Umsetzung, sind diese erweiterten Möglichkeiten zu berücksichtigen. Mit anderen Worten: Mit dem Wechsel von WinForms bzw. WPF zu WinUI 3 muss ein Umdenken in Bezug auf die gesamte Gestaltung des User Interface einhergehen. Das betrifft die folgenden Aspekte:

  • Layout: Wie ist der Aufbau der Seiten zu gestalten? Im Mittelpunkt steht der Content und die Bedienung der App rückt in den Hintergrund. Das gewünschte Layout wird dann mittels der Layoutcontainer (Grid, StackLayout, …) umgesetzt.
  • Navigation: Viele Jahre gab es keine Alternative zu einem klassischen Anwendungsmenü mit den typischen Einträgen Datei, Bearbeiten, Ansicht usw. Auch in der WinUI 3 haben wir ein solches Control (MenuBar). Handelt es sich bei der App um eine dokumentenbasierte Applikation, dann kann dieses Navigationskonzept immer noch richtig sein. Für einen Großteil von anderen Situationen in der Praxis wird man sich jedoch für eine andere Art der Navigation entscheiden. Das sind zum Beispiel Registerkarten (Tabnavigation), Hamburgermenü oder die Listen-/Detailnavigation. Viele Szenarien lassen sich mit unterschiedlichen Konzepten realisieren. Bei der Auswahl gilt es, den konkreten Bedienvorgang im Auge zu behalten. Welche Navigation schafft die meiste Übersicht? Wie viele Unterelemente muss ich ansteuern? Ist die Hierarchie der Navigation flach, d. h., befinden sich alle Elemente auf einer Ebene oder sind mehrere Ebenen zu überbrücken? Ebenso ist die Anzahl der Klicks, Touch-Gesten usw. zu berücksichtigen, bis man zum gewünschten Element gekommen ist. Hier gilt es, die Anzahl der notwendigen Aktionen aus Sicht der Nutzerin bzw. des Nutzers zu minimieren.
  • Auswahl der Controls: Wählen Sie die passenden Controls aus. Das ist gar nicht so einfach. Viele Szenarien (Use Cases) lassen sich mit mehreren Controls realisieren. Hier ist es hilfreich, in der Dokumentation nachzusehen. Es geht nicht um die technische Umsetzung, sondern um die Wahl des passenden Elements im Sinne der Designrichtlinien. In der Dokumentation findet sich zu jedem Control ein Abschnitt, der genau diese Frage beantworten soll: „Ist dies das richtige Steuerelement?“ (Abb. 5).
  • Gestaltung: Die Anpassung der Controls in Bezug auf Farben, Schriftarten, Größen usw. folgt als letzter Schritt bei dieser Vorgehensweise.

Sind die genannten Punkte geklärt, geht es um die technische Umsetzung des User Interface. Dieses wird deklarativ mittels XAML-Code erstellt.


Abb. 5: Der Abschnitt „Ist dies das richtige Steuerelement?“ gibt wichtige Hinweise

 

Fragen der (XAML-)Codierung

WinForms-Anwender müssen sich am meisten umstellen. In WinForms wurde der gesamte Aufbau mittels Drag and Drop und der Zuweisung von Eigenschaftswerten erledigt. WPF und UWP verwendeten beide bereits XAML-Code. Beide Systeme hatten jedoch auch einen grafischen Designer. Der XAML-Dialekt von WPF unterscheidet sich gegenüber dem von WinUI. WinUI 3 ist eine direkte Weiterentwicklung von WinUI 2, daher brauchen sich Entwickler von Apps der UWP nicht umgewöhnen. Nur dass es jetzt keinen Designer mehr gibt, mag etwas „schmerzen“. Mit dem Wegfall des grafischen Designers kann auch das Designtool Blend nicht für WinUI 3 genutzt werden. Wer mit diesem Tool, dessen Bedienung sich eher an Designer als an Entwickler richtet, zum Beispiel Animationen erstellt hat und den daraus resultierenden Quellcode in XAML direkt übernommen hat, wird sich ggf. etwas ärgern. XAML-Vorschau und Designer soll nun eine leistungsfähigere Version von Hot Reload ersetzen.

In der Praxis heißt das, den XAML-Editor (Visual Studio) links auf dem Bildschirm (oder Bildschirm 1) und die gestartete App rechts auf dem Bildschirm (oder Bildschirm 2) anzuordnen. Änderungen am Quellcode werden dann unmittelbar in die gestartete App übernommen, ohne diese neu kompilieren zu müssen. Die Praxis zeigt, dass es hier Fortschritte gibt, dennoch erfordern einige größere Änderungen – zum Beispiel am Layout in einem Grid – weiterhin einen Neustart der Anwendung.

Das PersonPicture Control

In diesem Abschnitt zeigen wir die Arbeit mit interessanten Controls aus der WinUI 3. Wir haben dazu das PersonPicture Control ausgewählt. Die Verwendung des Control ist unter [8] und die zugehörige API-Dokumentation unter [9] zu finden. Ein simpler Einsatz erfolgt z. B. so:

<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
  <PersonPicture
    DisplayName="Maria Musterfrau"
    ProfilePicture="Assets\meinAvatar.png"
    Initials="MM" />
</StackPanel> 

Ist ein Bild vorhanden, wird dieses angezeigt. Gibt es keins, werden die Initialen eingeblendet (Abb. 6).


Abb. 6: Das PersonPicture Control

Man muss die Datenfelder des Control nicht manuell setzen. Für die Datenhaltung der Kontaktdaten bietet sich hier die spezialisierte Klasse Contact an. Ein Objekt dieser Klasse enthält bereits Properties für alle wesentlichen Merkmale eines Kontakts wie Adressen (mehrere), Vorname, Nachname, Telefonnummern (mehrere), E-Mail-Adressen (mehrere), Jobinfo, ID, Nickname, Notizen usw. Die Verwendung dieser Klasse scheint in diesem Fall angezeigt, da man auf eine vollständig definierte Datenstruktur zurückgreifen kann. Im XAML-Code binden wir in diesem Fall lediglich die Eigenschaft Contact des PersonPicture Control, also:

<PersonPicture
  Contact="{x:Bind CurrentContact, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Im C#-Quellcode benötigen wir eine Property und eine Implementierung des INotifyPropertyChanged Events, welches bei einer Änderung der Daten die View benachrichtig und die Anzeige daraufhin automatisch aktualisiert wird. Ein Objekt der Klasse Contact wird daher wie in Listing 1 definiert.


public Contact CurrentContact{…};
var contact = new Contact();
contact.FirstName = "Maria";
contact.LastName = "Musterfrau";
...
CurrentContact = contact;

 

Wir definieren ein Property mit der Bezeichnung CurrentContact des Typs Contact. Setter- und Getter sind hier nur angedeutet. Danach erstellen wir ein konkretes Objekt der Klasse Contact und weisen es dieser Property zu. Über die Datenbindung wird die Anzeige aktualisiert. Anhand des Control PersonPicture und der Klasse Contact zeigt sich die gute Abstimmung zwischen visuellen Controls für das User Interface und vorbereiteten Klassenstrukturen für die Datenhaltung. Die Features sind nun nicht mehr für Verwendung in der UWP begrenzt, sondern können auch über WinUI 3 in Anwendungen für den Desktop eingesetzt werden.

 

Eine App für WinUI 3

An dieser Stelle wollen wir die Vorgehensweise in der Praxis anhand eines konkreten Beispiels demonstrieren. Dafür wählen wir die Gestaltung einer Seite zur Anzeige von Wetterinformationen auf einem Info-Display oder einem Smarthome-Bildschirm. Dabei können wir beispielsweise davon ausgehen, dass wir einen visuellen Entwurf zum späteren Aussehen der Oberfläche vorliegen haben. Ein solcher Prototyp wird idealerweise von einem Designer angefertigt und gibt uns folgende Hinweise: grundsätzliches Aussehen des betreffenden Screens, Navigationsstruktur, darzustellende Inhalte, Schriftgrößen, Farben, Schriftarten usw. Dieser visuelle Prototyp ist Ausgangspunkt für unsere Arbeiten. Wir empfehlen an dieser Stelle, noch nicht direkt mit der Umsetzung in XAML zu starten. Ein konzeptioneller Zwischenschritt kann die Arbeit erleichtern. Das Ziel ist es, sich über das Layout und die Verwendung der Control genauer im Klaren zu werden. Dazu können wir eine Skizze zum geplanten Layout anfertigen. Es genügt eine Handskizze mit Papier und Bleistift. Hieran können wir den Einsatz der Layoutcontainer planen und damit die Aufteilung des Screens vornehmen. Ebenso können wir die Controls den Layoutcontainern zuordnen. Den Entwurf zu unserem Beispiel sehen Sie in Abbildung 7.

 


Abb. 7: Layoutskizze (Entwurf) der Wetterdarstellung

Anhand dieser Skizze können wir in Bezug auf das Beispiel folgende Festlegungen treffen:

  • Root-Element: Wir wollen zwischen unterschiedlichen Orten durch eine Wischbewegung oder mittels Tastatur bzw. einem Klick auf einen Pfeil wechseln. Für jeden Ort wird die Darstellung der Wetterdaten vollständig aufgebaut. Wir verwenden zur Navigation auf oberster Ebene ein Control des Typs .
  • Basislayout: Die Seite wird mit Hilfe einer Gitterstruktur, d. h. einem -Element eingeteilt. Dabei definieren wir nur eine Zeilenstruktur mit vier Elementen. Eine Einteilung in mehrere Spalten nehmen wir nicht vor, denn wir erkennen, dass die Zeilen eine sehr unterschiedliche Anzahl von Spalten aufweisen. Hierbei ist es nicht immer sinnvoll, die größte Anzahl von Spalten als Einteilungsmaßstab zu verwenden. Daher haben wir uns für ein Grid mit vier Zeilen und null Spalten entschieden. Für die einzelnen Zeilen werden unterschiedliche Layouts definiert. Die Höhendefinition erfolgt relativ. Die erste und die letzte Zeile haben eine Höhe von 0,7* und die beiden mittleren Zeilen eine relative Höhe von *.
  • Sublayout: Für jede Zeile wird das untergeordnete Layout definiert. Es werden für die wöchentliche Vorschau (Zeile 2) fünf Spalten und eine Zeile für die Überschrift („Täglich“) benötigt. Hier platzieren wir ein -Element mit fünf Spalten und gleicher Breite und einer fixen Höhe für die Überschrift. Ähnlich verfahren wir in der nächsten Zeile. Die Wetterdaten werden im Dreistundenrhythmus für den gewählten Tag vorhergesagt. Daher verwenden wir für das Sublayout (Zeile 3) ein -Control mit acht Spalten und zwei Zeilen. Die Sublayouts für Zeile 1 und 4 sind noch nicht fertiggestellt.
  • User-Controls: Wiederkehrende Elemente werden in ein eigenes User-Control ausgelagert. Dieses ist im Beispiel der Fall für die Tagesvorhersage. Dieses User-Control enthält dann die Struktur für die Anzeige der Wetterdaten für den betreffenden Tag. Wir haben es als User-Control Day bezeichnet. Analog ist es für die Anzeige der täglichen Vorhersage. Hierzu definieren wir ein User-Control namens Hourly. Die Eigenschaften eines User-Control werden in Form einer Dependency Property ausgeführt und können damit von außen definiert werden.

Die relevanten Ausschnitte aus dem Quellcode zur Definition des User Interface sind in Listing 2 dargestellt.


<Window
  ...">
  <FlipView>
    <!--Ort -->
    <Grid>
      ...
      <Grid.RowDefinitions>
        <RowDefinition Height="0.7*"/>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition Height="0.7*"/>
      </Grid.RowDefinitions>
      <!-- Kopfzeile-->
      <StackPanel HorizontalAlignment="Center">
        <TextBlock FontSize="50" FontWeight="Bold" FontFamily="Consolas" Text="Weimar"/>
        <TextBlock HorizontalAlignment="Center" FontSize="36" FontWeight="Bold" Text="21°"/>
      </StackPanel>
      <!-- Täglich-->
      <Grid Margin="20,0" Grid.Row="1">
        <Grid.RowDefinitions>
          <RowDefinition Height="30"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
          <ColumnDefinition/>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock FontFamily="Consolas" Text="Täglich" FontSize="20"/>
        <local:Day DayName="Montag" Grid.Row="1"/>
        <local:Day DayName="Dienstag" Grid.Row="1" Grid.Column="1"/>
        <local:Day DayName="Mittwoch" Grid.Row="1" Grid.Column="2"/>
        <local:Day DayName="Donnerstag" Grid.Row="1" Grid.Column="3"/>
        <local:Day DayName="Freitag" Grid.Row="1" Grid.Column="4"/>
      <!-- Stündlich -->
      ...
      <!--Tagesinformationen-->
                ...
      </Grid>
    </Grid>
    <!--Ort 2-->
    <Grid>
      ...
    </Grid>
  </FlipView>
</Window>

Zu beachten ist auf oberster Ebene das <FlipView\>-Element, um zwischen kompletten Ansichten zu wechseln – in diesem Fall zwischen mehreren Orten bei der Wetteranzeige. Es folgt die Definition des <Grid\>-Elements zur Festlegung des Basislayouts (Zeilenstruktur). Dann kommen die Sublayouts für Kopfzeile, für die stündliche Vorhersage und die Tagesinformationen. Für sich wiederholende Anzeigen werden eigene User-Controls definiert.


<UserControl
  x:Class="Weather.Day"
  ...>
  <StackPanel HorizontalAlignment="Center">
    <TextBlock Margin="0,20" Text="{x:Bind DayName}"/>
    <Image HorizontalAlignment="Left" Margin="0,10" Width="50" Source="Assets/weather/day_clear.png"/>
    <StackPanel Orientation="Horizontal">
      <TextBlock FontSize="22" VerticalAlignment="Center" FontWeight="SemiBold" Text="23°"/>
      <TextBlock Margin="10,0" FontSize="16" VerticalAlignment="Center" Text="8°"/>
    </StackPanel>
    <TextBlock Text="Sonnig"/>
  </StackPanel>
</UserControl>
 
public sealed partial class Day : UserControl
{
  public Day()
  {
   this.InitializeComponent();
  }
 
  public string DayName
  {
   get { return (string)GetValue(DayNameProperty); }
   set { SetValue(DayNameProperty, value); }
  }
 
  public static readonly DependencyProperty DayNameProperty = DependencyProperty.Register("DayName", typeof(string), typeof(Day), new PropertyMetadata(null));
 
}

 

In Listing 3 sieht man beispielhaft den Quellcode für das User Control Day. Die Oberfläche wird in XAML erstellt. Wie man eine Dependency Property deklariert, ist im unteren Abschnitt des Quellcodes zu sehen. Hier ist das beispielhaft für die Eigenschaft DayName ausgeführt. DayName kann dann im User-Control selbst in der Datenbindung verwendet werden, zum Beispiel um ein Textfeld an diese Eigenschaft zu binden:

<TextBlock Margin="0,20" Text="{x:Bind DayName}"/>

Ebenso können wir bei der Verwendung des User-Control die Eigenschaft von außen definieren:

<local:Day DayName="Montag"/>

Eine erste – noch unfertige – Version der App ist in Abbildung 8 zu sehen. Bitte beachten Sie: Es handelt sich um eine Desktopapplikation und nicht um eine App für die UWP. Wir haben hier alle Möglichkeiten des Systemzugriffs und gleichzeitig mit dem Einsatz von WinUI 3 auch ein deutlich erweitertes Spektrum an grafischen Gestaltungsmöglichkeiten. Controls für das User Interface spielen dabei eine entscheidende Rolle. Ausgangspunkt sind die Basis Controls, die über WinUI 3 von Haus aus zur Verfügung gestellt werden.


Abb. 8: Erste Version der App, Wetteranzeige (noch unvollständig)

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Fazit und Ausblick

Controls sind das A und O bei WinUI 3. Mit Blick auf den Umsetzungsstand lässt sich feststellen, dass die Lücken des Windows App SDK von Version 1 gegenüber Version 0.8 bereits umfassend geschlossen wurden. In der App WinUI 3 Controls Gallery haben die ausgegrauten Bereiche – für noch nicht migrierte Controls – merklich abgenommen, d. h., fast alle Controls aus WinUI 2 sind nun auch unter WinUI 3 verfügbar. Das ist ein wichtiges Argument für die Praxis, wenn man zum Beispiel eine App von der UWP (mäßiger (Aufwand), von der WPF (größerer Aufwand) oder von WinForms (sehr großer Aufwand) nach WinUI 3 migrieren möchte.

Doch fast alle Elemente heißt eben auch nur „fast alle“. Mit Blick auf die Roadmap sehen wir auch, dass die Controls für das Zeichnen mit Stift oder Finger und die Kartenanzeige, das InkCanvas Control und das Map Control, noch fehlen. Das InkCanvas Control wurde jedoch bereits in Aussicht gestellt. Wird man in der Controls Gallery von Microsoft nicht fündig, kann man schauen, was die Drittanbieter für WinUI 3 bereits vorhalten. Sie adaptieren ihre Controls nun auch sukzessive und es gibt bereits erste Versionen von Bibliotheken für das neue Grafikframework.

Links & Literatur

[1] https://docs.microsoft.com/en-us/windows/apps/windows-app-sdk/set-up-your-development-environment?tabs=vs-2022

[2] https://docs.microsoft.com/en-us/windows/apps/design/controls/buttons

[3] https://docs.microsoft.com/en-us/windows/apps/design/controls/

[4] https://docs.microsoft.com/de-de/windows/communitytoolkit/animations/lottie

[5] https://airbnb.io/lottie/#/

[6] https://docs.microsoft.com/de-de/windows/apps/design/motion/parallax

[7] https://docs.microsoft.com/en-us/windows/apps/design/controls/pipspager

[8] https://docs.microsoft.com/de-de/dual-screen/introduction

[9] https://docs.microsoft.com/de-de/windows/apps/design/controls/person-picture

The post Controls für WinUI 3 – funktionell und ansprechend im Design appeared first on BASTA!.

]]>
Azure Kubernetes Service zu Diensten https://basta.net/blog/azure-kubernetes-service-zu-diensten/ Wed, 23 Mar 2022 09:46:49 +0000 https://basta.net/?p=85451 In diesem Artikel wird der Azure Kubernetes Service (AKS) vorgestellt. Es werden die wichtigsten Begriffe geklärt und ein Überblick gegeben, wie AKS bei einer containerbasierten Applikation unterstützen kann und welche Vorteile er gegenüber reinem Kubernetes bietet.

The post Azure Kubernetes Service zu Diensten appeared first on BASTA!.

]]>
Das Open-Source-Projekt Kubernetes (K8s) ist längst in aller Munde und hat sich als „das“ Containerorchestrierungstool etabliert. Es bringt, hauptsächlich im Kontext einer Microservices-basierten Multicontainer-anwendung, bereits viele praktische Features mit. Da wären beispielsweise die Möglichkeiten von Updates ohne Downtime, Skalierbarkeit, Service Discovery und Selbstheilungstechniken.

 

 

Was ist AKS?

Ein Kubernetes-Cluster selbst zu installieren und zu betreiben, bedeutet allerdings einiges an Aufwand. An dieser Stelle setzt Azure Kubernetes Service an. Hierbei handelt es sich um einen Managed Kubernetes Service in der Azure Cloud. Auch AKS verwendet Kubernetes und zielt auf Containeranwendungen, unterstützt zusätzlich aber in vielen Bereichen wie Node-Management, Security, Skalierung und Monitoring. Das vereinfacht die Installation und den Betrieb eines Kubernetes-Clusters erheblich.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

AKS – Fakten und Begriffe

Cluster Management: Die virtuellen Maschinen zur Steuerung des Clusters – die Master Nodes – werden komplett von Microsoft verwaltet und sind kostenfrei. Bezahlen muss man lediglich für die Rechenleistung der Worker Nodes, ihren Speicherbedarf und ihre Netzwerkressourcen. Für die Worker Nodes, auf denen die Applikationen laufen, stehen Linux- und Windows-Maschinen zur Verfügung.

Abbildung 1 verdeutlich die Arbeitsteilung. Die Komponenten sind bereits von Kubernetes bekannt. Die Master Nodes bestehen aus dem API-Server, der etcd-Datenbank, dem Scheduler und dem Controller Manager. Der API-Server nimmt Befehle entgegen und prüft diese. In der etcd-Datenbank wird der State des Clusters gespeichert. Über den Scheduler wird geregelt, auf welchen Nodes welche Pods (mit den Containern) platziert werden. Der Controller Manager verwaltet Replica Set Controller und Deployment Controller. Der linke Teil wird komplett von Azure verwaltet, inklusive beispielsweise der Skalierung und dem Patching der virtuellen Maschinen.


Abb. 1: AKS-Komponenten (Quelle: Microsoft)

Auf den virtuellen Maschinen der Worker Nodes finden sich der kubelet-Agent zur Kommunikation mit dem API-Server und Pod-Erstellung, kube-proxy für Netzwerkaufgaben und eine Container-Runtime. Auch bei diesen Bestandteilen unterstützt AKS: Sie müssen nicht manuell auf den Nodes installiert werden.

Auch bei AKS sind natürlich Pods, Replica Sets, Deployments, Nodes und Node Pools relevant [1].

Worker Nodes: Ein Node Pool beschreibt ein Set gleicher virtueller Maschinen, auch Worker Nodes genannt, auf denen die Applikationen betrieben werden. Bezogen auf Azure-Ressourcen können das einzelne VMs oder Virtual Machine Scale Sets sein. Des Weiteren besteht die Möglichkeit, virtuelle Nodes zu verwenden, mehr dazu beim Thema Skalierung. Für die Pools können verschiedene Typen virtueller Maschinen gewählt werden, beispielsweise CPU-optimierte Serien oder solche mit GPUs. Für erhöhte Security- und Complianceanforderungen kann auch das Modell Compute Isolation gewählt werden, bei dem physikalische Server im Azure Data Center nicht mit anderen Kunden geteilt werden.

High Availability: Die Node-Auto-Repair-Funktion hilft, ungesunde Nodes zu identifizieren und ggf. neu zu starten [2]. Um eine höhere Verfügbarkeit bei den Worker Nodes zu gewährleisten, können diese per Pool auf Availability Zones verteilt werden, das heißt auf mehrere Data Center in einer Region. Für sehr kritische Workloads kann außerdem eine kostenpflichtige SLA-Option gebucht werden, die durch zusätzliche Ressourcen bei den Master Nodes die Verfügbarkeit auf 99,95 Prozent bei Verwendung von Availability Zones erhöht. Die SLA ist dann auch finanziell gesichert, falls das Versprechen seitens Azure nicht eingehalten wird [3].

Für Anforderungen im Bereich Disaster Recovery können z. B. Multi-Region-Cluster (Abb. 2) aufgebaut werden. Die AKS-Cluster werden dafür in verschiedenen Regionen für Ausfallsicherheit aufgebaut und die globale Request-Verteilung etwa mit Hilfe eines Azure Traffic Manager erreicht.

Abb. 2: Multi-Region-Cluster (Quelle: Microsoft)

 

Auch Container-Images können global repliziert werden, wenn die entsprechende SKU in der Azure Container Registry verwendet wird. Das verringert die Latenz beim Image Pull, erhöht die Verfügbarkeit und spart Kosten beim Netzwerktransfer [4].

Netzwerk: Beim AKS gibt es zwei verschiedene Netzwerktypen, die für die Cluster zur Verfügung stehen: kubenet und Container Networking Interface (CNI).

Die kubenet-Variante ist der einfachere Ansatz. Die IP-Adressen der Pods sind hier abstrahiert und müssen nicht im Adresspool des virtuellen Netzwerks berücksichtigt werden. Angesprochen werden sie durch Network Address Translation (NAT) auf den Nodes. Bei diesem Ansatz sind allerdings keine Windows Node Pools möglich. Beim CNI-Ansatz bekommt jeder Pod eine IP-Adresse aus dem IP-Pool des VNets zugewiesen, was daher eine aufwendigere Planung beim Clusteraufbau erfordert. Dieser Ansatz muss gewählt werden, wenn die AKS Network Policies angewendet werden sollen oder ein Azure Application Gateway als Ingress-Ressource gewünscht ist. Tabelle 1 listet weitere Unterschiede auf.

 

SIE LIEBEN AZURE?

Entdecken Sie die BASTA! Tracks

 

kubenet CNI
POD IPs werden abstrahiert, NAT auf den Nodes Jeder Pod bekommt eine eigene IP-Adresse
Limit von 400 Nodes pro Cluster Notwendig für:
– Network Policies
– Application Gateway als Ingress-Ressource
Neine Windows Nodes Benötigt einen größeren IP-Adressraum und mehr Planung

Tabelle 1: Vergleich der AKS-Netzwerkoptionen

 

Es ist zu beachten, dass der Netzwerktyp nach der Clustererstellung nicht mehr geändert werden kann [5].

Magie mit kubectl: Bei der Interaktion mit dem AKS-Cluster wird das Tool kubectl verwendet. Dass hierbei eine gewisse Magie stattfindet, indem durch K8s-Befehle gleichzeitig auch Azure-Ressourcen erstellt und konfiguriert werden, liegt an zusätzlichen Pods auf den Master Nodes. Beispielsweise sind das CSI Driver für das universelle Speichermanagement oder Pods vom cloud-provider-azure-Projekt. Letztere helfen dabei, die zusätzliche Azure-Load-Balancer- und Network-Security-Group-Konfigurationen durchzuführen, wenn ein externer Service als Kubernetes-Ressource erstellt wird [6].

Storage: Auch bei der Speicherverwaltung kann AKS unterstützen. Sofern der lokale Speicher auf den Nodes selbst nicht ausreicht, beispielsweise weil Persistenz über den Pod-Lifecycle hinaus gebraucht wird, können Azure Discs oder Fileshares angebunden werden (Abb. 3). Dabei muss die Kubernetes-Ressource Persistent Volume Claim (PVC) erstellt und die Volume-Einbindung beim Deployment angegeben werden. Je nach der im PVC verwendeter Storage Class wird dann automatisch eine Azure Disc oder ein Azure Fileshare erstellt. Storage Classes gibt es built-in, sie können aber auch individuell erstellt werden. Im Hintergrund wird dann – im empfohlenen, moderneren Ansatz – der Azure Disc Container Storage Interface (CSI) Driver verwendet. Dieser arbeitet gemäß CSI-Spezifikation [7] und läuft als Plug-in im Cluster. Damit ist es nun auch möglich, Storage-Typen neu zu erstellen oder anzupassen, ohne den Kubernetes Core zu verändern. Über die Storage Classes wird auch das Löschverhalten für die Azure-Ressourcen geregelt, wenn das Volume nicht mehr in Pods eingebunden ist [8], [9].

Abb. 3: Storage-Optionen im AKS-Cluster (Quelle: Microsoft)

 

Tools: Für die Arbeit mit einem AKS-Cluster stehen mehrere Tools zur Verfügung. Zu erwähnen ist hier sicher das Azure Portal, in dem zum einen das GUI vorhanden ist und außerdem die Cloud Shell für Konsolenbefehle verwendet werden kann. Über das GUI kann der Cluster selbst verwaltet werden, außerdem bietet es Möglichkeiten für das Clustermonitoring. In der Cloud Shell ist kubectl bereits installiert. Komfortabel ist auch die Arbeit mit Visual Studio Code, das mit entsprechenden Extensions unterstützt. kubectl muss hier aber extra installiert werden.

Als weitere interessante Option zur Clusterverwaltung ist Octant zu erwähnen, ein Open-Source-Projekt mit webbasiertem Interface [10].

 

 

 

Unterstützung der Security

Netzwerk: Beim Thema Security bietet AKS zahlreiche Möglichkeiten, um den Cluster abzusichern, Zugriffe und Datenverkehr zu steuern. Bei AKS handelt es sich um PaaS, daher hat es automatisch einen Public Endpoint. Soll der Zugriff darauf eingeschränkt werden, kann das z. B. mit definierten IP Ranges erreicht werden, außerhalb derer kein Access möglich ist. Der Cluster kann aber auch privat sein und komplett aus dem Internet genommen werden. In diesem Fall ist eine Kommunikation mit dem API-Server nur über das konfigurierte VNet mit Hilfe von Azure Private Links möglich. Der Datenverkehr bleibt dann im Azure-Backbone-Netzwerk [11].

Die Einschränkung per Private Link funktioniert ebenfalls bei anderen Azure Services, die häufig im Cluster eingebunden sind, beispielsweise Storage oder eine Azure Container Registry. Bei der Filterung vom Datenverkehr zwischen den Nodes kommen auch Network Security Groups (NSG) zum Einsatz, bei denen AKS die Verwaltung der Regeln übernimmt. Das funktioniert bei beiden Netzwerktypen. Wird beispielsweise einer externer K8s Service erstellt, wird neben der Load-Balancer-Konfiguration auch die der NSG von AKS angepasst [12].

Microsoft Defender for Cloud und Key Vault: Vor kurzem noch unter den Namen Azure Security Center und Azure Defender bekannt, kann für den Cluster selbst oder andere im Kontext verwendete Services zusätzlicher Schutz aktiviert werden, beispielsweise für die Azure Container Registry. Hierbei unterstützt der Defender u. a. durch das kontinuierliche Scannen der Images mit der Engine des IT-Security-Anbieter Qualys, was z. B. fehlende Securitypatches des OS Layers aufdecken kann [13].

Bei AKS kann durch die Aktivierung des kostenpflichtigen Defenders ein eingeschleuster Krypto-Mining-Container entdeckt werden oder auch Zugriffe von ungewöhnlichen oder verschleierten IP-Adressen. Die Resultate können dann im Defender-Portal (Security Center) eingesehen werden [14].

Für das sichere Ablegen von Passwörtern, Connection-Strings oder Zertifikaten eignet sich der Azure Key Vault. Er kann über eine Erweiterung direkt in den Cluster eingebunden werden. Es laufen dann einfach zusätzliche Pods auf den Nodes und über den CSI Driver können die Key-Vault-Ressourcen als Volumes den Pods zur Verfügung gestellt werden.

Azure Policies und Network Policies: Zur Überwachung von Compliance und eingehaltenen Sicherheitsrichtlinien sind die Azure Policies ein mächtiges Instrument. Sie können durch das Aktivieren eines Cluster-Add-ons auch in diesem verwendet werden. Viele AKS spezifische Policy-Definitionen sind bereits standardmäßig vorhanden und es gibt auch Initiativen, in denen Policys für einen Security-Baseline-Standard gebündelt werden. Es können aber auch individuelle Policys erstellt werden. Je nach Konfiguration kann eine Policy eine Fehlkonfiguration melden (Audit) oder neben anderen Effekten z. B. eine Ressourcenerstellung verhindern [15].

Beispiele für Complianceprüfungen ist das Festlegen erlaubter Regionen oder der SKUs virtueller Maschinen. Im Bereich Security gibt es Optionen zur Einschränkung von Pod-Privilegien, erlaubten Ports oder Ressourcenzuweisungen für Pods.

Um den Datenverkehr zwischen den Pods granularer steuern zu können, stehen die Kubernetes Network Policies zur Verfügung, bei deren Erstellung und Verwaltung AKS mit dem gleichnamigen Feature unterstützen kann. Basierend auf den Namespace- und Pod-Labels kann eine Restriktion zwischen Pods erstellt werden, per Default gibt es dort keine.

Es kann zwischen den Microsoft Azure Network Policies und denen des Anbieters Calico gewählt werden. Bei den Microsoft Policies ist anzumerken, dass diese nur auf Linux Nodes empfohlen werden und ein CNI-Netzwerk brauchen. Calico Policies arbeiten auch mit dem kubenet-Netzwerk und haben in der Preview Windows-Unterstützung, können aber natürlich nicht mit Azure-Support aufwarten [16].

AAD-Integration: Für die Zugriffssteuerung auf den Cluster kann Azure Active Directory (AAD) integriert werden. Auch in diesem Fall muss dafür lediglich ein Feature aktiviert werden. Die Integration vom AAD verbindet dann das Azure-RBAC-System mit dem Kubernetes RBAC. Dadurch kann der Cluster-Access über die AAD-Gruppen- und Rollenzuweisungen gesteuert werden, mit vorhandenen AKS-spezifischen Rollen oder selbst erstellten. Außerdem kann damit auch mit Services wie Conditional Access oder Privileged Identity Management (PIM) gearbeitet werden. Zu beachten ist, dass dieses Feature zwar nachträglich aktiviert, aber nicht wieder deaktiviert werden kann. Außerdem ist die Aktivierung von K8s RBAC im Cluster erforderlich [17].

Node Patching/Upgrades: Ein wichtiges Thema im Securitykontext ist natürlich das Patching der Betriebssysteme. Bei AKS muss das bei den Master Nodes gar nicht beachten werden, da diese von Azure verwaltet und gepatcht werden. Bei den Linux Worker Nodes werden Updates in der Nacht automatisch bezogen und für die Unterstützung bei nötigen Neustarts der Nodes gibt es das Kured-Projekt. Zusätzliche Pods auf den Nodes in Form von Daemon-Sets prüfen die Existenz der /var/run/reboot-required-Datei und können im entsprechenden Fall das Neustarten des Nodes inklusive Rescheduling der Pods übernehmen [18].

Bei den Windows Worker Nodes gibt es keine täglichen Updates, es kann aber der AKS-Upgrade-Prozess verwendet werden, um die neuesten gepatchten Basis-Images für die Nodes zu erhalten.

Dieser Prozess, mit dem auch die K8s-Version im Cluster erneuert werden kann, verwendet den Cordon-and-Drain-Ansatz: Ein Node wird zunächst markiert, damit der K8s Scheduler keine neuen Pods mehr auf diesem plant. Laufende Pods werden auf andere Nodes verteilt und ein neuer Node wird erstellt und kann Pods aufnehmen. Sobald der markierte Node keine Pods mehr aufweist, wird er gelöscht und es wird mit dem nächsten Node fortgefahren. Auf diese Art kann eine Downtime der Anwendung verhindert werden. Das Upgrade kann z. B. mit dem Azure CLI erledigt und optional getrennt nach Master und Worker Nodes vorgenommen werden [19]. Snippet für einen Upgradebefehl per CLI:

az aks upgrade --resource-group $rgName --name $aksClusterName --kubernetes-version 1.21.1 --control-plane-only

Des Weiteren gibt es für die Kubernetes-Version einen Auto-Upgrade-Channel in der Preview, der automatisch und in verschiedenen Stufen die Version updaten kann [20]. Snippet für CLI:

az aks update --resource-group $rgName --name $aksClusterName --auto-upgrade-channel stable

 

 

 

Skalierung und Monitoring

HPA und Cluster-Autoscaler: Bei einer containerbasierten Microservices-Architektur ist die Skalierung der Pods bzw. des gesamten Clusters auch ein bedeutendes Thema, bei dem AKS mit zusätzlichen Techniken helfen kann. Der Horizontal Pod Autoscaler (HPA), der anhand von Pod-Metriken wie CPU-Nutzung skaliert und so Pods auf einem Node erstellt und entfernt, ist bereits von K8s selbst bekannt.

AKS hat aber auch einen Cluster-Autoscaler. Dieser kann dem Cluster automatisch neue Nodes hinzufügen bzw. sie entfernen, wenn die Ressourcennutzung auf den virtuellen Maschinen das erfordert. Der Autoscaler kann über das Portal oder einfach per CLI aktiviert werden, wie im folgenden Code-Snippet für einen Cluster mit Single Node Pool ersichtlich ist:

az aks update --name $aksClusterName --resource-group $rgName --enable-cluster-autoscaler --min-count 1 --max-count 3

Es kann damit zwischen einem und drei Nodes automatisch skaliert werden, mit verschiedenen Profilen wird eine granulare Skalierung möglich. Die zusätzlichen Nodes benötigen keine weitere Konfiguration [21].

Außerdem gibt es für die Skalierung noch das Framework Kubernetes-based Event-driven Autoscaling (KEDA), das weitere umfangreiche Metriken und Events als Trigger erlaubt. KEDA kreiert zusätzliche Pods zum Operieren und Event Listening im Cluster und arbeitet mit dem HPA zusammen. Dadurch kann für das Pod Scaling mit vielen weiteren Signalen gearbeitet werden, z. B. Azure-Monitor-Metriken, Storage oder Event Hubs. Andere Event Sources weiterer Cloud-Provider werden auch unterstützt [22].

Rapid Burst Scaling: Die Skalierung über Nodes kann unter Umständen einfach zu langsam sein. Für diesen Anwendungsfall kann mit Virtual Kubelets gearbeitet werden (Abb.4) [23]. Hierbei handelt es sich um eine Open-Source-Kubernetes-Implementierung, mit der Nodes im Cluster simuliert werden können; ein Kubelet registriert sich dann selbst als Node. Die nötige Rechenpower hinter den Virtual Kubelets kommt nicht direkt von virtuellen Maschinen, sondern beispielsweise vom Azure Batch Service mit GPUs oder von Azure Container Instances, auf denen die Container laufen. Das ist zwar teurer, skaliert aber deutlich schneller als über den Node Scaler. Daher kommt auch der Name Rapid Burst Scaling, weil es eher für den Ausgleich von Spitzenlasten und nicht für den Dauerzustand konzipiert ist [24].

Abb. 4: AKS-Virtual-Kubelet-Optionen (Quelle: Microsoft)

 

Azure Monitor mit AKS: AKS bietet durch die Integration in den Azure Monitor komfortable Möglichkeiten, den Cluster zu überwachen (Abb. 5). Für die umfangreichste Datenlage wird der Cluster mit einem Azure Log Analytics Workspace verknüpft, der die zentrale Datensenke darstellt. Wenn noch die entsprechenden Diagnostic-Settings vom Cluster aktiviert werden, können neben metrischen Daten wie CPU-Nutzung, Disk-Belegung oder Netzwerkverkehr auch Logdaten vom API-Server, dem Controller Manager oder dem Scheduler gesammelt werden.

Wie aus der Monitoringwelt bekannt, stehen für die Analyse dieser Daten zahlreiche Werkzeuge zur Verfügung. Die Daten können z. B. in Monitor Workbooks oder über Power BI visualisiert werden. Der Cluster kann aber auch auf Basis der Daten skalieren und es gibt Optionen, über das Alerting Benachrichtigungen zu versenden. Bei Bedarf können sogar weitere Arbeitsschritte, beispielsweise über Azure Functions, Logic Apps oder verbundene Ticketsysteme, erledigt werden.

Abb. 5: AKS und Azure Monitor (Quelle: Microsoft)

 

Microsoft liefert über die Container Insights Solution, basierend auf vorhandenen Workbooks, u. a. Einsichten über den Zustand von Nodes, Containern und deren Live-Logs. Das Monitoring-Add-on kann auch nach der Erstellung des Clusters aktiviert werden.

Abb. 6: KQL-Query und Chart

 

Die Kusto Query Language (KQL), eine SQL-ähnliche Abfragesprache aus der Azure-Monitor-Welt, bietet zudem viele Optionen einer individuellen Analyse. Im Log-Analytics-Bereich des Azure Portal können dann direkt Abfragen für Informationen aus dem Cluster ausgeführt werden [25]. Folgende Query liefert Basisdaten über im Cluster laufende Container der letzten 30 Minuten und rendert direkt einen Chart (Abb. 6):

ContainerInventory | where TimeGenerated >= ago(30m) | summarize AggregatedValue = dcount(ContainerID) by ContainerState, Image | render columnchart

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Erweiterte Integration mit anderen (Azure) Services

Neben der automatischen Erstellung von Storage-Ressourcen oder der NSG-Konfiguration gibt es noch zahlreiche weitere Integrationen zu Azure Services und Ressourcen, wovon einige an dieser Stelle noch einmal kurz beleuchtet werden sollen.

Verwendung von Azure Application Gateway als Ingress: In einem Kubernetes-Cluster werden häufig Ingress-Controller verwendet, um den Datenverkehr granularer zu steuern und bestimme Regeln umzusetzen. Eine verbreitete Implementierung ist NGINX [26]. Damit bleibt die Ressourcenlast für den Ingress-Controller allerdings im Cluster selbst. Eine Alternative ist, das Azure Application Gateway als Ingress-Controller zu verwenden.

Beim Azure Application Gateway handelt es sich um einen Layer 7 Load Balancer mit der Option einer integrierten Firewall. Das Application Gateway kann ebenfalls mit Features wie TLS-Terminierung oder URL-Path-Routing aufwarten. Mit dem aktivierten Application-Gateway-Ingress-Controller-(AGIC-)Add-on kann es als Ingress-Ressource im Cluster verwendet werden, reduziert die Last im Cluster selbst und kann unabhängig skalieren. Da das Application Gateway direkt mit den Pods über private IPs kommuniziert, benötigt es keine Node Ports oder KubeProxy Services und nimmt damit zusätzlich Last aus dem Cluster. Ein CNI-Netzwerk ist aber Voraussetzung. Die Aktivierung des Add-ons erstellt dann nötige Kubernetes-Ingress-Ressourcen, Services und Pods [27].

Mit folgendem CLI-Befehl kann das Add-on in einem bestehenden Cluster aktiviert werden, der dann mit einem vorhandenen Application Gateway verknüpft wird:

az aks enable-addons -n $aksClusterName -g $rgName -a ingress-appgw --appgw-id $appgwId

Azure Functions mit KEDA: Im Abschnitt „Monitoring“ wurde bereits einmal auf das KEDA-Framework eingegangen. KEDA ermöglicht es, Azure Functions direkt im AKS-Cluster in einem Container auszuführen. Die Azure Function App muss hierfür im Containermodus erstellt werden, damit die Runtime entsprechend zur Verfügung steht. Nachdem KEDA im Cluster installiert wurde [28], kann die Function-App z. B. über die Function-Core-Tools in den Cluster geladen werden. Dieser Vorgang erstellt ein Manifest mit Deployment, Scaled Object und Secrets für die Umgebungsvariablen aus der local.settings.json-Datei.

Auch hier arbeitet KEDA mit dem HPA zusammen und ermöglicht, basierend auf Events wie neuen Nachrichten in einer Azure Storage oder Service Bus Queue, anhand von Cronjobs oder anderen Triggern, den Function-Container zu skalieren, solange Arbeit zu verrichten ist [29].

Azure Active Directory (AAD) Pod Identity: Sofern die AAD-Integration für den Cluster vorhanden ist und ein CNI-Netzwerk gewählt wurde, kann für den Azure-Ressourcenzugriff die AAD Pod Identity verwendet werden. Für Kubenet wird das aus Sicherheitsaspekten nicht empfohlen. Wird das Add-on aktiviert, laufen für die Funktionalität wieder zusätzliche Pods als Daemon-Set auf den Nodes. Über diesen Ansatz kann die Identität eines Pods mit einer Identität aus Azure verknüpft werden und der Pod bekommt den nötigen Zugriff auf eine Cloud-Ressource. Dieses Feature ist aktuell nur auf Linux Nodes verfügbar und befindet sich noch im Preview-Status [30].

Continuous Integration und Deployment: Zur Abrundung des Bilds sei hier nochmals die mögliche Verknüpfung zu CI/CD Pipelines beispielsweise von Azure DevOps oder GitHub Actions erwähnt. Ein Code-Commit triggert z. B. die automatische Erstellung neuer Images, die in die Containerrepositorys geladen und etwa durch einen Helm-Upgrade-Schritt eine aktualisierte Version der Anwendung im AKS-Cluster verfügbar machen. Helm ist ein Package-Manager für Kubernetes-Ressourcen und in AKS integriert [31].

 

 

Architekturen

Abbildung 7 veranschaulicht den Aufbau einer Beispiel-Microservices-Architektur mit Verwendung eines AKS-Clusters. Die verschiedenen Services sind durch Container in Pods realisiert, Datenbanken befinden sich aber häufig außerhalb des Clusters, um eine Stateless-Architektur zu vereinfachen. Es wird grundsätzlich empfohlen, Daten eher in PaaS-Diensten zu speichern, die Multi-Region-Replikation unterstützen. Als Ingress-Controller wird NGINX verwendet, ein Load Balancer routet den Internetverkehr zum Ingress. Das AAD ist als Identity Provider angebunden und Secrets liegen im Azure Key Vault. Updates der Anwendungen erfolgen durch CI/CD Pipelines, es erfolgt ein Pushen der Images in die Container Registry und ein Pullen dieser vom Cluster nach einem Helm-Upgrade-Befehl [32].

Abb. 7: AKS-Cluster-Architektur (Quelle: Microsoft)

 

 

Tipps, Tricks und Hands-on

Ist ein Cluster nicht produktiv und wird nicht dauerhaft gebraucht, soll es aber auch nicht bei jedem Gebrauch neu provisioniert werden, kann es gestoppt werden [33]. Damit werden sämtliche Worker Nodes dealloziert und verursachen keine (CPU-)Kosten mehr (Abb. 8). Es bleiben lediglich die Kosten für Netzwerk-ressourcen und Storage. Das Anhalten des Clusters kann im Azure-Portal und durch den folgenden CLI-Befehl erreicht werden:

az aks stop --name $aksClusterName --resource-group $rgName

Um den Cluster wieder zu starten, genügt folgendes Command:

az aks start --name $aksClusterName --resource-group $rgName

Bei der Erstellung der Node Pools können keine virtuellen Maschinen mit Standard-HDDs verwendet werden.

Abb. 8: Gestoppter AKS-Cluster

 

Beim CNI-Netzwerktyp sollte die IP-Adressplanung definitiv nicht unterschätzt werden. Jeder Pod braucht eine Adresse und nachträgliche Änderungen an Adressbereichen können schwierig bzw. praktisch unmöglich werden. Daher sollte die Menge der Workloads im Cluster so gut wie möglich eingeschätzt und bei der Definition der Adressbereiche berücksichtigt werden [34].

Außerdem bietet es sich an, bestimmte Prozesse und Rollen zu definieren und zu besetzen, beispielsweise einen Cluster-Admin oder ein AKS-Team. AKS kann zwar in vielen Bereich unterstützen, ersetzt aber dennoch nicht eine gute Operations-Basis.

Abb. 9: Generierte Ressourcengruppe von AKS

 

Die Ressourcen, die von AKS erstellt werden, finden sich in einer anderen, automatisch generierten Ressourcengruppe nach dem Schema (Abb. 9):

MC_<Ressourcengruppe AKS>_<Cluster Name> _<Region>

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

Walkthrough

Wer den Einsatz des Azure Kubernetes Service Hands-on ausprobieren möchte, findet bei Microsoft ein gutes Tutorial [35]. Im Beispiel wird ein AKS-Cluster Schritt für Schritt aufgebaut und deckt Themen wie Skalierung, Ingress, Zertifikatsmanagement und Monitoring mit ab (Abb.10).

Abb. 10: Tutorial zum Aufbau eines AKS-Clusters (Quelle: Microsoft)

 

 

Fazit

Wer eine Multi-Container-Applikation mit Kubernetes hosten möchte, findet in AKS einen Dienst, der gegenüber einer eigens betriebenen Kubernetes-Landschaft viel Unterstützung bietet und den Aufwand beim Aufbau und beim Betrieb reduziert. Wer simplere Containerarchitekturen in der Azure-Cloud betreiben will, ist eventuell mit Azure Container Instances [36] oder Azure App Service [37] besser beraten. Auch ein Blick in den neuen (Preview) Service Azure Container Apps lohnt sich [38].

 

 

Links & Literatur

[1] https://docs.microsoft.com/en-us/azure/aks/concepts-clusters-workloads

[2] https://docs.microsoft.com/en-us/azure/aks/node-auto-repair

[3] https://docs.microsoft.com/en-us/azure/aks/uptime-sla

[4] https://docs.microsoft.com/en-us/azure/aks/operator-best-practices-multi-region

[5] https://docs.microsoft.com/en-us/azure/aks/concepts-network

[6] https://github.com/kubernetes-sigs/cloud-provider-azure

[7] https://github.com/container-storage-interface/spec/blob/master/spec.md

[8] https://docs.microsoft.com/en-us/azure/aks/azure-disk-csi

[9] https://docs.microsoft.com/en-us/azure/aks/concepts-storage

[10] https://octant.dev

[11] https://docs.microsoft.com/en-us/azure/aks/private-clusters

[12] https://docs.microsoft.com/en-us/azure/aks/concepts-network

[13] https://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-container-registries-usage

[14] https://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-kubernetes-introduction

[15] https://docs.microsoft.com/en-us/azure/aks/policy-reference

[16] https://docs.microsoft.com/en-us/azure/aks/use-network-policies

[17] https://docs.microsoft.com/en-us/azure/aks/managed-aad

[18] https://docs.microsoft.com/en-us/azure/aks/node-updates-kured

[19] https://github.com/Azure/sg-aks-workshop/tree/master/day2-operations

[20] https://docs.microsoft.com/en-us/azure/aks/upgrade-cluster#set-auto-upgrade-channel

[21] https://docs.microsoft.com/en-us/azure/aks/cluster-autoscaler

[22] https://keda.sh/docs/2.4/scalers/azure-monitor/

[23] https://azure.microsoft.com/en-us/resources/videos/azure-friday-virtual-kubelet-introduction/

[24] https://docs.microsoft.com/en-us/azure/aks/concepts-scale

[25] https://github.com/Azure/sg-aks-workshop/tree/master/day2-operations

[26] https://www.nginx.com

[27] https://azure.github.io/application-gateway-kubernetes-ingress/

[28] https://keda.sh/docs/2.0/deploy/

[29] https://microsoft.github.io/AzureTipsAndTricks/blog/tip277.html

[30] https://docs.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity

[31] https://docs.microsoft.com/en-us/azure/aks/kubernetes-helm

[32] https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/containers/aks-microservices/aks-microservices

[33] https://docs.microsoft.com/en-us/azure/aks/start-stop-cluster?tabs=azure-cli

[34] https://docs.microsoft.com/en-us/azure/aks/configure-azure-cni

[35] https://docs.microsoft.com/en-us/learn/modules/aks-workshop/01-introduction

[36] https://docs.microsoft.com/en-us/azure/container-instances/

[37] https://docs.microsoft.com/de-de/azure/app-service/overview

[38] https://azure.microsoft.com/en-us/services/container-apps/#overview

 

 

 

The post Azure Kubernetes Service zu Diensten appeared first on BASTA!.

]]>
Improving Agility by Using Customers’ Definitions of “Quality” and “Done” https://basta.net/blog/improving-agility/ Wed, 09 Mar 2022 14:33:35 +0000 https://basta.net/?p=85070 “Quality”… velocity, productivity, and efficiency? Improved performance? Few or no bugs? Meets stakeholder requirements? “Done”… we did what we planned? Fits business objectives? Coded, tested, documented, and deployable?

The post Improving Agility by Using Customers’ Definitions of “Quality” and “Done” appeared first on BASTA!.

]]>
In this keynote session of BASTA! Spring 2022, you will learn with Debbie Levitt how to change processes to improve agility, eliminate some Lean waste, and produce better customer outcomes.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

 

Wollen Sie stets auf dem neusten Stand bleiben und keine weiteren Keynotes oder andere Videos verpassen? Dann melden Sie sich für unseren Newsletter an und wir informieren Sie, sobald es etwas neues in den Bereichen .NET,Windows & Open Innovations gibt.

SIE LIEBEN AGILE?

Entdecken Sie die BASTA! Tracks

The post Improving Agility by Using Customers’ Definitions of “Quality” and “Done” appeared first on BASTA!.

]]>
5 Top-Argumente für Ihren BASTA!-Besuch https://basta.net/blog/5-argumente-fuer-die-basta/ Wed, 17 Nov 2021 13:41:21 +0000 https://basta.net/?p=83730 Ein .NET-Entwickler konzeptioniert, designt und entwickelt Softwareanwendungen, welche auf die Bedürfnisse des Unternehmens zugeschnitten sind. Neben der Bedarfsermittlung und Analyse der Anforderungen, gehören auch der Support und die stetige Weiterentwicklung zu seinen Aufgaben.

The post 5 Top-Argumente für Ihren BASTA!-Besuch appeared first on BASTA!.

]]>
Kontinuierliches Lernen hat hohen Stellenwert

 

In einer schnelllebigen Industrie wie der IT müssen Sie als Entwickler Ihr Wissen laufend aktualisieren. Die digitale Transformation und der damit einhergehende Aufbruch in eine neue digitale Ära bringt unzählige neue Möglichkeiten mit sich und führt dazu, dass eingefahrene Prozesse und Strukturen hinterfragt, neue Trends eingeschätzt und in die konkreten Projekte implementiert werden müssen. Mit einem BASTA!-Besuch können Sie bestehende Probleme lösen und das nötige Wissen erlangen.

Wenn Sie noch nicht überzeugt sind, warum es sich wirklich lohnt, an der BASTA! teilzunehmen, haben wir für Sie 5 unschlagbare Argumente parat. Machen Sie sich schon mal auf den Weg!

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

The post 5 Top-Argumente für Ihren BASTA!-Besuch appeared first on BASTA!.

]]>
In die Azure-Cloud loggen https://basta.net/blog/in-die-azure-cloud-loggen/ Wed, 20 Oct 2021 10:26:27 +0000 https://basta.net/?p=83332 Sei es bei Amazon (AWS) oder bei Microsoft (Azure), Cloud-Lösungen haben sich bewährt, und so wagen immer mehr Unternehmen den Schritt in die Wolken. In diesem Artikel lernen wir drei verschiedene Wege kennen, wie wir Anwendungslogs in Azure mit dem Log-Analytics-Arbeitsbereich-Dienst sammeln und auswerten können, und was es dabei zu beachten gibt.

The post In die Azure-Cloud loggen appeared first on BASTA!.

]]>
Cloud-Dienste stehen in unterschiedlichen Modellen zur Verfügung. Neueinsteiger, die bereits eine etablierte Anwendungslandschaft mit verschiedenen Applikationen haben, beginnen dabei oft mit dem Infrastructure-as-a-Service-Modell (kurz IaaS). Bei diesem Ansatz werden die eigenen Anwendungen zu in der Cloud gehosteten virtuellen Maschinen (nachfolgend VM genannt) migriert. Nach der Migration möchte man sichergehen, dass sich das System wie erwartet verhält. Ein bekanntes Mittel zur Prüfung der Systemgesundheit sind die Anwendungslogs. Sofern diese nicht mit Fehlermeldungen befüllt werden, gehen wir davon aus, dass das System einwandfrei ausgeführt wird.

Mein Kollege Roland Krummenacher hat in einem früheren Artikel [1] bereits einen Weg aufgezeigt, wie Logdaten in die Azure Cloud gesammelt werden können. Dazumal wurde eine Lösung unter der Verwendung des Azure-Storage-Diensts erläutert. In diesem Artikel möchte ich Ihnen den aktuellen Ansatz unter der Verwendung des Azure-Log-Analytics-Arbeitsbereich-Diensts vorstellen.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Daten sammeln

Ausgangslage für die Codebeispiele in diesem Artikel bildet ein Legacy-System bestehend aus einer oder mehreren .NET-Anwendungen, das auf VMs in der Azure Cloud migriert wird, und dessen Daten wir nun in Azure sammeln und auswerten möchten. Als Logging-Framework setzen wir hier auf log4net [2]. Listing 1 zeigt eine log4net-Konfiguration, mit der die Anwendungslogs in Textdateien geschrieben werden. Eine Logmeldung besteht in der Regel aus verschiedenen Attributen, die sich für die textuelle Ausgabe als Layout konfigurieren lassen. Für unser Beispiel beschränken wir uns auf die Attribute Datum (%date), Level (%level), Logger (%logger) und Meldung (%message).

Listing 1:
<log4net>
  <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
    <file value="example.log" />
    <appendToFile value="true" />
    ...
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date %-5level - %logger - %message%newline" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="RollingFile" />
  </root>
</log4net>

Und so sieht es anschließend in den Logdateien aus:

2021-04-26 14:07:04,146 INFO  - StartUpLogger - Service Started
2021-04-26 14:04:48,843 INFO  - StartUpLogger - Service Stopped
2021-04-26 14:23:25,971 ERROR - StartUpLogger - The service terminated abnormally
2021-04-26 14:23:37,744 FATAL - StartUpLogger - The service did not start successfully

Der Inhalt dieser Logdateien soll nun inAzurein einen Log-Analytics-Arbeitsbereich übertragen werden. Der Log-Analytics-Arbeitsbereich-Dienst speichert die Daten in sogenannte Protokolle. Für ein besseres Verständnis, was Protokolle sind, können Datenbanktabellen für einen Vergleich herangezogen werden. Indiesem Kontext wäre ein Log-Analytics-Arbeitsbereich eine Datenbank, und die Protokolle wären die Tabellen. Zur besseren Auswertung der gesammelten Daten empfiehlt es sich, die Anwendungsdaten in benutzerdefinierte Protokolle zu schreiben [3]. Auf YouTube finden sich viele gute Schritt-für-Schritt-Anleitungen, wie ein Log-Analytics-Arbeitsbereich samt benutzerdefiniertem Protokoll aufgesetzt werden kann [4]. In unserem Beispiel kreieren wir einen Log-Analytics-Arbeitsbereich mit der Bezeichnung „MyLogAnalyticsWorkspace“ und legen ein eigenes, benutzerdefiniertes Protokoll namens „MyLog_CL“ an (Abb. 1).

 

Abb. 1: Log-Analytics-Arbeitsbereich mit benutzerdefiniertem Protokoll

 

Variante 1: Log Analytics Agent

Eine Möglichkeit, die Anwendungslogs mit wenig Aufwand zu sammeln und an den Log-Analytics-Arbeitsbereich zu übermitteln, bietet der Log Analytics Agent (auch bekannt als Microsoft Monitoring Agent, kurz MMA) [5]. Der MMA kann als VM Extension für Azure VMs über das Azure Portal aktiviert und so auf der VM installiert werden. Danach wird der MMA als Windows-Dienst auf dem installierten Windows-System ausgeführt. Beim Einrichten des benutzerdefinierten Protokolls wird der MMA mit den Ablageorten der Logdateien konfiguriert. Danach registriert er automatisch Änderungen in den Logdateien und überträgt diese an den Log-Analytics-Arbeitsbereich direkt in das benutzerdefinierte Protokoll [6]. Nebenbei erwähnt kann der MMA auch außerhalb von Azure verwendet werden, beispielsweise auf einem On-Site-Server (Abb. 2). Das ist vor allem dann praktisch, wenn bei der Migration indieCloud nicht alle Anwendungen auf einen Streich migriert werden.

 

Abb. 2: Logdaten mit dem Log Analytics Agent (MMA) sammeln

 

 

 

Ist der MMA samt benutzerdefiniertem Protokoll eingerichtet, sollten nach ein paar Minuten die ersten Logdaten im Log-Analytics-Arbeitsbereich sichtbar sein (Abb. 3).

 

Abb. 3: Logdaten im eigenen Protokoll inAzure abfragen

 

Die Sammlung der Logdaten mittels MMA ist zwar schnell und einfach umgesetzt, doch hat sie auch Einschränkungen. So werden zum Beispiel zirkuläre Schreibvorgänge, bei denen alte Einträge in der Logdatei mit neuen Meldungen überschrieben werden, vom MMA nicht unterstützt [6]. Auch muss die Logdatei entweder nach dem ASCII- oder dem UTF-8-Standard kodiert sein, damit der MMA den Inhalt verarbeiten kann. Ein weiterer Wermutstropfen könnte in der Anzahl der Anwendungen liegen, aus denen das Legacy-System besteht. Bei vielen Anwendungen mit unterschiedlichen Logdateien erweist sich die manuelle Konfiguration der MMAs als zeitraubende Tätigkeit.

 

Abb. 4: Aufbereitete Logdaten im eigenen Protokoll inAzure

 

Wir haben nun die Anwendungslogs erfolgreich nach Azure übertragen und möchten sie auswerten. In der aktuellen, rohen Form ist eine Auswertung etwas schwierig, da wir alle Attribute der Logmeldung (Datum, Level, Logger, Meldung) in einem String zusammengefasst haben (in unserem Beispiel wäre das das Feld RawData). Hier kommt die Kusto Query Language (kurz KQL) ins Spiel. Je nach Aufbau der Logmeldungen lassen sich die Attribute durch geschickte KQL-Anweisungen extrahieren [7]. Mit dem in Listing 2 gezeigten KQL können die Logeinträge aus unserem Beispiel in ihre Bestandteile aufgelöst werden (Abb. 4).

 

Listing 2
.MyLog_CL
| extend _firstDashIdx = indexof(RawData, '-', 20)
| extend _secondDashIdx = indexof(RawData, '-', _firstDashIdx + 1)
| extend
   Datum = substring(RawData, 0, 10),
   Zeit = substring(RawData, 11, 12),
   Level = trim_end(@"[^\w]+", substring(RawData, 24, 5)),
   Logger = substring(RawData, _firstDashIdx+1, _secondDashIdx-_firstDashIdx-2),
   Meldung = substring(RawData, _secondDashIdx + 1)
| project Datum, Zeit, Level, Logger, Meldung

 

Statt die einzelnen Attribute einer Logmeldung über KQL-Abfragen jedes Mal zu extrahieren, kann inAzure auch von benutzerdefinierten Feldern Gebrauch gemacht werden. Benutzerdefinierte Felder können als Tabellenspalten betrachtet werden und werden direkt von Azure beim Schreiben der Logmeldungen befüllt. Zur Erstellung von benutzerdefinierten Feldern markiert man im Ergebnisbereich einer KQL-Abfrage den Teil des Textes, der für das benutzerdefinierte Feld extrahiert werden soll. Eine ausführliche Anleitung findet sich in Microsofts Onlinedokumentation [8]. Aufgrund des markierten Textbereiches ermittelt eininAzureintegrierter Algorithmus automatisch, welche Textteile zum benutzerdefinierten Feld gehören und welche nicht (Abb. 5).

 

Abb. 5: Benutzerdefinierte Felder extrahieren

MyLog_CL
| project TimeGenerated, Level_CF, Logger_CF, Meldung_CF

 

SIE LIEBEN AZURE?

Entdecken Sie die BASTA! Tracks

 

Die benutzerdefinierten Felder lassen sich zwar einfacher auswerten, doch kann es vorkommen, dass der von Azure verwendete Algorithmus die Felder nicht mit den richtigen Informationen befüllt. Das ist vor allem dann der Fall, wenn sich das Format der Logmeldungen aufgrund von neuen Attributen ändert. Azure verlässt sich bei der Extraktion von benutzerdefinierten Feldern darauf, dass das Format der Logmeldungen stabil bleibt. Das heißt, würde man die bestehenden Logmeldungen um weitere Attribute ergänzen, z. B. Thread-ID, IP-Adresse, Benutzername usw., könnte das das Log durcheinanderbringen. Man könnte indiesem Fall zwar auf eine eigene Extraktionsanweisung mittels KQL (wie in Abb. 4) zurückgreifen, doch es geht auch anders.

 

Abb. 6: Benutzerdefinierte Felder abfragen

 

Variante 2: HTTP-Datensammler-API

Azure Log Analytics bietet zusätzlich ein HTTP-Datensammler-API (nachfolgend Web-API) an, an das die Logmeldungen direkt übertragen werden können. Microsoft stellt eine Beispielimplementation in seiner Onlinedokumentation bereit, mit der sich das Web-API ansprechen lässt [9]. Doch statt das Web-API selbst anzusprechen, kann man auf bequemere Mittel zurückgreifen. Eines davon wäre das Open-Source-Projekt Log4ALA, das als NuGet-Paket frei verfügbar ist [10]. Log4ALA bietet einen für den Azure-Log-Analytics-Arbeitsbereich konfigurierbaren log4net-Appender (Listing 3), der sich direkt in die eigene Anwendungskonfiguration integrieren lässt.

 

Listing 3
<log4net>
  ...
  <appender name="Log4ALAAppender" type="Log4ALA.Log4ALAAppender, Log4ALA">
    <!-- Hier die ID des Log-Analytics-Arbeitsbereichs eintragen -->
    <workspaceId value="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
    <!-- Hier den primären oder sekundären Zugriffschlüssel eintragen -->
    <SharedKey value="....................................==" />
    <!-- Name des benutzerdefinierten Protokolls -->
    <logType value="MyLog4ALA_CL" />
    <!-- Optional: Soll das Log-Level mitübertragen werden? (Standard: true) -->
    <appendLogLevel value="true" />
    <!-- Optional: Soll der Logger mitübertragen werden? (Standard: true) -->
    <appendLogger value="true" />
  </appender>
  <root>
    <appender-ref ref="Log4ALAAppender" />
  </root>
</log4net> 

 

Für die Authentifizierung gegenüber dem Web-API ist neben der Log-Analytics-Arbeitsbereich-ID auch einer der beiden Zugriffsschlüssel (primär oder sekundär) benötigt. Hierbei handelt es sich um schützenswerte Informationen, die nicht in der Quellcodeverwaltung eingecheckt werden sollten.

Gesetzt den Fall, dass es bei der Übertragung der Anwendungslogs zu Verbindungsproblemen kommt, kann Log4ALA so konfiguriert werden, dass nicht übertragene Logmeldungen in einer lokalen Textdatei niedergeschrieben werden, sodass sie nicht verloren gehen. Weiter kann Log4ALA auch mehr als nur einen Log-Analytics-Arbeitsbereich zeitgleich ansprechen. Im Gegensatz zu den einfachen Codebeispielen von Microsoft zur Ansteuerung des Web-APIs bietet Log4ALA eine interne Verarbeitungs-Queue, die verhindert, dass es bei der Übermittlung der Anwendungslogs über das Netzwerk zu Ausführungsverzögerungen innerhalb der .NET-Anwendung kommt.

Anders als in der ersten Variante mit dem MMA lässt sich bei der Log4ALA-Variante kein Logmuster mit Attributen konfigurieren. Sofern nichts weiter konfiguriert ist, füllt Log4ALA lediglich die Attribute Level, Logger und Meldung eines Logeintrags aus (Abb. 7).

 

Abb. 7: Durch Log4ALA protokollierte Logmeldungen abfragen

Möchte man zusätzliche Attribute protokollieren, so bietet Log4ALA zwei Möglichkeiten. Die eine Möglichkeit besteht darin, die Anwendungslogs im JSON-Format im Code zu verfassen. Die andere setzt voraus, dass die Attribute und Werte einer Logmeldung mit einem Trennzeichen (z. B. Gleichheitszeichen =) und Separator (z. B. Semikolon 😉 getrennt sind. Log4-ALA kann so konfiguriert werden, dass es aufgrund des Trennzeichens und Separators automatisch Attribute und ihre zugehörigen Werte erkennt und für sie benutzerdefinierte Felder in Azure erstellt, die wiederum einfach abgefragt werden können (Listing 4).

 

Listing 4
public void Log4AlaSample(int requestId)
{
  ILog log = LogManager.GetLogger(this.GetType());
 
  // Logmeldungen im JSON-Format:
  // {"requestId":"123456", "message":"Anfrage eingetroffen"}
  log.Info($"{{\"requestId\":\"{requestId}\", " +
           + $"\"message\":\"Anfrage eingetroffen\"}}");
 
  // Logmeldungen mit '=' als Trennzeichen und ';' als Separator:
  // requestId=123456; message=Anfrage eingetroffen
  log.Info($"requestId={requestId};message=Anfrage eingetroffen");
}

 

Beide vorgestellten Möglichkeiten funktionieren nur, sofern die eigene Legacy-Anwendung bereits indiesem Format die Anwendungslogs schreibt. Möchte man bestimmte Attribute nachträglich abfüllen, müssen Änderungen im Code vorgenommen werden, was bei Legacy-Anwendungen selten eine willkommene Lösung ist.

 

 

 

Variante 3: Application Insights

Falls man neben dem reinen Anwendungslog noch zusätzliche Telemetriemetriken wie Arbeitsspeicher, CPU, Netzwerklatenz usw. sammeln und auswerten möchte, könnte auch der Einsatz des Diensts Application Insights in Frage kommen. Application Insights ist die Rundum-Monitoring-Lösung für Anwendungen in der Azure-Cloud [11].

Zur Anbindung von Application Insights in unsere Beispielanwendungen stellt Microsoft ein NuGet-Paket mit einem konfigurierbaren log4net-Appender zur Verfügung [12]. Anschließend kann der Appender in der log4net-Konfiguration ergänzt werden (Listing 5).

 

Listing 5
<log4net>
  ...
  <appender name="aiAppender" type="Microsoft.ApplicationInsights.Log4NetAppender.ApplicationInsightsAppender, Microsoft.ApplicationInsights.Log4NetAppender">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%message%newline" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="aiAppender" />
  </root>
</log4net>

 

Damit der Application-Insights-Appender die Daten auch überträgt, muss die Telemetriekonfiguration eingerichtet werden. Sie benötigt, anders als bisher, nicht die Log-Analytics-Arbeitsbereich-ID, sondern den Instrumentierungsschlüssel des Application-Insights-Diensts, der mit dem Log-Analytics-Arbeitsbereich inAzure verknüpft ist. Der Instrumentierungsschlüssel kann entweder über eine ApplicationInsights.config-Datei oder direkt im Quellcode mit der nachfolgenden Anweisung konfiguriert werden [13]:

 

TelemetryConfiguration.Active.InstrumentationKey =
  "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

 

Wenn alles richtig konfiguriert ist, erscheinen die Logmeldungen im Log-Analytics-Arbeitsbereich in dem für Application Insights vorgegebenen Protokoll AppTraces (Abb. 8).

 

Abb. 8: Durch Application Insights protokollierte Logmeldungen abfragen

 

Dass der Application-Insights-Dienst derart viele Daten sammelt, hat nicht nur Vorteile. Durch die vielen Mehrdaten, die protokolliert werden, fallen auch höhere Kosten an. Daher lohnt es sich hier, die Konfiguration des Diensts nochmals zu prüfen und nicht benötigte Telemetriedaten vom Monitoring auszuschließen.

 

Wie weiter?

Dank der Tatsache, dass wir die Anwendungslogs im Log-Analytics-Arbeitsbereich konzentriert haben, erschließen sich uns nun neue Möglichkeiten [14]. Wir können beispielsweise in regelmäßigen Abständen unsere Anwendungslogs prüfen und proaktiv Warnungen über E-Mail oder SMS versenden, sobald Ungereimtheiten in der Anwendungsausführung ersichtlich sein sollten. Außerdem sind wir nun in der Lage, basierend auf unseren KQL-Abfragen ausgeklügelte Reports mittels PowerBI zu erstellen, grafische Diagramme direkt im Azure-Dashboard einzubinden oder durch den Azure-Sentinel-Dienst [15] vorzeitig auf mögliche Sicherheitsbedrohungen zu reagieren. Darüber hinaus können die Daten auch über ein Web-API für sonstige Weiterverarbeitungen bereitgestellt werden. Da das Azure-Dienstangebot ständig ausgeweitet wird, kommen fortlaufend neue Verarbeitungsmöglichkeiten hinzu.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Zusammenfassung

Wir haben nun drei Möglichkeiten besprochen, wie die Anwendungslogs in der Azure-Cloud gesammelt und ausgewertet werden können, sowie weitere Optionen angesprochen, wie diese Daten weiterverarbeitet werden könnten. Tabelle 1 gibt eine grobe Zusammenfassung. Wie immer gilt es, die Vor- und Nachteile der vorgestellten Lösungsansätze abzuwägen und diejenige Variante zu identifizieren, diedie eigene Problemstellung am besten beantwortet.

Variante Vorteile Nachteile
1. Log Analytics Agent + benutzerdefinierte Felder können bei klarem Logformat einfach definiert werden

+ ohne Änderungen am bestehenden Code einsetzbar, da nur die Logdateien berücksichtigt werden

– nachträgliche Anpassungen am Logformat können sich negativ auf benutzerdefinierte Felder auswirken

– für die Logdateien gelten Einschränkungen (kein zirkuläres Schreiben, nur ASCII- oder UTF-8-Format)

– hat man ein System mit sehr vielen Anwendungen, kann die manuelle Einbindung der Logdateien zeitraubend sein

2. HTTP-Datensammler-API + dank Open-Source-Bibliotheken wie Log4ALA ist eine Einbindung sehr einfach – ohne intelligenten Puffermechanismus (wie ihn z. B. Log4ALA mittels interner Queue besitzt) können viele Nachrichten die Ausführung der Anwendung negativ beeinflussen

– nachträgliches Ergänzen von benutzerdefinierten Felder kann umständliche Anpassungen im Quellcode erfordern

3. Application Insights + zusätzliche Telemetriedaten können sehr nützlich für Fehleranalyse sein – höhere Kosten, da viel mehr Daten gesammelt werden

Tabelle 1: Zusammenfassung der Vor- und Nachteile der vorgestellten Varianten

 

Links & Literatur

[1] Krummenacher, Roland: „Sammeln von Client-Log-Dateien in der Cloud“; in: Windows Developer 10.2014

[2] https://logging.apache.org/log4net/

[3] https://docs.microsoft.com/de-de/azure/azure-monitor/logs/quick-create-workspace

[4] https://www.youtube.com/watch?v=N-aYZ3WDRII

[5] https://docs.microsoft.com/de-de/azure/azure-monitor/agents/agent-windows

[6] https://docs.microsoft.com/de-de/azure/azure-monitor/agents/data-sources-custom-logs

[7] https://docs.microsoft.com/de-de/azure/azure-monitor/logs/parse-text

[8] https://docs.microsoft.com/de-de/azure/azure-monitor/logs/custom-fields

[9] https://docs.microsoft.com/de-de/azure/azure-monitor/logs/data-collector-api

[10] https://ptv-logistics.github.io/Log4ALA

[11] https://docs.microsoft.com/de-de/azure/azure-monitor/app/app-insights-overview

[12] https://www.nuget.org/packages/Microsoft.ApplicationInsights.Log4NetAppender

[13] https://docs.microsoft.com/en-us/azure/azure-monitor/app/configuration-with-applicationinsights-config

[14] https://docs.microsoft.com/de-de/azure/azure-monitor/logs/data-platform-logs

[15] https://azure.microsoft.com/de-de/services/azure-sentinel

 

The post In die Azure-Cloud loggen appeared first on BASTA!.

]]>
Building and running anything with the Windows Subsystem for Linux https://basta.net/blog/windows-subsystem-for-linux/ Fri, 15 Oct 2021 10:00:45 +0000 https://basta.net/?p=83241 Can you really use Windows to develop Linux apps? How real is WSL (Windows Subsystem for Linux) and how close is it to reality? Join Scott Hanselman as he walks you through the state of the art of Linux on Windows. What’s possible, what’s not, what about the Windows Terminal and Docker? Tons of live demos in this highly technical talk!

The post Building and running anything with the Windows Subsystem for Linux appeared first on BASTA!.

]]>
In this keynote session of BASTA! Mainz 2021, you will learn with Scott Hanselman on what is important.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

 

Wollen Sie stets auf dem neusten Stand bleiben und keine weiteren Keynotes oder andere Videos verpassen? Dann melden Sie sich an und wir informieren Sie, sobald es etwas neues in den Bereichen .NET,Windows & Open Innovations gibt

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

The post Building and running anything with the Windows Subsystem for Linux appeared first on BASTA!.

]]>
Einstieg in gRPC mit ASP.NET Core https://basta.net/blog/grpc-mit-asp-net-core/ Wed, 06 Oct 2021 10:15:53 +0000 https://basta.net/?p=83162 Ein Gamechanger in der Datenkommunikationstechnologie?
Der Grundgedanke des klassischen RPC (Remote Procedure Call – Aufruf einer fernen Prozedur) existiert bereits seit 1976. Google hat ein eigenes Protokoll, gRPC, entworfen, das für große Daten optimal ist und ökosystemübergreifend für viele aktuelle Sprachen zur Verfügung steht. Das perfekte Setting, um die Datenkommunikationstechnologie umzukrempeln.

The post Einstieg in gRPC mit ASP.NET Core appeared first on BASTA!.

]]>
Der Quasistandard bei den Datenkommunikationstechnologien ist zur heutigen Zeit HTTP. Dazu kommen Architekturstile und APIs wie REST, GraphQL und WebSockets zum Einsatz. Jede dieser einzelnen Möglichkeiten hat ihre eigenen Stärken und Schwächen. Einen Gewinner für alle Anforderungen gibt es hierbei nicht. Eher zählt die Devise, auf eine polyglotte Lösung zu setzen. Jetzt haben wir mit gRPC einen weiteren Mitspieler dazu gewonnen. Ist dieser jetzt der totale Gamechanger der Datenkommunikationstechnologie? Und wie können wir diesen in unseren .NET-Projekten nutzen? Tauchen wir gemeinsam ab, in dieses spannende neue Thema.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Einführung in gRPC

Das gRPC [1] ist ein Protokoll, um Funktionen in verteilten Computersystemen aufzurufen. Es wurde von Google entworfen und 2016 veröffentlicht. gRPC basiert auf dem HTTP/2-Standard und setzt auf Protocol Buffers. Google hat es der Cloud Native Computing Foundation gespendet [2]. Es steht ökosystemübergreifend für C, C++, C#, Dart, Go, Java, Kotlin, Node.js, Objective-C, PHP, Python und Ruby zur Verfügung. Die Datenkommunikation findet hier komprimiert im binären Datenformat statt, das gerade für richtig große Daten optimal ist. Eine weitere, besondere Stärke ist eine Echtzeitkommunikation über Streaming. Ein gRPC-Dienst unterstützt alle Streamingkombinationen: Unär (kein Streaming – einfache Client-Server-Kommunikation), Streaming vom Server zum Client, Streaming vom Client zum Server und bidirektionales Streaming (ein Streaming in beide Richtungen zur gleichen Zeit).

Der hauptsächliche Anwendungszweck von gRPC besteht eher in der Server-to-Server-Kommunikation. Es kann ebenfalls für native Desktop-, Mobile-, oder IoT-Clients ideal sein, die leistungsschwache Hardware haben und eine große Menge Daten beanspruchen. Eher suboptimal wären hierbei, anders als erwartet, HTTP-Clients. Obwohl gRPC über HTTP/2 bereitgestellt wird, gibt es kein natives Browser-API, um mit gRPC kommunizieren zu können. Es gibt zwar die Möglichkeit, mit zusätzlichen Tools und Libraries wie gRPC-Web eine gRPC-Kommunikation über einen HTTP-Client aufzubauen, in der Praxis erweist sich das allerdings als nicht gerade komfortabel. Eine Herausforderung ist hier auf jeden Fall das binäre Datenformat, womit man auf die Generierung von Client-Proxy-Code angewiesen ist. Das Integrieren der Codegenerierungstools beansprucht nochmal einiges an Zeit und ist nicht Native-WebDev-like. Ein weiterer, bitterer Nachgeschmack ist ebenfalls eine technische Einschränkung: Das gleichzeitige Streamen der Daten in beide Richtungen wird nicht unterstützt. Eine Ausnahme betrifft die unattraktive Toolunterstützung: Blazor-WebAssembly-Apps. Microsoft hat hierbei eine fertig integrierte Lösung in Visual Studio bereitgestellt. Es empfiehlt sich allerdings, auch hier nur gRPC einzusetzen, wenn wirklich große Daten permanent ausgetauscht werden müssen. Wieso sollte man einen Lamborghini fahren, wenn die Geschwindigkeitsgrenze bei dreißig liegt. Denn eines hat die Praxis dem Autor gezeigt: Auf Codegeneratoren angewiesen zu sein, kann die Produktivität stark beeinflussen. Nach jedem Build wird der Proxy-Code neu generiert und wenn das Tooling mal gerade nicht so will, muss man regelmäßig Visual Studio neustarten, damit alles wieder ordnungsgemäß funktioniert. Nicht gerade berauschend bei der täglichen Arbeit. Das Bereitstellen des eigenen gRPC API an externe Konsumenten ist ebenfalls nicht gerade überwältigend. Es gibt im Grunde genommen keinen Playground und auch keine einfache Art, diese Schnittstellen zu dokumentieren. Die bereits erwähnte Abhängigkeit von der Codegenerierung lässt einen die Developer Experience von gRPC dann mit der Schulnote befriedigend bis ungenügend bewerten. In Gänze bedeutet es, dass man das seinen Kunden nicht gerade antun möchte und wirft auch kein gutes Bild auf das eigene Unternehmen.

Zugegeben, die letzten Passagen klingen nicht gerade motivierend. Es gibt allerdings wieder Anwendungsfälle, in denen gRPC eine prachtvolle Lösung ist. Haben Sie bereits eine native Desktop-App in WinForms oder WPF? Wurde hierbei WCF als Client-Server-Kommunikation gewählt und soll das Backend auf .NET 5 oder höher umgestellt werden? Oder sollen im Backend unterschiedliche Services miteinander kommunizieren? Oder haben Sie leistungsschwache Clients, die in Echtzeit große Daten austauschen sollen? Dann ist gRPC auf jeden Fall die ideale Technologie.

 

Design by Contract

Der Grundstein von gRPC beginnt mit einem Vertrag: Design by Contract. Hierbei gibt es zwei unterschiedliche Möglichkeiten, einen Vertrag aufzusetzen: Contract first und Code first. Bei Contract first handelt es sich um den offiziellen Weg, wie er uns in der .NET-Welt zur Verfügung steht. Die Services und ihre Message-Datentypen werden in einer eigenen Standardsprache (IDL, Interface Definition Language) beschrieben. Codegeneratortools können daraus dann Proxycode erzeugen und der Protocol Buffer Serializer kann die Messages damit bestens binär serialisieren und deserialisieren.

In Abbildung 1 sehen wir sogenannte *.proto-Dateien. Das sind normale Textdateien, die mit IDL den Dienst und die Datenstrukturen deklarieren. Daraus wird dann der notwendige Code generiert. Wir konzentrieren uns hauptsächlich auf die Contract-first-Variante.

Abb. 1: Der Grundstein von gRPC beginnt mit Contract first

 

Die zweite Möglichkeit besteht nicht aus einem offiziellen Weg von Microsoft, sondern aus einer aus der Community getriebenen Methode: Code first. Hier wird, wie aus WCF bekannt ist, über Attribute bei Interfaces (für Services) und Datenklassen (DTOs) deklariert, wie etwas für gRPC bereitgestellt werden soll. In Abbildung 2 sehen wir, dass daraus der notwendige Code erst zur Laufzeit über die Metaprogrammierung bereitgestellt wird. Das Besondere an dieser Lösung ist, dass wir unser bisheriges Know-how zu WCF wiederverwenden können und uns nicht erst in die neue Deklarationssprache IDL mit Protocol Buffer (Protobuf) einarbeiten müssen [3].

 

Abb. 2: Eine besondere Alternative ist der Code-first-Ansatz

 

 

 

Ein neues ASP.NET-Core-Projekt mit gRPC erzeugen

Um die Anatomie hinter gRPC besser zu verstehen, wollen wir interaktiv einige Beispiele implementieren. Microsoft stellt bereits eine gRPC-Projektvorlage für ASP.NET Core zur Verfügung, die über ein neues Projekt in Visual Studio 2019 oder über die .NET CLI ausgewählt werden kann. In der Eingabeaufforderung wird mit der .NET CLI das neue Projekt mit folgendem Befehl erzeugt:

 

dotnet new grpc –name HelloGrpc

 

Anschließend wurde ein neuer HelloGrpc-Ordner erzeugt, die darin enthaltene HelloGrpc.csproj-Datei öffnen wir gemeinsam mit Visual Studio 2019. Im Prinzip handelt es sich um ein gewohntes neues ASP.NET-Core-Projekt. Entdecken wir zunächst einmal die Unterschiede. Der gravierendste hierbei ist, dass bereits das Grpc.AspNetCore-NuGet-Paket vorinstalliert ist. Wie man es bereits von den Projektvorlagen gewohnt ist, kann man dieses Paket erst einmal manuell auf die neueste Version updaten. In der Startup.cs-Datei werden bei der ConfigureServices-Methode alle notwendigen Klassen beim Dependency-Injection-Provider registriert und instanziiert:

 

services.AddGrpc();

 

Mit einer Middlewareroute für die Routing-Pipeline wird unser gRPC Service zur Laufzeit für andere Clients zugänglich gemacht. Es ist bis jetzt nur eine Route registriert, und zwar für einen Beispieldienst:

 

endpoints.MapGrpcService<GreeterService>();

 

Dann bleiben noch die beiden Verzeichnisse Protos und Services übrig. Im Protos-Ordner befindet sich unsere Protocol-Buffer-Datei für Contract first, die wir jetzt genauer erforschen.

 

Protocol Buffers

Protocol Buffers [4] ist ein Datenformat zur Serialisierung mit der Schnittstellenbeschreibungssprache IDL. Auch diese wurde von Google entwickelt und bereits 2008 veröffentlicht. Hauptsächliche Entwurfskriterien der Protocol Buffers sind Einfachheit und Performance. Daher ist es im Gegensatz zu XML oder JSON als Binärformat konzipiert, das auf ein textuelles Format setzt. Die .NET-Implementierung (protobuf-net) gibt es allerdings erst seit 2014. Einen tatsächlichen Mehrwert des binären Formats gewinnt man allerdings erst bei richtig großen Datenmengen. Ein Vergleich von JSON und Protobuf hat gezeigt, dass bei einem gewöhnlichen Datenaustausch kein wirklicher Unterschied festzustellen ist.

 

Abb. 3: Die greet.proto-Datei beschreibt in IDL, wie Protobuf binär (de)serialisieren soll

 

In Abbildung 3 sehen wir den Inhalt der greet.proto-Datei. Zu Beginn wird auf die aktuelle Protobuf-Version [5] hingewiesen, was ausschlaggebend dafür ist, welche Sprachfeatures wir nutzen können. Die package-Deklaration entspricht einem Namespace unter C#, wodurch eine saubere Strukturierung der unterschiedlichen Dienste erfolgen kann. Die Daten zur Kommunikation werden mit Messages behandelt. Diese sind notwendig für Anfragen sowie für die zu liefernden Antworten. Im Prinzip handelt es sich dabei um Datentransferobjekte (DTOs).

In beiden Messages wird eine Zuweisung mit dem Wert 1 festgelegt. Dabei handelt es sich nicht um den tatsächlichen Integer-Wert 1, sondern um eine Indexierung vom jeweiligen Feld für die binäre Serialisierung. So soll unsere Datenkommunikation später stark optimiert werden können, indem nicht der tatsächliche Name im Binärformat serialisiert wird, sondern nur der Indexwert. Würden wir jetzt ein weiteres Feld hinzufügen, müssen wir natürlich mit Wert 2 aufsteigend weiter machen. Jeder Wert muss hierzu innerhalb einer Message einzigartig sein. Die explizite Deklaration soll hierbei eine kontrollierte Abwärtskompatibilität gewährleisten.

Anschließend wird hier das Service-Modell beschrieben. Der offizielle Styleguide [6] gibt auch vor, dass „Service“ im Namen enthalten sein sollte. Eine Service-Funktion beginnt mit der rpc-Deklaration gefolgt vom Funktionsnamen. Dann werden ebenfalls eine Parameter- und eine Rückgabe-Message festgelegt.

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

Die Codegenerierung von Protobuf zu .NET

In Visual Studio 2019 ist protobuf-net bereits ein fester Bestandteil, wodurch beim Abspeichern des Projekts oder notfalls bei einem Rebuild alle notwendigen Service-Kontexte und DTO-Klassen generiert werden. In Abbildung 4 sehen wir die versteckten Verzeichnisse unseres Projekts und den Ort, an dem die generierten Daten liegen. Es hat sich allerdings in der Praxis gezeigt, dass ein wiederholtes Neustarten von Visual Studio 2019 notwendig ist, wenn die Codegenerierung versagt.

 

Abb. 4: Die von Visual Studio 2019 generierten Daten für unseren gRPC Service

 

Den GreeterService erkunden

Der Beispielservice GreeterService ist im Services-Ordner enthalten. In Listing 1 wird die Implementierung gezeigt. Hier können wir schön erkennen, dass vom generierten Service-Kontextcode eine Basisklasse festgelegt wurde. Wir erhalten hier ebenfalls, wie man es vom ASP.NET-Core-Ökosystem gewohnt ist, Unterstützung durch Constructor Injection. Die Implementierung der Kommunikation erfolgt mit async/await. Bei dieser Implementierung handelt es sich um eine einfache Request-Response-Kommunikation

 

Listing 1: Der GreeterService mit einer Unary-Kommunikation
.namespace HelloGrpc
{
  public class GreeterService : Greeter.GreeterBase
  {
    private readonly ILogger _logger;
    public GreeterService(ILogger logger)
    {
      _logger = logger;
    }

    public override Task SayHello(HelloRequest request, ServerCallContext context)
    {
      return Task.FromResult(new HelloReply
      {
        Message = "Hello " + request.Name
      });
    }
  }
}

Kein Support für IIS Express

Ein ungewohnter Aspekt dieses Projekttemplates für ASP.NET Core ist, dass kein IIS-Express-Profil vorkonfiguriert ist. Das liegt daran, dass gRPC auf HTTP/2 läuft, das nicht von IIS Express unterstützt wird. Aus diesem Grund kann hier der Einsatz nur direkt über den leichtgewichtigen Cross-Platform-Webserver Kestrel erfolgen. Hier darf man auf keinen Fall vergessen, dass bei der eigenen Produktivumgebung der Support für HTTP/2 noch aktiviert werden muss. Der aktuelle Stand ist sogar, dass gRPC noch nicht über Azure mit iisnode unterstützt wird [7]. Eine Alternative wäre gRPC-Web, das nicht gerade berauschend ist, wie eingangs erwähnt wurde.

 

 

 

Wir bauen einen gRPC-Client

Zu unserem Projekt erzeugen wir einfach eine neue Konsolenanwendung innerhalb der Solution. Der Name wird auf HelloGrpc.Client festgelegt. Die nächsten Schritte dürften allen WSDL-, Web-Services- und WCF-Services-Entwicklern und -Entwicklerinnen bekannt vorkommen. Nach einem Rechtsklick auf das neue Konsolenprojekt wählen wir im Kontextmenü Add und dann Service reference … Anschließend öffnet sich ein Tab mit einer Übersicht über alle bereits hinzugefügten Services. Beim Klick auf den Add-Button von Service References öffnet sich ein Wizard (Abb. 5). Hier wählen wir gRPC Service aus und beim nächsten Dialog wird, wie in Abbildung 6 zu sehen ist, die Protobuf-Datei ausgewählt. Anschließend wird uns der notwendige Client-Proxy-Code generiert.

In Listing 2 steht der Code für einen einfachen Request-Response-Zugriff (Unary). Über die Solution-Eigenschaften müssen beide Projekte als Startprojekte festgelegt werden. Anschließend können wir die Anwendung ausführen und erhalten die Textausgabe „Hello World“ auf der Konsole.

 

Abb. 5: gRPC-Client generieren lassen

 

Abb. 6: Protobuf-Datei für Clientcodegenerierung auswählen

 

Listing 2: Der gRPC-Client für eine Unary-Kommunikation zum GreeterService
class Program
{
  static async Task Main(string[] args)
  {
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var greeter = new GreeterClient(channel);

    var result = await greeter.SayHelloAsync(new HelloRequest { Name = "World" });
    Console.WriteLine(result.Message);
    Console.ReadKey();
  }
}

Streaming-Service für Server-to-Client-Kommunikation implementieren

Eine besondere Stärke von gRPC ist eine Echtzeitkommunikation in unterschiedliche Streamingrichtungen. In diesem Beispiel soll der Server dem Client dauerhaft Informationen senden, während der Client einfache Antworten asynchron liefert. Ein idealer Algorithmus zur Veranschaulichung ist die Fibonacci-Folge. Diese beginnt mit einer 1, die mit einer weiteren 1 addiert wird. Daraus erhalten wir das Ergebnis 2. Dann folgt die weitere Berechnung mit 2 plus das letzte Ergebnis 1, wir erhalten 3 usw., bis wir zu einem festgelegten Ende gelangen. Listing 3 zeigt die dazugehörige Fibonacci-Implementierung, die wir für unserem gRPC Service benötigen.

Passend zur Implementierung fügen wir mit Visual Studio im Protos-Ordner eine neue Protobuf-Datei mit dem Namen fibonacci.proto hinzu. Hier werden der Service sowie die eingehende und ausgehende Message deklariert. Der wesentliche Aspekt im Vergleich zur greeting.proto-Datei ist, dass zusätzlich der Rückgabetyp mit einem stream-Keyword gekennzeichnet wird (Listing 4). Ein sehr wichtiger Schritt ist jetzt das Aktivieren des Protobuf-Compilers für unsere neue Datei. Das muss jeweils explizit pro Datei in den Dateieigenschaften erfolgen. Die Datei benötigt in Visual Studio die folgenden Eigenschaften: Build Action auf Protobuf compiler und gRPC Stub Classes auf Server only.

Listing 3: Die Fibonacci-Implementierung
public class Fibonacci
{
  public IEnumerable GetNumbers(int from, int to)
  {
    var previous = 0;
    var current = from;

    while (current <= to)
    {
      (previous, current) = (current, previous + current);
      yield return current;
    }
  }
}

 

Listing 4: Die fibonacci.proto-Datei für einen Server-to-Client Streaming
syntax = "proto3";

option csharp_namespace = "HelloGrpc.Services";

service Fibonacci {
  rpc GetNumbers(NumberRequest) returns (stream NumberResponse);
}

message NumberRequest {
  int32 from = 1;
  int32 to = 2;
}

message NumberResponse {
  int32 result = 1;
}

 

Im Services-Verzeichnis wird jetzt eine FibonacciService-Klasse angelegt. Sie soll von der generierten Server-Basisklasse Fibonacci.FibonacciBase ableiten. Über Constructor Injection soll unsere Fibonacci-Instanz injiziert werden. Die Basisklasse selbst bietet uns jetzt das Überschreiben einer GetNumbers-Methode an. In Listing 5 ist die fertige FibonacciService-Implementierung zu sehen. Die Stream-Kommunikation zum Client findet dann über den responseStream-Parameter statt.

 

Listing 5: Die Fibonacci-Service-Implementierung
public class FibonacciService : Fibonacci.FibonacciBase
{
  private readonly HelloGrpc.Fibonacci _fibonacci;

  public FibonacciService(HelloGrpc.Fibonacci fibonacci)
  {
    _fibonacci = fibonacci;
  }

  public override async Task GetNumbers(NumberRequest request, IServerStreamWriter responseStream, ServerCallContext context)
  {
    foreach (var current in _fibonacci.GetNumbers(request.From, request.To))
    {
      await responseStream.WriteAsync(new NumberResponse { Result = current });
      await Task.Delay(300);
    }
  }
}

 

Zum Schluss muss das Backend alle Bestandteile an der richtigen Stelle registriert haben. Das erfolgt in der Startup.cs-Datei. Bei der ConfigureService-Methode registrieren wir die Fibonacci-Klasse als Singleton beim Dependency Injection Provider mit:

 

services.AddSingleton<Fibonacci>();

 

Der neue Service muss ebenfalls als Route zur Verfügung gestellt werden. Das erfolgt mit einem Eintrag innerhalb der Configure-Methode:

 

endpoints.MapGrpcService<FibonacciService>();

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Streamingclient für Server-to-Client-Kommunikation implementieren

Der Client benötigt eine neue Client-Proxy-Implementierung unseres neuen Service. Wir machen dazu einen Rechtsklick auf das Konsolenprojekt. Im Kontextmenü wählen wir Add und dann Service reference … Es öffnet sich wieder der Tab mit der Übersicht über alle bereits hinzugefügten Services. Beim Klick auf den +-Button von Service References öffnet sich wieder der Wizard. Hierbei wählen wir gRPC Service aus und beim nächsten Dialog wird die neue fibonacci.proto-Datei ausgewählt. Anschließend wird uns der notwendige Client-Proxy-Code generiert.

In Listing 6 sehen wir dazu dann die passende Implementierung. Einige neue C#-Features kommen hier zum Einsatz. Zum Beispiel sorgt das using hinter der Variablendeklaration für eine flachere Codestruktur. Über das ResponseStream Property können wir asynchron mit ReadAllAsync auf die eingehenden Datenströme reagieren. Mit der await/Forearch-Deklaration funktioniert das auch wunderbar in Kombination mit der Foreach-Schleife. Beim gleichzeitigen Ausführen vom Service und Client können wir leicht verzögert sehen, wie der Service die Daten nacheinander an den Client ausliefert und dieser sie dann zur Ausgabe weitergibt.

 

Listing 6: Die Fibonacci-Client-Implementierung
class Program
{
  static async Task Main(string[] args)
  {
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var fibonacciClient = new FibonacciClient(channel);

    using var result = fibonacciClient.GetNumbers(new NumberRequest { From = 1, To = 100000000 });

    await foreach (var response in result.ResponseStream.ReadAllAsync())
    {
      Console.WriteLine("Response: " + response.Result);
    }

    Console.ReadKey();
  }
}

 

Bidirektionales Streaming

Eine technologische Meisterleistung von gRPC ist das bidirektionale Streaming. Damit ist das gleichzeitige Streamen von Server zu Client und umgekehrt gemeint. Das bisherige Beispiel kann zur Demonstration ganz einfach angepasst werden. Bei der fibonacci.proto-Datei erhält der Anfrageparameter zusätzlich das Stream-Schlüsselwort (Listing 7). Beim Speichern und Bauen erzeugt Visual Studio automatisch die neuen Proxydateien im Hintergrund.

 

Listing 7: Die fibonacci.proto-Datei für ein bidirektionales Streaming
syntax = "proto3";

option csharp_namespace = "HelloGrpc.Services";

service Fibonacci {
  rpc GetNumbers(stream NumberRequest) returns (stream NumberResponse);
}

message NumberRequest {
  int32 from = 1;
  int32 to = 2;
}

message NumberResponse {
  int32 result = 1;
}

 

Der FibonacciService erhält eine neue Methodensignatur von der Basisklasse. Der Request-Parameter wird zum RequestStream-Parameter. Diesen nutzen wir wieder mit einer asynchronen Foreach-Schleife, genauso, wie wir sie erst nur beim Client implementiert hatten. Listing 8 zeigt die neue Implementierung.

Listing 8: Die FibonacciService-Implementierung für ein bidirektionales Streaming
public class FibonacciService : Fibonacci.FibonacciBase
{
  private readonly HelloGrpc.Fibonacci _fibonacci;

  public FibonacciService(HelloGrpc.Fibonacci fibonacci)
  {
        _fibonacci = fibonacci;
  }

  public override async Task GetNumbers(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context)
  {
    await foreach (var request in requestStream.ReadAllAsync())
    {
      foreach (var current in _fibonacci.GetNumbers(request.From, request.To))
      {
        await responseStream.WriteAsync(new NumberResponse { Result = current });
        await Task.Delay(300);
      }
    }
  }
}

 

Der Client kann jetzt bei einer dauerhaften Verbindung nacheinander seine Daten senden. Zusätzlich möchten wir aber auch die Daten vom Server in einem eigenen Task beobachten. Die komplette Implementierung dazu ist in Listing 9 zu sehen.

 

Listing 9: Die Fibonacci-Client-Implementierung für ein bidirektionales Streaming
class Program
{
  static async Task Main(string[] args)
  {
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var fibonacciClient = new FibonacciClient(channel);

    using var result = fibonacciClient.GetNumbers();
    var task = Task.Run(async () => await WatchResponse(result));

    await result.RequestStream.WriteAsync(new NumberRequest { From = 1, To = 1000000 });
    await result.RequestStream.WriteAsync(new NumberRequest { From = 1, To = 1000000 });
    await result.RequestStream.WriteAsync(new NumberRequest { From = 1, To = 1000000 });
    await task;

    Console.ReadKey();
  }

  static async Task WatchResponse(AsyncDuplexStreamingCall<NumberRequest, NumberResponse> result)
  {
    await foreach (var response in result.ResponseStream.ReadAllAsync())
    {
      Console.WriteLine("Response: " + response.Result);
    }
  }
}

 

Fazit

Der Artikel hat gezeigt, was sich hinter gRPC verbirgt. Zusätzlich haben wir entdeckt, wie einfach gRPC unter .NET Core zum Einsatz kommen kann, aber auch, welche Schwächen wir dabei haben. Abbildung 7 zeigt nochmal alle Vor- und Nachteile. Wir konnten allerdings auf viele weitere Aspekte nicht eingehen, da sie den Rahmen für diesen Artikel sprengen würde. Dazu zählen eine korrekte Fehlerbehandlung, erweiterte Protobuf-Sprachfeatures, gRPC-Web und vieles Weitere. Passend zu diesem Artikel hat der Autor eine kostenlose Livestreamaufzeichnung als Video auf YouTube [8] bereitgestellt, in der auch die nicht besprochenen Aspekte ausführlich gezeigt werden. Viel Spaß beim Ansehen!

Links & Literatur

[1] https://grpc.io/docs/what-is-grpc/faq/

[2] https://www.cncf.io/blog/2017/03/01/cloud-native-computing-foundation-host-grpc-google/

[3] https://www.twitch.tv/videos/731620698

[4] https://developers.google.com/protocol-buffers

[5] https://developers.google.com/protocol-buffers/docs/proto3

[6] https://developers.google.com/protocol-buffers/docs/style

[7] https://devblogs.microsoft.com/aspnet/grpc-web-for-net-now-available/

[8] https://youtu.be/QUdr0qbEKE4?list=PLbWYOayzrhS_WPoubt7PV0h2n1fOimI2j

The post Einstieg in gRPC mit ASP.NET Core appeared first on BASTA!.

]]>
Software Development Quality Map https://basta.net/blog/software-development-quality-map/ Mon, 13 Sep 2021 09:04:58 +0000 https://basta.net/?p=81715 Gute Softwarequalität liegt jedem Entwicklungsteam am Herzen. Doch was ist, wenn die Software nicht die Qualität aufweist, die man sich vorgenommen hat? Die Software Development Quality Map zeigt den Lösungsweg: Sie nimmt Teams auf Entdeckungsreise durch 80 verschiedene Themen. Gehen Sie auf Schatzsuche!

The post Software Development Quality Map appeared first on BASTA!.

]]>
Die Software Development Quality Map nimmt Teams auf Entdeckungsreise durch 80 verschiedenen Themen. Sie segeln dabei durch den «Stream of Team Building», machen eine kurze Pause im «Gulf of Legacy Code», bevor sie an der «Bug Bay» und dem «Release Monster» zum Land «Product Release» kommen. Gehen Sie mit Ihrem Team auf Schatzsuche!

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

 

Wie funktioniert die Quality Map?

Gute Softwarequalität liegt jedem Entwicklungsteam am Herzen. Doch was ist, wenn die Software nicht die Qualität aufweist, die man sich vorgenommen hat? Die Software Development Quality Map von bbv zeigt den Lösungsweg: Sie nimmt Teams auf Entdeckungsreise und deckt in einer unterhaltsamen und spielerischen Art Verbesserungsmöglichkeiten auf. Das Video erklärt kurz und knackig, was auf der Software Development Quality Map alles zu entdecken gibt.

The post Software Development Quality Map appeared first on BASTA!.

]]>
Alle Zügel in einer Hand: Effektive Steuerung von Softwareentwicklungsprozessen https://basta.net/blog/alle-zuegel-in-einer-hand/ Tue, 31 Aug 2021 09:36:36 +0000 https://basta.net/?p=82840 Viele Azure Services, wie z. B. Azure App Services, Azure Functions und Azure Kubernetes Services ermöglichen es, Konfigurationen beim Deployment von .NET-Konfigurationssettings zu übernehmen und in Produktion dann bei den ressourcenspezifischen Konfigurationen Einstellungen vorzunehmen. Anstatt jeden Azure App Service und jede Azure Function eigens zu konfigurieren, kann aber auch ein zentraler Konfigurations-Service verwendet werden: Azure App Configuration.

The post Alle Zügel in einer Hand: Effektive Steuerung von Softwareentwicklungsprozessen appeared first on BASTA!.

]]>
Dank Azure App Configuration ist es möglich, alle Konfigurationen einer gesamten Lösung zu verwalten. Azure App Configuration bietet eine zentrale Stelle für die Konfigurationen, ermöglicht das Nutzen unterschiedlicher Konfigurationen für verschiedene Environments und bietet auch die Möglichkeit einer Abstraktionsschicht für konfigurierte Secrets in Azure Key Vault. Nebenbei bietet Azure App Configuration auch Featuremanagement

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

.NET Configuration

Im Gegensatz zu .NET-Framework-Applikationen bietet .NET seit der ersten .NET-Core-Version eine flexible Konfiguration, in der unterschiedliche Provider konfiguriert werden können, um auf Konfigurationen aus unterschiedlichen Sources zuzugreifen, z. B. JSON-, XML-, und INI-Files, Environment Variables und Applikationsargumente. Die Sample-Applikation [1] wurde über diesen .NET CLI Command erstellt:

 

> dotnet new webapp

 

Beim Verwenden der Host-Klasse und der Methode CreateDefaultBuilder (Listing 1) werden für das Lesen der Applikationskonfigurationen diese Provider vordefiniert:

 

  • JSON Provider für die Datei json
  • JSON Provider für die Datei {EnvironmentName}.json
  • Environmental Variable
  • Command-Line-Argumente
  • User Secrets (nur im Develop Environment, wenn User Secrets konfiguriert wurden)

 

Listing 1: Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>

  Host.CreateDefaultBuilder(args)

  .ConfigureWebHostDefaults(webBuilder =>

    {

      webBuilder.UseStartup<Startup>();

  });

 

Um Konfigurationswerte von der Applikation zu lesen, wird der Config1 Key mit einem Wert in die Konfigurationsdatei appsettings.json (Listing 2) geschrieben.

 

Listing 2: appsettings.json

{

  "AppConfigurationSample": {

    "Settings": {

      "Config1": "value 1 from appsettings.json"

    }

  }

}

 

Für einen Konfigurationswert, der im Development Environment gelesen wird, wird der Konfigurationswert in der Datei appsettings.Development.json überschrieben. appsettings.{EnvironmentName}.json wird nach der Datei appsettings.json gelesen und damit werden im Development Environment die Konfigurationswerte, die in dieser Datei definiert sind, überschrieben (Listing 3).

 

Listing 3: appsettings.Development.json

{

  "AppConfigurationSample": {

    "Settings": {

      "Config1": "value 1 from appsettings.Development.json"

    }

  }

}

 

Um die Konfiguration im C#-Code zu lesen, wird die IndexAppSettings-Klasse definiert. Der Property-Name entspricht dem Key-Namen, um die Konfigurationswerte automatisch zu übernehmen (Listing 4).

 

Listing 4: IndexAppSettings.cs

public class IndexAppSettings

{

  public string? Config1 { get; set; }
}

 

Um die IndexAppSettings-Klasse mit dem IOptions-Interface injecten zu können, wird die IndexAppSettings-Klasse im Dependency-Injection-(DI-)Container konfiguriert (Listing 5).

 

Listing 5: Startup.cs

public void ConfigureServices(IServiceCollection services)

{

  services.Configure<IndexAppSettings>(

    Configuration.GetSection(

      "AppConfigurationSample:Settings"));

  services.AddRazorPages();

}

 

Mit Inject des generic IOptions Interfaces (Listing 6) werden die Konfigurationen in der Index-Page geladen.

Listing 6: Index.cshtml.cs

public class IndexModel : PageModel

{

  private readonly ILogger<IndexModel> _logger;

 

  public IndexModel(IOptions<IndexAppSettings> options,

    ILogger<IndexModel> logger)

  {

    _logger = logger;

    Setting1 = options.Value.Config1 ?? "no value configured";

  }

  public string Setting1 { get; init; }

}

 

Die Applikation ist bereit, die Konfigurationen aus irgendeiner Konfigurationsquelle zu laden. Mit Ändern eines Environments (Development, Staging, Production) können Konfigurationen auch entsprechend überschrieben werden. Um jetzt Azure App Configuration zu nutzen, sind nur kleine Anpassungen erforderlich.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Erstellen einer Azure App Configuration Resource

Ein Azure App Configuration Service kann über das Azure Portal [2], das Azure CLI, oder über PowerShell-Skripte erstellt werden. Hier wird gezeigt, wie die Azure CLI dafür genutzt werden kann. Im Sample-Source-Code-Repository gibt es ein Bash-Skript zum Erstellen dieser Resource.

Listing 7 zeigt die Azure CLI Commands. Mit dem Command az appconfig create wird die Azure App Configuration Resource erstellt. Der Command az appconfig kv set erstellt Key-Value-Paare.

 

Listing 7

rg=rg-appconfigsample

loc=westeurope

conf=configsample

key1=AppConfigurationSample:Settings:Config1

val1="configuration value for key 1 from Azure App Configuration"

devval1="configuration value for the Development environment"

 

az group create --location $loc --name $rg

az appconfig create --location $loc --name $conf --resource-group $rg

az appconfig kv set -n $conf --key $key1 --value "$val1" --yes

 

az appconfig kv set -n $conf --key $key1 --label Development --value "$devval1" –yes

 

Azure App Configuration mit .NET

Nachdem der Azure App Configuration Service erstellt wurde, braucht es jetzt nur kleine Anpassungen, um die .NET-Applikation [3] damit zu verknüpfen. Für ASP.NET-Core-Applikationen sollte das NuGet Package

Microsoft.Azure.AppConfiguration.AspNetCore hinzugefügt werden. Dieses Package hat eine Dependency auf Microsoft.Extensions.Configuration.AzureAppConfiguration, das für andere .NET-Applikationen reicht.

Alles was es jetzt braucht, um die Applikationskonfiguration aus dem Azure Service zu laden, ist es, den Azure Configuration Provider einzutragen. Um vom Development-System die Connection herzustellen, kann der Connection-String verwendet werden. Da der aber ein Secret beinhaltet, ist es am besten, diesen Connection-String nicht im Source-Code-Repository zu speichern, sondern mit den User Secrets zu speichern. User Secrets können über die .NET CLI konfiguriert werden (Listing 8).

 

Listing 8: dotnet user-secrets
> dotnet user-secrets init
> dotnet user-seccrets set AzureAppConfigurationConnection "enter your connection string"

 

Secrets sollten nie Teil des Source Codes sein. Mit User Secrets werden geheime Konfigurationen aus dem Userprofil gelesen. Das steht nur während der Entwicklung zur Verfügung. In Produktion müssen für Secrets andere Funktionalitäten genutzt werden, wie z. B. der Azure Key Vault.

Beim Set-up der Host-Klasse können weitere Configuration Provider mit der Methode ConfigureAppConfiguration (Listing 9) hinzugefügt werden. Mit dem Aufruf von config.Build werden die bisherigen Provider genutzt, um den Connection-String aus den User Secrets auszulesen. Der Connection-String wird der Extension-Methode AddAzureAppConfiguration mitgegeben und mit diesem Provider werden auch schon die Konfigurationen aus dem Azure Service gelesen.

 

Listing 9: Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((context, config) =>
      {
        var settings = config.Build();
        var connectionString = 
          settings["AzureAppConfigurationConnection"];
        config.AddAzureAppConfiguration(connectionString);
    })
    .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup();
    });

 

Die User Secrets funktionieren jetzt auf dem Development-System. Um diese initiale Konfiguration jetzt auch direkt von einem Azure Service nutzten zu können, sollten hierfür Managed Identities genutzt werden.

 

 

Default Azure Credentials mit Azure App Configuration

Um in Produktion von einem Azure App Service oder einer Azure Function auf Azure App Configuration zugreifen zu können, können Managed Identities [4] genutzt werden. Ein Overload der AddAzureAppConfiguration-Methode (Listing 10) definiert den AzureAppConfigurationOptions-Parameter. Damit ist es möglich, die Connection zu Azure App Configuration mit einem Endpoint (der kein Secret beinhaltet) und einem TokenCredential-Objekt zu definieren. Für das Erzeugen des TokenCredential-Objekts kann die DefaultAzureCredential-Klasse verwendet werden (definiert im NuGet Package Azure.Identity).

DefaultAzureCredential verwendet unterschiedliche Credential Types. Der ManagedIdentityCredential Type kann verwendet werden, wenn die Applikation in einem Azure App Service konfiguriert wird. DefaultAzureCredential verwendet aber auch VisualStudioCredential, VisualStudioCodeCredential und AzureCliCredential. Der Account, der mit Visual Studio verwendet wird, um auf Azure Services zuzugreifen, benötigt hierfür unter Azure App Configuration eine Zuweisung zur Rolle App Configuration Data Reader. Damit kann der gleiche Code im Development Environment und in Produktion genutzt werden.

 

Listing 10
.ConfigureAppConfiguration((context, config) =>
{
  var settings = config.Build();

  config.AddAzureAppConfiguration(options =>
  {
    DefaultAzureCredential credential = new();
    var endpoint = settings["AzureAppConfigurationEndpoint"];
    options.Connect(new Uri(endpoint), credential);
  });
})

 

Environments mit Azure App Configuration

Mit .NET können Konfigurationen je nach Environment überschrieben werden, z. B. im Production, Staging und Developer Environment. Bei Azure App Configuration gibt es Labels, die für das Mapping zu den Environments verwendet werden können, wie in der nächsten Version der Applikation [5] gezeigt wird.

Beim Erstellen des Azure App Configuration Services wurde ein Konfigurationswert mit dem Label Development erstellt. Um die Funktionalität auf die .NET Environments zu übertragen, kann die Select-Methode der AzureAppConfigurationOptions-Klasse verwendet werden. Mit dem ersten Parameter dieser Methode kann ein Key-Filter spezifiziert, um nicht alle Konfigurationswerte zu laden, sondern nur die Werte, die dem Filter entsprechen. Im Sample Code (Listing 11) werden alle Keys geladen, die mit dem String AppConfigurationSample: starten.

Wenn der Azure App Configuration Service Konfigurationen für mehrere Applikationen beinhaltet, ergibt es Sinn, diesen Filter zu definieren, um nicht alle Konfigurationen zu laden. Mit dem zweiten Parameter wird ein Labelfilter spezifiziert. Der erste Aufruf der Filter-Methode verwendet die vordefinierte Null-Property der LabelFilter-Klasse. Damit werden alle Konfigurationswerte ohne Label geladen. Mit dem zweiten Aufruf der Select-Methode wird ein Labelfilter mit dem Namen des Environments genutzt. Die Konfigurationen mit einem entsprechenden Label überschreiben die Konfigurationswerte ohne Label. Bei ASP.NET-Core-Applikationen wird der Name des Environments über die ASPNETCORE_ENVIRONMENT-Environment-Variable gesetzt.

 

Listing 11: Program.cs
config.AddAzureAppConfiguration(options =>
  {
    DefaultAzureCredential credential = new();
    var endpoint = settings["AzureAppConfigurationEndpoint"];
    options.Connect(new Uri(endpoint), credential)
      .Select("AppConfigurationSample:*", LabelFilter.Null)
      .Select("AppConfigurationSample:*", context.HostingEnvironment.EnvironmentName);
});

 

Development, Staging und Production sind übliche Environment-Namen, in denen unterschiedliche Konfigurationswerte, z. B. unterschiedliche Datenbank-Connection-Strings genutzt werden können. Mit einem Cloud-Environment ist es sinnvoll, das Developer-Environment über unterschiedliche Subscriptions komplett von der Produktion zu trennen. Damit ist es sinnvoll, im Development-Environment einen eigenen Azure App Configuration Service zu nutzen. In der Production Subscription ist es wiederum sinnvoll, zumindest zwischen Staging und Production Environments zu trennen – um die letzten Tests vor dem Switch in Produktion im Staging Environment durchzuführen.

 

Dynamische Konfiguration

Um nach Änderungen von Konfigurationswerten einen App Service nicht restarten zu müssen, können Konfigurationen in der laufenden Applikation neu geladen werden. Dafür sind aber einige Änderungen erforderlich [6].

Bei der Konfiguration des Azure App Configuration Providers muss die ConfigureRefresh-Methode der AzureAppConfigurationOptions-Klasse aufgerufen werden. Der Delegate-Parameter bei dieser Methode erlaubt es, die AzureAppConfigurationRefreshOptions zu konfigurieren. Mit der Register-Methode (Listing 12) wird ein Key registriert und mit den Änderungen verglichen, bevor alle Konfigurationswerte neu geladen werden.

Hier ergibt es Sinn, einen Key (Sentinel) zu definieren, bei dem der Wert geändert wird, sobald irgendeine Konfiguration der Applikation geändert wird. Mit diesem Key wird verglichen und Werte im Memory werden neu geladen, sobald dieser Wert nach der Cache Expiration geändert wird. Die Zeitdauer für die Cache Expiration wird über die Methode SetCacheExpiration definiert.

Wird diese z. B. auf fünf Minuten gesetzt, wird der Sentinel-Wert alle 5 Minuten in Azure App Configuration geladen. Ist der Wert anders als im letzten Request, werden alle Konfigurationswerte der Applikation (definiert über die Select-Methode) neu geladen. Im Code-Sample wird die Expiration-Dauer auf 30 Tage gesetzt, womit der Cache explizit neu geladen werden muss. Ein explizites Laden kann über das Interface IConfigurationRefresher gesteuert werden.

 

Listing 12
.ConfigureRefresh(refreshConfig =>
  {
    refreshConfig.Register(
      "AppConfigurationSample:Sentinel", refreshAll: true)
        .SetCacheExpiration(TimeSpan.FromDays(30));
});

 

Weitere notwendige Änderungen für das Aktivieren der Refresh-Funktionalität sind die Konfiguration des DI-Containers über die Methode AddAzureAppConfiguration und die Konfiguration der Middleware mit der Methode UseAzureAppConfiguration (Startup-Klasse).

Statt des IOptions-Interface muss auch das IOptionsSnapshot-Interface injectet werden. Beim IOptions-Interface wird ein Cache der Konfiguration gehalten, und damit gibt es kein Update bei weiteren Aufrufen. Das IOptionsSnapshot-Interface hält einen Snapshot der Daten, die nach jedem Injecten aus dem Azure App Configuration Provider Cache geladen werden.

Um den Cache jetzt upzudaten, kann über Azure App Configuration ein Event Grid Event registriert werden, das aktiviert wird, sobald ein Key-Value-Wert modifiziert wird. Von dort ist es möglich, unterschiedliche Resources wie Azure Functions, Logic Apps, oder WebHooks anzustoßen.

In der Sample-Applikation wird in der RefreshSettings-Page (Listing 13) das IConfigurationRefreshProvider-Interface injectet, um die Azure App Configuration mit den Aufrufen SetDirty und TryRefreshAsync neu zu laden.

 

Listing 13: RefreshSettings.cshtml.cs
public class RefreshSettingsModel : PageModel
{
  private readonly IConfigurationRefresher _configurationRefresher;
  public RefreshSettingsModel(IConfigurationRefresherProvider refresherProvider)
  {
    _configurationRefresher = refresherProvider.Refreshers.First();
  }
  public async Task OnGet()
  {
    _configurationRefresher.SetDirty(TimeSpan.FromSeconds(1));
    await Task.Delay(1000);
    bool success = await _configurationRefresher.TryRefreshAsync();
  }
}

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Konfiguration von Secrets

Für die Konfiguration der Secrets bietet Azure Key Vault Optionen, die von Azure App Configuration nicht zur Verfügung gestellt werden. Azure Key Vault bietet in der Standardvariante Software-Protection über unterschiedliche Rollen, in der Premiumvariante auch Hardware-Protection mit dedizierten Hardware-Security-Modulen.

Azure Key Vault [7] kann über einen eigenen Provider in der .NET-Konfiguration eingetragen werden. Alternativ ist es auch möglich, in Azure App Configuration eine Referenz auf den Azure Key Vault zu registrieren. Dabei ist es in der Applikation [8] nicht erforderlich, eine Connection zu Azure Key Vault zu konfigurieren – die Konfiguration zur Azure App Configuration reicht dafür.

Mit der ConfigureKeyVault-Methode wird konfiguriert, dass der Azure Key Vault auch über Azure App Configuration genutzt wird. Dabei ist es erforderlich, die Credentials mitzugeben, um vom Key Vault lesen zu können. Die Credentials, die über DefaultAzureCredential erzeugt werden, können auch hier mitgegeben werden – Voraussetzung ist, dass diese Credentials nicht nur Zugriff auf die Azure App Configuration sondern auch Lesezugriff auf die Secrets im Azure Key Vault haben.

 

Fazit

Der Azure App Configuration Service bietet eine zentrale Konfiguration. Um diesen Service mit .NET-Anwendungen zu verwenden, ist es nur erforderlich einen Configuration Provider hinzuzufügen. Azure App Configuration kann damit leicht in bestehende .NET-Anwendungen integriert werden. Für unterschiedliche Konfigurationen je nach Environment können mit Azure App Configuration Labels verwendet werden, die sich einfach in das .NET-Environment-Modell integrieren lassen.

Um diesen Service von Azure Services zu verwenden, bieten sich Managed Identities mit Role-based Security an. Um den Service auch vom Development-System verwenden zu können, eignet sich die DefaultAzureCredential-Klasse, die sowohl Managed Identities als auch Visual Studio Credentials unterstützt. Für ein dynamisches Update der Konfigurationswerte ist mit einem Refresh-Modell gesorgt – und Konfigurationen, die Secrets beinhalten, können über den Azure Key Vault von Azure App Configuration integriert werden.

 

Links & Literatur

[1] https://github.com/ProfessionalCSharp/MoreSamples/Azure/AppConfig/01-ConfigSample

[2] https://portal.azure.com

[3] https://github.com/ProfessionalCSharp/MoreSamples/Azure/AppConfig/02-ConfigSample

[4] https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview

[5] https://github.com/ProfessionalCSharp/MoreSamples/Azure/AppConfig/04-ConfigSample

[6] https://github.com/ProfessionalCSharp/MoreSamples/Azure/AppConfig/05-ConfigSample

[7] https://docs.microsoft.com/en-us/azure/key-vault/general/basic-concepts

[8] https://github.com/ProfessionalCSharp/MoreSamples/Azure/AppConfig/06-ConfigSample

The post Alle Zügel in einer Hand: Effektive Steuerung von Softwareentwicklungsprozessen appeared first on BASTA!.

]]>
Microsofts Miet-AI: Wie KI-Lösungen die Unternehmenslandschaft verändern https://basta.net/blog/microsoftsche-miet-ai/ Mon, 09 Aug 2021 12:35:58 +0000 https://basta.net/?p=82744 Das Programmieren der eigentlichen AI-Pipeline ist allzu oft nur ein kleiner Teil der gesamten Solution, während der Gutteil der Arbeit auf die Erzeugung des Modells entfällt. Die Nutzung kommerzieller Modelle schafft an dieser Stelle Abhilfe – ein Trend, dem sich Microsoft mit den Azure Cognitive Services zuwendet.

The post Microsofts Miet-AI: Wie KI-Lösungen die Unternehmenslandschaft verändern appeared first on BASTA!.

]]>
Als Microsoft den Kinect-Sensor im Rahmen eines Softwareupdates mit einem durchaus brauchbaren grammatikbasierten Spracherkennungs-API ausstattete, staunten die Auguren – die Sprachverarbeitung erfolgte damals nämlich ausschließlich auf der Workstation des Entwicklers. Seither nutzt Microsoft die Möglichkeiten der immer verfügbaren Internetverbindung, um Entwickler mehr und mehr in Richtung von (bequem abrechenbaren) Cloud-Services zu treiben. Mit den Azure Cognitive Services steht ein durchaus leistungsfähiges Portfolio von AI-Cloud-Diensten zur Verfügung, die wir uns in den folgenden Schritten anhand einiger kleiner Beispiele kurz ansehen wollen.

 

Was gibt es?

AI-Start-ups stehen immer im Spannungsfeld zwischen Generalisierung und Spezialisierung. Ist der angebotene Dienst zu generell, muss der Entwickler noch zu viel Arbeit beitragen und ist deshalb nicht wirklich zu seiner Nutzung motiviert. Ist der Dienst allerdings zu speziell, ist die erreichbare Zielgruppe zu klein.

Nach der Reorganisation – die Websuchdienste wurden aus den Azure Cognitive Services in die Bing-APIs ausgelagert – bestehen die Cognitive Services noch aus vier Dienstgruppen, die wir in den Abbildungen 1 bis 4 im Profil vorstellen.

Abb. 1: Decision, …

 

Abb. 2: … Language, …

 

Abb. 3: … Speech …

 

Abb. 4: … und Vision sind der Vierkampf der künstlichen Cloud-Intelligenz im Hause Microsoft

 

In der Rubrik „Decision“ finden sich dabei Werkzeuge, die aus bereitzustellenden Informationen Entscheidungen ableiten, darunter auch ein als Content Moderator bezeichnetes Modul, das sich um die automatische Ausfilterung von nicht akzeptablem User-generated Content kümmert. In der Rubrik „Language“ finden sich verschiedene Dienste, die sich auf die Verarbeitung von in Form von Strings vorliegenden Texten konzentriert haben. Ein Klassiker ist dabei die Sentiment Analysis, die die vom Schreiber im Text zu vermittelnde Stimmung analysiert. Mit dem Translator steht analog dazu ein Übersetzer zur Verfügung, der Texte in durchaus brauchbarer Qualität zwischen mittlerweile 90 Sprachpaaren hin- und hertransferiert. In der Rubrik „Speech“ findet sich im Allgemeinen das, was man erwarten würde: TTS-Dienste. Seit der Übernahme des Unternehmens Lernout & Hauspie darf Microsoft hier auf die Kompetenz von Dragon zurückgreifen. Zu guter Letzt gibt es in der Rubrik „Vision“ eine ganze Gruppe von Diensten, die sich um angeliefertes Bildmaterial kümmern – neben der (ebenfalls am Kinect erstmals demonstrierten) Emotions- und Gesichtserkennung gibt es auch Systeme, die aus Videos und/oder Bildern Informationen über die in ihnen enthaltenen Elemente darstellen. Kurzum: Wer die Webseite der Azure Cognitive Services [1] besucht, findet jede Menge nützliche Dienste.

 

Erste Schritte mit Sprache

Jeder der vier Cognitive-Services-Unterdienste ist gleich interessant. Wir wollen in den folgenden Schritten aus Spaß an der Freude mit dem Sprachdienst beginnen. Interessant ist, dass Microsoft die Cognitive Services nicht wirklich zum Boosten der sonstigen hauseigenen Betriebssysteme einsetzt. Abbildung 5 zeigt, dass die Beispiele (und die dazugehörigen SDKs) für verschiedene Plattformen gleichermaßen angeboten werden.

 

Abb. 5: Microsoft ist in Sachen Plattformzielauswahl nicht sehr wählerisch

 

Trotzdem wollen wir in den folgenden Schritten mit Visual Studio 2019 arbeiten. Zunächst erbeuten wir eine halbwegs aktuelle Version der Entwicklungsumgebung und erzeugen darin ein neues Projekt auf Basis der Vorlage Leere App (Universelle Windows-App). Im Bereich der Projektnamensvergabe haben wir wie immer freie Hand, der Autor entscheidet sich in den folgenden Schritten für „SUSTalker1“. Im daraufhin erscheinenden Zielauswahldialog ist es wichtig, dass wir in der Rubrik Mindestens erforderliche Version den Wert Windows 10 Fall Creators Update (10.0; Build 16299) oder besser auswählen. Die Microsoft-Speech-Algorithmen kümmern sich nicht wirklich darum, woher wir unsere Toninformationen beziehen. Im Interesse der Bequemlichkeit wollen wir in den folgenden Schritten mit dem (hoffentlich hochqualitativen) Mikrofon unserer Workstation arbeiten, weshalb wir die Manifestdatei unserer Applikation öffnen und nach folgendem Schema ergänzen:

Capabilities>
  <Capability Name="internetClient" />
  <DeviceCapability Name="microphone"/>
</Capabilities>

eachten Sie, dass die Arbeit mit AI-Spracherkennungssystemen wesentlich stringentere Anforderungen an die Tonqualität stellt als die Arbeit mit Skype und Co. Insbesondere bei Systemen, die für wenig technisch versierte Anwender vorgesehen sind. Lernout & Hauspies Dragon ist ein Klassiker. Es kann sogar empfehlenswert sein, die Verwendung hochqualitativer externer USB-Mikrofone vorzuschreiben. Der Autor arbeitet in den folgenden Schritten mit einem Mikrofon aus dem Hause auna. Der letzte Schritt der Vorbereitungshandlungen auf Applikationsebene ist dann das Aufrufen der NuGet-Paketverwaltung, in der wir uns für das Paket Microsoft.CognitiveServices.Speech entscheiden.

Im nächsten Schritt rufen wir das Azure-Portal auf, in dem wir uns auf die Suche nach dem Microsoft Speech Service begeben. Sein Symbol ist in Abbildung 6 mit einem roten Pfeil markiert. In der Azure-Backend-Verwaltung finden sich mittlerweile Dutzende von AI-Angeboten von Drittherstellern. Wer mit den Azure Cognitive Services arbeiten möchte, ist gut beraten, in der Volltextsuche den mit dem gelben Pfeil hervorgehobenen Filter auf Microsoft zu setzen, um Drittanbieterangebote von vornherein auszuschließen.

 

Abb. 6: Diese Einstellungen führen zum Ziel

Im Bereich der Namensvergabe haben wir auch hier freie Wahl, der Autor entschied sich in den folgenden Schritten für „SUSSpeechService“. Als Preisebene wollen wir die kostenlose Version F0 auswählen, die für erste Experimente ausreicht.

Weitere Informationen zu den Möglichkeiten und Grenzen der verschiedenen als Pricing Tier bezeichneten Preisstufen finden sich unter [2].Die eigentliche Realisierung eines Text-to-Speech-Prozesses unter der Universal Windows Platform wird dadurch erschwert, dass die von Microsoft bereitgestellte Dokumentation teilweise fehlerhaft ist. Es gibt in der UWP-Version des Speech SDK nämlich noch keinen Weg, um die eigentliche Tonausgabe unter Nutzung der Cognitive-Services-Bibliothek zu erledigen. Aus diesem Grund müssen wir stattdessen nach folgendem Schema mit der Realisierung einiger globaler Member-Variablen beginnen:

public sealed partial class MainPage : Page {
  SpeechConfig myConfig;
  MediaPlayer mediaPlayer;

Als Nächstes ergänzen wir die XAML-Formulardatei um einen Knopf mit der Aufschrift Speak English, der im Code-Behind mit einem asynchronen Klick-Event-Handler auszustatten ist:

async private void CmdSpeakEnglish_Click(object sender, RoutedEventArgs e)
{
  AudioConfig myAudioC = AudioConfig.FromDefaultSpeakerOutput();
  var devices = await DeviceInformation.FindAllAsync(DeviceClass.AudioRender);

In der Vollversion des Speech SDKs sorgt die AudioConfig-Klasse für das Routing der vom Server zurückgelieferten Sprachinformationsdateien. Im Interesse der Vollständigkeit wollen wir an dieser Stelle illustrieren, wie wir über alle beim Gerät angemeldeten Audiogeräte iterieren können, um ein bestimmtes über seine ID festzulegen:

foreach (var device in devices){
  if (device.Name.Contains("Lautsprecher")) {
    myAudioC = AudioConfig.FromSpeakerOutput(device.Id);
  }
}

Die nächste idiomatische Amtshandlung des Entwicklers besteht darin, eine SpeechSynthesizer-Instanz anzuwerfen und diese durch Aufruf der Methode SpeakTextAsync zur Kommunikation mit dem Server anzuleiten:

var synthesizer = new SpeechSynthesizer(myConfig, myAudioC);
  using (var result = await synthesizer.SpeakTextAsync("SUS sends its best regards")) {
    if (result.Reason == ResultReason.SynthesizingAudioCompleted) {

An dieser Stelle findet sich der in der Einleitung dieses Sektors besprochene Dokumentationsbug: Auf anderen Plattformen liefert SpeakTextAsync die vom Server zurückgegebenen Informationen direkt an das Standardausgabegerät der unterliegenden Hardware weiter. In der UWP ist das allerdings nicht möglich, weshalb trotz Zurückgabe des Ergebnisses SynthesizingAudioCompleted keine Audioausgabe erfolgt. Zur Umgehung dieses Problems – Microsoft empfiehlt diese Vorgehensweise übrigens auch im offiziellen UWP-Sample – bietet es sich an, auf einen Mediaplayer zu setzen. Haarig ist dabei nur, dass er eine separat zu erzeugende Eingabedatei erwartet (Listing 1).

Listing 1

using (var audioStream = AudioDataStream.FromResult(result)) {
  var filePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "outputaudio_for_playback.wav");
  await audioStream.SaveToWaveFileAsync(filePath);
  mediaPlayer.Source = MediaSource.CreateFromStorageFile(await StorageFile.GetFileFromPathAsync(filePath));
  mediaPlayer.Play();
}

Zur Fertigstellung des Programms müssen wir uns dann noch die Deklaration der beiden Formular-Member-Variablen zu Gemüte führen:

public MainPage() { . . .
  myConfig = SpeechConfig.FromSubscription("KEY", "westeurope");
  mediaPlayer = new MediaPlayer();
}

Die SpeechConfig-Instanz ist dabei von besonderer Bedeutung, da sie diverse für das gesamte Programmleben geltende Konstanten festlegt und auch für die Authentication unserer Applikation beim Azure-Server verantwortlich ist. Anstatt des vom Autor hier genutzten Platzhalters müssen Sie den Inhalt der Felder Key 1 oder Key 2 eingeben; der in den zweiten String einzufügende Wert wird vom Azure-Backend netterweise ebenfalls in der Rubrik Location angezeigt.

Danach sind wir mit diesem Beispiel auch schon fertig. Starten Sie es und klicken Sie bei bestehender Internetverbindung auf den Knopf, um sich an der Ausgabe des englischen Samples zu erfreuen. Angemerkt sei noch, dass Sie in der Variable auch ein Array mit Audiodaten bekommen, das Sie zum Beispiel für die Bluetooth-Übertragung an einen Prozessrechner weiterverwenden können.

 

Exkurs: Mehrsprachigkeit

Wer seinem Programm oder Produkt schnell einen Vorsprung am Markt verschaffen möchte, sollte es nach Möglichkeit lokalisieren. Das ist im Fall von textbasierten Applikationen vergleichsweise einfach, die Lokalisierung von Sprachausgaben stellt den PT-Entwickler allerdings vor immense Zusatzkosten. Microsoft ist sich dieser Situation bewusst, und bietet Dutzende von vorgefertigten Sprachmodellen an [3].

Als kleine Fingerübung wollen wir unser bisher Englisch sprechendes Programm noch auf Schwyzerdütsch umstellen. Hierzu müssen wir die XAML-Datei im ersten Schritt um einen weiteren Knopf ergänzen, den wir – fehlerhaft – mit Speak German betiteln. Im Interesse der Kompaktheit wollen wir den im Allgemeinen vom Englisch-Knopf zu übernehmenden Event Handler nur insofern abdrucken, als er sich von seinem Kollegen unterscheidet (Listing 2).

Listing 2

async private void CmdSpeakGerman_Click(object sender, RoutedEventArgs e)
{
  . . .
  myConfig.SpeechSynthesisVoiceName = "de-CH-LeniNeural";
  var synthesizer = new SpeechSynthesizer(myConfig, myAudioC);
  using (var result = await synthesizer.SpeakTextAsync("SUS sendet beste Grüße"))
  . . .

Neben der offensichtlichen Änderung des Hinzufügens eines neuen Ausgabetexts legen wir in der globalen Konfiguration über das Attribut myConfig.SpeechSynthesisVoiceName fest, dass fortan Schwyzerdütsch zu verwenden ist. Die MyConfig-Instanz wird dabei witzigerweise mit dem englischen Knopf geteilt, weshalb das Ausführen englischer Sprachausgaben nach der Anpassung des Attributs zu einer recht lustigen Intonation führt.

 

Sprache in Text umwandeln

Nachdem wir dem Rechner das Sprechen beigebracht haben, wollen wir die weiter oben in die Manifestdatei eingepflegte Berechtigung nun auch zum Hören einspannen. Da Microsoft Googles Versuchung zur Einführung eines aktiven Permission-Managements nicht widerstehen konnte, müssen wir an dieser Stelle eine Gruppe von Steuerelementen hinzufügen. Neben einem Textblock und einem Start– sowie einem Stop-Knopf müssen wir auch einen Mic-Button einfügen, über den der Benutzer das Anfordern der Mikrofonberechtigungen lostritt.

Für die eigentliche Ausgabe der Statusinformationen brauchen wir dann noch die Funktion AppendToLog. An ihr ist eigentlich nur die Verwendung des Dispatchers interessant, der Aktualisierungen der Logging-Textbox auch außerhalb des WPS- bzw. UWP-GUI-Threads erlaubt:

void AppendToLog(String _what) {
  Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
    TxtRaus.Text = TxtRaus.Text + "/ /" + _what;
  });
}

Der Code für die Anforderung der Mikrofonberechtigung ist mehr oder weniger gewöhnlicher Boilerplate, der sich in diversen Microsoft-Codebeispielen unverändert wiederfindet (Listing 3).

Listing 3

private async void EnableMicrophone_ButtonClicked(object sender, RoutedEventArgs e)
{
  bool isMicAvailable = true;
  try {
    var mediaCapture = new Windows.Media.Capture.MediaCapture();
    var settings = new Windows.Media.Capture.MediaCaptureInitializationSettings();
    settings.StreamingCaptureMode = Windows.Media.Capture.StreamingCaptureMode.Audio;
    await mediaCapture.InitializeAsync(settings);
  }
  catch (Exception) {
    isMicAvailable = false;
  }

Die erste Amtshandlung des Codes besteht darin, eine Medienaufnahmesession bei der UWP anzufordern und dabei das Auftreten von Exceptions zu überprüfen. Dahinter steht die simple Logik, dass das Werfen einer Exception an dieser Stelle darauf hinweist, dass unsere Applikation die benötigte Berechtigung vom Betriebssystem noch nicht zugewiesen bekommen hat. In diesem Fall nutzen wir danach die Funktion LaunchUriAsync, um den Benutzer zur Freigabe der Mikrofonberechtigungen aufzufordern (Listing 4).

Listing 4

if (!isMicAvailable) {
  await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-microphone"));
  }
  else {
    CmdGo.IsEnabled = CmdStop.IsEnabled = true;
  }
}

Je nach Konfiguration der Runtime wird die Workstation darauf entweder mit dem einmaligen oder aber mit dem mehrmaligen Einblenden eines Permission-Dialogs reagieren. Wichtig ist hier nur noch, dass wir die Start– und die Stop-Knöpfe für die Sprachaufnahme nur dann aktivieren, wenn die Methode das Vorhandensein der Mikrofonzugriffsberechtigung angezeigt hat. Sonst orientiert sich SpeechRecognizer – zumindest kontextuell – an seinem für die Sprachausgabe vorgegebenen Kollegen. Auch hier gilt, dass wir eine globale Instanz vom Typ SpeechRecognizer benötigen (Listing 5).

Listing 5

SpeechRecognizer myRecWorker;
async private void CmdGo_Click(object sender, RoutedEventArgs e)
{
  AudioConfig myAudioC = AudioConfig.FromDefaultMicrophoneInput();
  var devices = await DeviceInformation.FindAllAsync(DeviceClass.AudioCapture);
  foreach (var device in devices)
  {
    if (device.Name.Contains("Anua"))
    {
      myAudioC = AudioConfig.FromSpeakerOutput(device.Id);
    }
  }

Da die Workstation des Autors, wie übrigens die meisten Rechner, auch einen analogen Mikrofoneingang hat, „sucht“ er auch hier anhand des Gerätenamens nach seinem Mikrofon. Der dazu verwendete Code unterscheidet sich nur insofern vom weiter oben verwendeten Kollegen, als wir an FindAllAsync nun das Attribut DeviceClass.AudioCapture übergeben. Das permanente Betonen dieser „dedizierten“ Geräteauswahl ist dabei übrigens keine Marotte des Autors, denn wer sie nicht implementiert, riskiert furiose Benutzerwertungen. Das Unbequemsein dieser Einrichtung ist – zumindest nach Meinung des Autors – auch einer der Gründe, warum die Weboberfläche von Microsoft Teams so universell verhasst ist.

Die nächste Gemeinsamkeit mit der Ausgabeversion der Klasse findet sich bei der Konfiguration der zu verwendenden Sprache. Sie erfolgt ebenfalls in dem globalen Einstellungsobjekt, das sich auch um das Einrichten der Azure Authentication kümmert:

myConfig.SpeechRecognitionLanguage = "de-DE";

 

Grammatikfreier Sprechfluss

Im Bereich der computerbasierten Spracherkennungssysteme gibt es seit jeher einen Kampf zwischen grammatikbasierten und frei arbeitenden Versionen. Die in den Azure Cognitive Services implementierte Spracherkennungs-Engine arbeitet dabei analog zu Lernout & Hauspies Dragon ohne Grammatik, weshalb sie potenziell beliebig lange Ein- und Ausgabeströme verarbeiten kann.

Da Entwickler in der Praxis aber auch immer wieder das Bedürfnis haben, kürzere Kommandos zu verarbeiten, implementiert Microsoft zwei verschiedene Versionen des APIs. Wir wollen hier schon aus Gründen der Bequemlichkeit mit dem Vollausbau beginnen, was den in Listing 6 gezeigten Code im Start-Knopf voraussetzt.

Listing 6

myRecWorker = new SpeechRecognizer(myConfig, myAudioC);
  myRecWorker.Canceled += MyRecWorker_Canceled;
  myRecWorker.SpeechEndDetected += MyRecWorker_SpeechEndDetected;
  myRecWorker.Recognized += MyRecWorker_Recognized;
  myRecWorker.SpeechStartDetected += MyRecWorker_SpeechStartDetected;
  myRecWorker.SessionStarted += MyRecWorker_SessionStarted;
  myRecWorker.StartContinuousRecognitionAsync();
  AppendToLog("LOS!");
}

Die Funktion StartContinuousRecognitionAsync weist das SDK dabei darauf hin, dass es ab sofort in den „permanenten“ Spracherkennungsmodus wechseln soll. Dabei bekommt der Entwickler kein finales Resultat zurückgeliefert, sondern muss stattdessen eine Gruppe von Event Handlern einschreiben, die permanent vom Server angelieferte Ereignisse aufnehmen.

Der Autor realisiert hier nur einige der Handler, eine Liste aller exponierten Ereignisse findet sich am einfachsten durch Reflexion. Am wichtigsten ist im Bereich der Event-Handler-Kohorte die Verarbeitung des Canceled-Events, die nach folgendem Schema zu erfolgen hat:

private void MyRecWorker_Canceled(object sender, SpeechRecognitionCanceledEventArgs e){
  AppendToLog("Canceled! " + e.ErrorDetails);
}

Das Azure-Backend liefert unserer Applikation immer dann ein Canceled-Ereignis, wenn es die Datenverarbeitung final einstellt. In den diversen Attributen des SpeechRecognitionCanceledEventArgs-Objekts finden sich dann Informationen darüber, warum unsere Applikation das Missfallen des Servers provoziert hat. Aus der weiteren praktischen Erfahrung des mit einer Dual-Boot-Workstation arbeitenden Autors sei angemerkt, dass die Korrektheit des in Windows eingestellten Systemdatums für die Cognitive Services von größter Bedeutung ist. Wer versehentlich einen falschen Datumswert – schon ein oder zwei Stunden Unterschied reichen aus – einstellt, wird vom Server mit diversen „Connection Refused“-Ereignissen abgestraft.

Relevant ist dann noch das Event Recognized, das die Services immer dann abfeuert, wenn aus der Sprachsequenz ein kompletter String erkannt wurde. Zu seiner Verarbeitung reicht folgender Code aus, der die angelieferten Informationen zudem bequem in den weiter oben angelegten Textblock wirft:

private void MyRecWorker_Recognized(object sender, SpeechRecognitionEventArgs e) {
  AppendToLog("Erbeutetes Ergebnis: " + e.Result.Text);
}

Beim erstmaligen Bring-up verschiedener komplizierter Softwaresysteme ist es erfahrungsgemäß empfehlenswert, diverse andere Status-Events zu exponieren, um mehr Informationen über Interaktion und Programmverhalten zu bekommen. Die restlichen weiter oben abgedruckten Ereignisse sind mit nach dem folgenden Schema aufgebauten Handlern verbunden, die sich um das Ausspeien zusätzlicher Statusinformationen kümmern:

private void MyRecWorker_SpeechEndDetected(object sender, RecognitionEventArgs e) {
  AppendToLog("Redepause!");
}

Zum „kompletten“ Spracherkennungsprogramm fehlt uns dann nur noch eine Implementierung des Stop-Knopfes, der den Server von der weiteren Entgegennahme von Mikrofondaten befreit. Seine Implementierung ist schon aus dem Grund erforderlich, weil sonst nicht unerhebliche Kosten entstehen:

private void CmdStop_Click(object sender, RoutedEventArgs e) {
  myRecWorker.StopContinuousRecognitionAsync();
}

An dieser Stelle können wir unser Programm zur Ausführung freigeben. Wer es, wie der Autor, testweise mit dem String „Hallo Welt“ ausstattet, bekommt das in Abbildung 7 gezeigte Ergebnis präsentiert.

 

Abb. 7: Die Spracherkennung unter Nutzung der Cognitive Services funktioniert problemlos

 

Für alle ASP.NET-Core-basierten Anwendungsarten hat Microsoft begonnen, die ASP.NET-Core-Klassen für die bessere Unterstützung der in C# 8.0 eingeführten Nullable Reference Types zu annotieren.

Die in Blazor 5.0 eingeführte CSS-Isolation (Beschränkung der Gültigkeit auf einzelne .razor-Komponenten) funktioniert in .NET 6 auch mit den älteren Multi-Page-Programmiermodellen Model View Controller (MVC) und Razor Pages. IAsyncDisposable funktioniert nun für Controller, Page Models und View Components.

 

Exkurs: Und jetzt in klein

Wir hatten weiter oben festgestellt, dass Microsoft auch eine „reduzierte“ Version der Spracherkennungs-Engine anbietet. Sie arbeitet nach dem Prinzip, dass sie so lange „zuhört“, bis eine Endbedingung erkannt wird. Legitime Endbedingungen sind dabei einerseits das Aufhören des Redeflusses und andererseits das Ablaufen von fünfzehn Sekunden nach dem Aufnahmestart.

Kommt Ihre Applikation mit einem derartig beschränkten Funktionsumfang aus, so lässt sich die Konfiguration des Listeners nach dem in Listing 7 gezeigten Schema stark vereinfachen.

Listing 7

var result = await recognizer.RecognizeOnceAsync().ConfigureAwait(false);
string str;
if (result.Reason != ResultReason.RecognizedSpeech) {
  str = $"Speech Recognition Failed. '{result.Reason.ToString()}'";
}
else {
  str = $"Recognized: '{result.Text}'";
}

Bei derartigen einfachen Spracherkennungsaufgaben geht es oft darum, bestimmte Kommandos besonders effizient zu erreichen. Microsoft unterstützt dieses Begehr insofern, als wir der an sich grammatikfreien Spracherkennungslogik einige benutzerspezifizierte Worte einschreiben können, die die Engine danach als besonders häufig auftretend betrachtet. Zur Nutzung dieser Sonderfunktion ist ein folgendermaßen aufgebauter Code erforderlich:

var phraseList = PhraseListGrammar.FromRecognizer(recognizer); phraseList.AddPhrase("Supercalifragilisticexpialidocious");

 

Fazit

Auch wenn der Gedanke an die Auslagerung von ML-Modellen in die Cloud auf den ersten Blick angesichts der Mietkosten wenig einladend erscheint, lohnt es sich trotzdem, einen zweiten Gedanken darauf zu verschwenden. Die von Microsoft bereitgestellten ML-Modelle erreichen unglaubliche Güteklassen, die hier durchgeführten Spracherkennungsversuche erreichten in Tests die Genauigkeit der von Lernout & Hauspie für teures Geld verkauften Erkennungs-Engine Dragon – und das, ohne dass der Autor die Services vorher, wie bei Dragon erforderlich, auf die eigene Sprache trainiert hätte.

 

 

Links & Literatur

[1] https://azure.microsoft.com/en-us/services/cognitive-services/#features

[2] https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/speech-services-quotas-and-limits

[3] https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support#text-to-speech

The post Microsofts Miet-AI: Wie KI-Lösungen die Unternehmenslandschaft verändern appeared first on BASTA!.

]]>
One .NET: Der Weg zu einer einheitlichen Plattform für Entwickler:innen https://basta.net/blog/one-net-ist-auf-dem-weg/ Tue, 20 Jul 2021 14:10:05 +0000 https://basta.net/?p=82620 Mit .NET 6 kommt nicht nur die ursprünglich schon für .NET 5 geplante Vereinheitlichung „One .NET“, sondern es wird auch neue Anwendungsarten und weitere Verbesserungen für die Entwickler geben.

The post One .NET: Der Weg zu einer einheitlichen Plattform für Entwickler:innen appeared first on BASTA!.

]]>
Der Nachfolger von .NET 5.0 soll im November dieses Jahres erscheinen und – wie in der Einleitung schon geschrieben – offiziell nur noch „.NET 6“, also ohne „.0“, heißen, wie aus einem Blogeintrag des Program Manager Richard Lander hervorgeht [1]. Microsoft hält sich aber selbst noch nicht daran, wie man auf der .NET-6-Downloadseite [2] in Abbildung 1 unschwer sieht. Zum Zeitpunkt der Erstellung dieses Beitrags sind dort Preview 1 und 2 verfügbar. Bis zur Auslieferung dieses Heftes wird es aber eine oder zwei weitere Previews geben. Wie bisher gibt es drei Runtimes (Konsole, Desktop und ASP.NET Core) sowie ein monolithisches SDK. Von der geplanten Modularisierung des SDK sieht man erste Ansätze, wenn aufgefordert wird, Workloads für Android und iOS nachzuinstallieren [3]. Bis zum fertigen Release soll man die Workloads direkt über den .NET-SDK-Befehl dotnet install beziehen können.

Neu bei den Runtimes für Konsole und ASP.NET Core ist die Unterstützung für den neuen ARM-64-basierten Apple-Chip M1 alias Apple Silicon. Während Microsoft in den Blogeinträgen [4] und [5] immer nur von Apple Silicon spricht, erscheint die neue Runtime in der Liste der Downloads unter macOS/Arm64.

 

Abb. 1: Übersicht über SDK und Runtimes in .NET 6 Preview 2 [2]

 

Wie bisher laufen Windows Forms und WPF nur auf Windows. Daran wird sich auch in .NET 6 nichts ändern. Das gilt auch für die kommende Windows UI Library 3 (WinUI 3), dem designierten Nachfolger von UWP und WPF. Microsoft geht in .NET 6 das Thema Cross-Platform (Abb. 2) aus zwei Richtungen an:

 

  1. Das .NET Multi-Platform App UI (MAUI) ist der Nachfolger von Xamarin.Forms und wird auf Android, iOS, Windows und macOS laufen. Linux ist vorerst ausgeklammert, aber in Diskussion [6]. Das UI wird hier weiterhin durch XAML beschrieben.
  2. Blazor Desktop wird eine hybride Lösung sein: HTML UI verpackt in eine Desktopanwendung (vgl. den Ansatz von GitHubs Electron).

 

Abb. 2: Aufbau .NET 6

 

MAUI

Microsoft hatte auf der Build-Konferenz im Mai 2020 angekündigt, das im Jahr 2016 zugekaufte Produkt Xamarin ganz in die aktuelle .NET-Welt zu integrieren. So soll die App-Entwicklung zukünftig die gleichen Werkzeuge aus dem .NET 6 SDK sowie die gleiche Klassenbibliothek wie der Rest von .NET 6 verwenden (Abb. 2). Im Hintergrund wirkt freilich in einigen Anwendungsarten weiterhin eine auf Mono basierende Runtime, wovon der Entwickler aber gar nichts mehr merken soll. Die Integration wird von Umbenennungen begleitet: Aus Xamarin.iOS wird „.NET for iOS“, aus Xamarin.Android „.NET for Android“ und aus Xamarin.Forms wird „.NET Multi-Platform App UI“ (MAUI). Zusammenfassend spricht Microsoft von .NET 6 Mobile [3]. Entsprechend gibt es nun neue Target Frameworks: net6.0-ios und net6.0-android. Seit .NET 6 Preview 2 können App-Entwickler nun erste einfache .NET-6-Mobile-Projekte für Android, iOS und Mac Catalyst auf Basis des .NET 6 SDK erstellen. Auf GitHub [3] sind Beispiele zu sehen.

Während bisher in Xamarin neben dem Shared Code für jede Zielplattform ein sogenanntes Kopfprojekt notwendig war, kann der App-Entwickler nun aus einem einzigen Projekt heraus unmittelbar für verschiedene Plattformen kompilieren („single, multi-targeted application project“). Ein MAUI-Projekt referenziert das Paket Microsoft.Maui und listet die gewünschten Target Frameworks auf, z. B. net6.0-android;net6.0-ios;net6.0-windows. Im Projekt sind plattformspezifisches XAML, plattformspezifischer Programmcode und plattformspezifische Ressourcen möglich (Abb. 3). Zum Übersetzen verwendet man derzeit noch die Kommandozeile, z. B. dotnet build HelloMaui -t:Run -f net6.0-android.

 

Abb. 3: Hello World in MAUI

 

Blazor 6

Das Entwicklungsteam für Blazor hat in .NET 6 Preview 2 bereits erhebliche Geschwindigkeitsvorteile im Entwicklungsprozess umgesetzt. Im Vergleich zu .NET 5.0 sind sowohl ein erster als auch ein inkrementeller Übersetzungsvorgang deutlich schneller [7], wie Abbildung 4 zeigt. Dort ist ebenfalls zu sehen, dass davon auch ASP.NET MVC profitiert. Ein Teil des Geschwindigkeitsvorteils basiert auf einem Compiler für ASP.NET-Core-Razor-Templates; dieser verwendet die in C# 9.0 eingeführten Source Code Generators.

 

Abb. 4: Geschwindigkeitssteigerungen beim Kompilieren von Razor Templates in Blazor und MVC in .NET 6 Preview 2 [7]

 

Geplant ist, für alle ASP.NET-Core-Anwendungsarten ein Hot Reloading/Hot Restart sowie Edit and Continue (EnC) beim Debugging bereitzustellen. Microsoft spricht von der „Inner Loop Performance“. Die Liste der dazu geplanten Verbesserungen findet man auf GitHub [8]. Auch zur Laufzeit soll es gerade bei Blazor WebAssembly schneller und schlanker zugehen. Ursprünglich stand im Plan, eine „Hello World“-Anwendung auf eine Größe von 0,5 MB zu drücken [9]. Dieses wohl doch zu hohe Ziel wurde aber inzwischen herauseditiert und durch die Aussage ersetzt, man wolle die bestehende Größe von 2.2 MB um mindestens 20 Prozent reduzieren. In Preview 1 gab es für Blazor die neue Razor Component <DynamicComponent>. Mit ihr kann man eine beliebige andere Komponente dynamisch rendern. Die Blazor-Eingabesteuerelemente haben nun einen direkten Zugang zur Element Reference, die der Entwickler zum Zugriff per JavaScript braucht. Seit Preview 2 gibt es mehr Flexibilität für die Parameterübergabe bei der Ereignisbehandlung. Bei generischen Komponenten muss der Entwickler nun in weiteren Fällen keine Typparameter mehr explizit angeben, weil die Blazor-Laufzeitumgebung den Typ automatisch ableiten kann. Beim Einsatz von Server-Prerendering mit dem neuen Tag-Helper <preserve-component-state /> kann der Entwickler in .NET 6 eine Zustandsverwaltung aktivieren, die eine erneute Verarbeitung in der Hauptrenderphase vermeidet.

In .NET 6 sollen erstmals auch hybride Blazor-Anwendungen als fertige Version unter dem Titel „Blazor Desktop“ erscheinen. Das ist seit längerem in Arbeit [10]. Blazor Desktop soll auf dem .NET Multi-Platform App UI (MAUI), dem Nachfolger von Xamarin.Forms, basieren und dort in einem WebView2-Steuerelement die von einer Blazor-Anwendung generierten HTML-Oberflächen darstellen. Da MAUI zunächst bei den Desktopbetriebssystemen nur Windows und macOS unterstützt, findet man Linux nicht in Abbildung 5. Android und iOS werden zwar von MAUI unterstützt, sind aber zunächst nicht von Blazor Desktop abgedeckt. Es soll aber möglich sein, im Rahmen der Blazor Mobile Bindings auch HTML-Inhalte von Blazor darzustellen.

 

Abb. 5: Pläne für Blazor Desktop; Quelle: Microsoft .NET Conf – Focus on Windows (25.02.2021)

 

Für alle ASP.NET-Core-basierten Anwendungsarten hat Microsoft begonnen, die ASP.NET-Core-Klassen für die bessere Unterstützung der in C# 8.0 eingeführten Nullable Reference Types zu annotieren.

Die in Blazor 5.0 eingeführte CSS-Isolation (Beschränkung der Gültigkeit auf einzelne .razor-Komponenten) funktioniert in .NET 6 auch mit den älteren Multi-Page-Programmiermodellen Model View Controller (MVC) und Razor Pages. IAsyncDisposable funktioniert nun für Controller, Page Models und View Components.

 

Entity Framework Core 6

Entity Framework Core 6.0 soll im November 2021 zusammen mit .NET 6 erschienen. Der OR-Mapper bietet seit .NET 6 Preview 1 neue Annotationen wie [Unicode], [Precision] und [EntityTypeConfiguration]. Der Datenbanktreiber für SQLite unterstützt nun ToString() und das in Version 5.0 eingeführte Savepoints API. Für den Microsoft SQL Server kann man nun per Fluent API eine Spalte als „Sparse Column“ deklarieren und damit Speicherplatz in den Datenbankdateien einsparen – für den Fall, dass viele Nullwerte vorkommen, was beim Table-per-Hierarchy-Mapping häufig auftritt, da hier eine ganze Vererbungshierarchie in einer Tabelle lebt. Auch die Methode IsNullOrWhitespace() unterstützt der SQL-Server-Treiber nun. Für SQL Server, SQLite und Cosmos DB funktioniert die Zufallszahlengenerierung mit EF.Functions.Random(). Beim Reverse Engineering übernimmt der Befehl Scaffold-DbContext jetzt Kommentare zu Tabellen und Spalten aus dem Datenmodell in den generierten Programmcode.

Preview 2 bietet hingegen nur kleine Verbesserungen: String.Concat() funktioniert auch mit mehr als zwei Parametern. SqlServerDbFunctionsExtensions.FreeText() und SqlServerDbFunctionsExtensions.Contains() funktionieren jetzt auch mit binären Spalten und in Zusammenarbeit mit Value-Konvertern. Außerdem hat Microsoft ein schon länger bestehendes lästiges Problem [11] mit doppelten Typen in zwei verschiedenen Assemblies in Zusammenhang mit asynchronen LINQ-Operationen endlich gelöst [12].

Weitere Pläne für Entity Framework Core 6.0 hat Microsoft am 18.1.2021 veröffentlicht [13]. Dazu gehören:

 

  • N:M-Unterstützung beim Reverse Engineering
  • Mapping für Spalten mit Typ JSON
  • Unterstützung für temporale Tabellen im Microsoft SQL Server
  • Festlegung der Reihenfolge beim Anlegen von Spalten (Order)
  • Migration Bundles für Schemamigrationen (war schon für Version 5.0 geplant: Alternativ zu einem SQL-Skript soll eine ausführbare Datei generiert werden, die das Datenbankschema aktualisiert)
  • Leistungsverbesserungen, insbesondere schnellerer Start durch kompilierte Modelle
  • Unterstützung für ADO.NET Batching API [14]

 

Bei der Performance hat sich Microsoft vorgenommen, im TechEmpower Benchmark [15] den konkurrierenden Micro-ORM Dapper von StackExchange [16] zu schlagen.

 

Weitere Verbesserungen in Preview 1 und 2

Das in .NET Core 3.0 eingeführte und bisher in C++ geschriebene CrossGen-Werkzeug wurde in C# neu implementiert (CrossGen2). Diese Version erlaubt eine profilgesteuerte Optimierung (PGO). Seit Preview 2 kompiliert Microsoft mit CrossGen2 alle Microsoft.NETCore.App Assemblies im .NET SDK zu Maschinencode vor. Weitere Anwendungsarten wie Windows Desktop sind für Preview 3 und 4 geplant. Bei den Basisklassen gibt es mit der Klasse System.Collections.Generic.PriorityQueue<TElement, TPriority> nun eine neue Warteschlange mit Priorität; bei der Entnahme werden die Werte nicht anhand der Einfügereihenfolge, sondern auf Basis der bei der Hineingabe angegebenen Priorität entnommen. Auch bei den mathematischen APIs im Namensraum System.Numerics gibt es Erweiterungen. Selbst bei den Basisfunktionen ToString() und TryFormat() hat Microsoft nach 20 Jahren .NET noch Verbesserungspotenzial erkannt und realisiert, vgl. Beispiele im Blogeintrag [5].

Der von Microsoft bereits in .NET Core 3.0 neu implementierte JSON Serializer System.Text.Json kann nun endlich auch – wie sein Vorgänger JSON.NET – Schleifen in Objektbäumen ignorieren. Die Komponente System.Threading.AccessControl bietet Verbesserungen für Access Control Lists auf Windows.

Ausblick

Es steht noch Einiges auf der Agenda für .NET 6 (vgl. www.themesof.net), was man nicht in Preview 1 und 2 sieht, u. a.:

 

  • C# 10
  • Ahead-of-Time-Kompilierung als Alternative zum Just-In-Time-Compiler
  • Single-File-Apps für alle Anwendungsarten inkl. Debugging
  • Verbessertes Application Trimming (Tree Shaking)
  • HTTP/3 auf Basis von Quick UDP Internet Connections (QUIC)
  • Client-Retry für gRPC
  • Anpassbarer Reverse Proxy
  • Leichterer Einstieg für neue Entwickler

 

Fazit

Ich persönlich freue mich in .NET 6 am meisten auf die Beschleunigung des Entwicklungsprozesses durch die Optimierung beim Kompilieren und Debugging, denn ich hasse es, bei der täglichen Programmierarbeit an Kundenprojekten immer einige Sekunden warten zu müssen, bis ich das Ergebnis meiner jüngsten Änderungen live sehe.

 

Links & Literatur

[1] https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-1/

[2] https://dotnet.microsoft.com/download/dotnet/6.0

[3] https://github.com/dotnet/net6-mobile-samples

[4] https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-1/

[5] https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-2/

[6] https://github.com/jsuarezruiz/forms-gtk-progress/issues/31

[7] https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-2

[8] https://github.com/dotnet/core/issues/5510

[9] https://github.com/dotnet/runtime/issues/44317

[10] https://github.com/aspnet/AspLabs/tree/main/src/ComponentsElectron

[11] https://github.com/dotnet/efcore/issues/18124

[12] https://github.com/dotnet/efcore/issues/24041

[13] https://devblogs.microsoft.com/dotnet/the-plan-for-entity-framework-core-6-0/

[14] https://github.com/dotnet/runtime/issues/28633

[15] https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=db

[16] https://github.com/StackExchange/Dapper

The post One .NET: Der Weg zu einer einheitlichen Plattform für Entwickler:innen appeared first on BASTA!.

]]>
Von UWP zu WinUI: Eine spannende Reise in die Zukunft der Windows-Anwendungsentwicklung https://basta.net/blog/eine-reise-von-uwp-zu-winui/ Wed, 09 Jun 2021 09:32:20 +0000 https://basta.net/?p=82433 Zum Entwickeln von Desktopanwendungen für Windows 10 ist WinUI das modernste UI Framework. Seit Ende März 2021 liegt es als Teil des sogenannten Project Reunion in einer Version vor, die laut Microsoft bereits für produktive Anwendungen verwendet werden kann. Dieser Artikel zeigt einen Überblick über UWP, WinUI und Project Reunion, und zeigt auch, wie ein WinUI-Projekt mit der neuesten Version erstellt wird.

The post Von UWP zu WinUI: Eine spannende Reise in die Zukunft der Windows-Anwendungsentwicklung appeared first on BASTA!.

]]>
Zum Entwickeln von Desktopanwendungen für Windows 10 haben .NET-Entwickler zahlreiche Möglichkeiten: Windows Forms, WPF, UWP und WinUI. Die Windows UI Library, kurz WinUI, stellt die modernste Variante dar. WinUI ist auch die native UI-Plattform von Windows 10, was bedeutet, dass diverse Teile von Windows 10, wie beispielsweise das Startmenü oder die App für die Einstellungen, mit WinUI entwickelt wurden und werden. Auch die bei Entwicklern beliebte Windows Terminal App [1] ist eine mit WinUI entwickelte Windows-Desktopanwendung. Sie lässt sich über den Microsoft Store installieren.

WinUI ist die Weiterentwicklung des UI Frameworks der Universal Windows Platform (UWP). Mit WinUI wurde das UWP UI Framework von Windows 10 entkoppelt. Das hat verschiedene Vorteile: WinUI kann einen anderen Releasezyklus als Windows 10 haben und zudem lassen sich neueste Controls auch auf älteren Windows-10-Versionen einsetzen. Doch UWP existiert weiterhin, und das ist für viele Entwickler etwas verwirrend. Der Grund dafür ist, dass es sich bei der UWP um weitaus mehr als um ein UI Framework handelt. Um zu verstehen, was genau WinUI ist, muss man auch verstehen, was eigentlich die Universal Windows Platform ist. Gehen wir dieser Frage nach.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Was ist die Universal Windows Platform (UWP)?

Mit Windows 10 hat Microsoft die Universal Windows Platform eingeführt. UWP ist auf allen Geräten verfügbar, auf denen Windows 10 läuft:

 

  • PC
  • Xbox
  • HoloLens
  • Surface Hub
  • Internet-of-Things-Geräte (IoT, beispielsweise Raspberry Pi)

 

Für Entwickler hat Visual Studio eine Projektvorlage mit dem Namen Blank App (Universal Windows), mit der sich eine UWP-Anwendung erstellen lässt, die auf all diesen Geräten läuft. Die Anwendung lässt sich dabei entweder mit .NET/C# oder mit C++ erstellen. Wichtig zu verstehen ist, dass UWP ein nativer Teil von Windows 10 und selbst in C++ geschrieben ist. Das macht das Programmieren einer UWP-App mit reinem C++ möglich, erlaubt es aber auch, eine Anwendung mit .NET/C# zu schreiben, die ganz gewöhnliche .NET-Standard-2.0-Klassenbibliotheken referenzieren kann.

Zum Erstellen von Benutzeroberflächen wird mit UWP die Extensible Application Markup Language (XAML) eingesetzt. XAML ist eine XML-basierte Sprache, die ursprünglich mit der Windows Presentation Foundation (WPF) im Jahr 2006 eingeführt wurde. WPF und UWP haben viele gemeinsame Konzepte wie XAML, Data Binding, Styles, Layout etc. Das bedeutet, dass ein WPF-Entwickler in UWP recht schnell zurechtkommt, und auch umgekehrt findet sich eine UWP-Entwicklerin in WPF schnell zurecht. Allerdings standen all die Innovationen, die mit WPF stattgefunden haben, C++-Entwicklern nicht zur Verfügung, denn zum Erstellen von WPF-Anwendungen muss man .NET/C# verwenden. Mit UWP hat sich das geändert, und C++-Entwickler konnten auch wieder bei der neuesten Technologie mitmischen.

Neben der Möglichkeit, mit .NET/C# oder C++ zu entwickeln, ist ein wichtiges Merkmal von UWP-Anwendungen, dass sie in einer Sandbox laufen. Das bedeutet, dass der Systemzugriff begrenzt ist. Das ergibt Sinn, wenn man bedenkt, dass UWP-Anwendungen auch über den Microsoft Store verteilt werden können. Eine UWP-Anwendung muss ihre Fähigkeiten (Capabilities) in einer Manifest-Datei deklarieren, damit diese sich im Anwendungscode auch nutzen lassen. Das ist ein bekanntes Konzept von mobilen Anwendungen, bei denen man als Benutzer Nachrichten sieht, wie beispielsweise „Diese Anwendung möchte Zugriff auf Ihre Bilder, Ihre Dokumente und Ihre Kontakte“. Bei einer UWP-Anwendung ist das genau gleich. Damit beispielsweise der Zugriff auf das Internet funktioniert, muss das als Capability im Manifest der Anwendung angegeben werden.

Zudem ist ein weiteres wichtiges Merkmal von UWP-Anwendungen, dass sie in einem .zip-basierten Format mit der Dateiendung .appx verpackt werden. Das erlaubt eine saubere Installation und Deinstallation auf Windows-10-Geräten. Nach einer Deinstallation ist die Anwendung sauber entfernt und hinterlässt keine Spuren, wie man das von anderen Installationsvarianten kennt.

Ein weiterer, sehr wichtiger Punkt von UWP ist das sogenannte Activation- und Lifecycle-Management. Dabei geht es darum, die Batterie eines Gerätes zu schonen. Klassische Win32-Anwendungen wie WPF- oder Windows-Forms-Anwendungen laufen immer, wenn der Benutzer sie gestartet hat. D. h., selbst wenn der Benutzer eine WPF-Anwendung minimiert oder zu einer anderen Anwendung wechselt, wird die WPF-Anwendung weiterlaufen und den Prozessor beanspruchen. Das ist natürlich nicht gut für batteriebetriebene Geräte wie Tablets. UWP-Anwendungen lösen dieses Problem mit dem erwähnten Activation- und Lifecycle-Management. Minimiert ein Benutzer eine UWP-App oder navigiert er zu einer anderen Anwendung, wird die UWP-App im Hintergrund automatisch ausgesetzt (suspended). Das ermöglicht es dem System, Ressourcen und somit auch Batterie zu sparen. Navigiert der Benutzer zurück zur UWP-Anwendung, wird sie wieder aktiviert und fortgesetzt.

Wie man also sieht, ist UWP mehr als nur ein UI Framework. Es hat verschiedenste Features wie XAML, Paketierung mit .appx, eine Sandbox, ein Lifecycle-Management etc. Genau aus diesem Grund muss man sich als Entwickler bewusst machen, dass UWP ein sehr weiter Begriff ist. Schauen wir uns ihn daher genauer an.

Was steckt hinter UWP?

Nachfolgend eine Auflistung, was alles unter UWP zu verstehen ist:

 

  1. UWP-App – das ist eine Anwendung, die mit C# oder C++ entwickelt wurde. Die Anwendung verwendet alle anderen Punkte, die in dieser Aufzählung gelistet sind. Visual Studio 2019 enthält zum Erstellen einer UWP-App die Projektvorlage Blank App (Universal Windows). Diese Vorlage ist verfügbar, wenn bei der Visual-Studio-Installation der Workload Universal Windows Platform Development ausgewählt wurde.
  2. UWP XAML Framework – das ist der Teil von Windows 10, mit dem die eigene UWP-App gebaut wird. Das UWP XAML Framework enthält XAML und auch die visuelle Schicht, die notwendig ist, um die eigene Anwendung auf den Bildschirm zu zeichnen. Auch gehören Benutzereingaben zum UWP XAML Framework, etwa Ereignisse für Tastatur, Maus und Stift.
  3. UWP XAML Controls – das sind die Controls, die in der eigenen UWP-App eingesetzt werden, wie TextBox, CheckBox, Button etc. Diese Controls sind auch Teil von Windows 10.
  4. WinRT – steht für Windows Runtime. Das ist das moderne Windows API (= Programmierschnittstelle), das bereits mit Windows 8 eingeführt und mit Windows 10 weiterentwickelt wurde. Es verwendet viele Konzepte aus .NET wie Klassen, Methoden und Properties. Auch verwendet WinRT .NET-Metadaten, um die eigenen Inhalte zu beschreiben. D. h., durch diese Metadaten ist bekannt, welche Klassen und Methoden es in der WinRT gibt. Die WinRT ist Teil von Windows 10 und existiert auf jedem Windows-10-Gerät. Auf PCs ist sie neben dem älteren Windows API namens Win32 verfügbar.
  5. UWP Packaging – UWP-Anwendungen werden in einer .appx-Datei paketiert (.zip-basiertes Format), was eine saubere Installation und Deinstallation ermöglicht. Das bedeutet, dass nach einer Deinstallation kein Datenmüll hinterlassen wird.
  6. UWP-App-Modell – UWP-Anwendungen laufen in einer Sandbox. Sie müssen ihre Fähigkeiten (Capabilities) deklarieren, um Zugriff auf verschiedene Systemressourcen zu erhalten, wie beispielsweise Zugriff auf die Internetverbindung. Um diese Sandbox zu ermöglichen, muss Windows 10 eine UWP-Anwendung in einer besonderen Form hosten/ausführen. Dieses Hostingmodell wird oft als UWP-App-Modell bezeichnet. Das andere bekannte und traditionelle Hostingmodell ist das Win32-Hostingmodell, was auch als Desktophostingmodell bezeichnet wird. WPF- und Windows-Forms-Anwendungen laufen unter dem Win32-Hostingmodell. Neben der Sandbox ist ein Teil des UWP-App-Modells das Activation- und Lifecycle-Management, was für batteriebetriebene Geräte sehr wichtig ist, da damit Energie gespart wird, wenn die UWP-App nicht im Vordergrund ist.

 

 

 

Hinweis

In diesem Artikel sehen wir uns UWP und WinUI aus der Perspektive eines XAML- und C#-Entwicklers an. Das UWP XAML Framework ist vermutlich der meistgenutzte Weg für .NET-Entwickler, um eine UWP-Anwendung zu erstellen, aber es ist nicht der einzige Weg. Das UWP XAML Framework sitzt in Windows 10 auf dem sogenannten Visual Layer, der selbst wiederum auf DirectX aufsetzt, um die Benutzeroberfläche zu zeichnen. Anstatt somit eine UWP-App mit XAML und C# zu erstellen, könnte man auch DirectX verwenden. Mit React Native for Windows, das auf UWP/WinUI aufbaut, lassen sich sogar HTML und JavaScript zum Erstellen einer UWP-App nutzen.

 

All diese verschiedenen Bereiche von UWP machen deutlich, dass Entwickler nicht immer dasselbe meinen, wenn sie von UWP sprechen. Sie können sich auf einen der Punkte 1 bis 6 beziehen. Wichtig zu wissen ist, dass all diese Punkte auf jedem Windows-10-Gerät verfügbar sind, egal ob PC, HoloLens, Surface Hub, Xbox oder IoT-Gerät. Das liegt daran, dass UWP ein Teil des Windows-10-Kerns ist. Und genau das führt beim Entwickeln von UWP-Apps zu einem Abhängigkeitsproblem.

Das Abhängigkeitsproblem der UWP XAML Controls in Windows 10

Die UWP XAML Controls sind Teil von Windows 10, was eine starke Abhängigkeit einer UWP-App an das Betriebssystem darstellt. Es bedeutet, dass man als Entwickler in der eigenen UWP-Anwendung nur dann die neuesten UWP XAML Controls und XAML-Features nutzen kann, wenn die Benutzer auch die neueste Version von Windows 10 installiert haben. Das bremst natürlich die Innovation sehr stark. Und jeder, der schon Enterprise-Software entwickelt hat, weiß, dass in Unternehmen eine Aktualisierung von Windows 10 durchaus eine Weile dauern kann. Microsoft hat diese Abhängigkeit entfernt, indem die UWP XAML Controls losgelöst von Windows 10 als NuGet-Paket angeboten werden. Es heißt WinUI, hat die Version 2.x, und lässt sich in einer UWP-App referenzieren. Dadurch können die neuesten XAML Controls eingesetzt werden, ohne dass die Benutzer ihre Windows-10-Version aktualisieren müssen.

Die UWP XAML Controls sind natürlich auch nach wie vor Teil von Windows 10, damit existierende UWP-Apps auch weiterhin ohne das NuGet-Paket funktionieren. Die UWP XAML Controls wurden jedoch von Windows 10 in das WinUI-2.x-NuGet-Paket kopiert; neue Features und neue Entwicklungen finden dort statt.

Das Abhängigkeitsproblem des UWP XAML Framework

Zwar hat Microsoft die Abhängigkeit zwischen einer UWP-App und den in Windows 10 integrierten UWP XAML Controls aufgelöst, doch das UWP XAML Framework ist weiterhin Teil von Windows 10 – und hier gibt es genau die gleiche Art von Abhängigkeit zwischen der eigenen UWP-App und Windows 10. Dass das UWP XAML Framework Teil von Windows 10 ist, bedeutet, dass man nur dann die neuesten Framework-Features nutzen kann, wenn man auch die neueste Windows-10-Version installiert hat. Microsoft hat auch diese Abhängigkeit von Windows 10 entkoppelt und stellt das UWP XAML Framework als NuGet Paket bereit. Es trägt den Namen WinUI 3.0 und ist ein volles UI Framework wie WPF oder Windows Forms. Das WinUI-3.0-NuGet-Paket enthält das UWP XAML Framework und – wie WinUI 2.x – die UWP XAML Controls. WinUI 3.0 wird Open Source auf GitHub [2] entwickelt.

 

Hinweis

Ein Blick in das GitHub Repository von WinUI 3.0 [2] lohnt sich. Die Issues enthalten Ankündigungen für WinUI Community Calls, was Livevideos auf YouTube sind, die einmal im Monat stattfinden – üblicherweise an jedem dritten Mittwoch im Monat. Jeder kann daran teilnehmen, und das WinUI-Team stellt Neuerungen vor und beantwortet auch Fragen.

 

WinUI 3.0 einsetzen

Das WinUI-3-NuGet-Paket ist nicht dazu gemacht, es in einer klassischen UWP-App zu referenzieren, die mit der Blank App (Universal Windows)-Projektvorlage in Visual Studio erstellt wurde. Eine solche UWP-App nutzt nach wie vor das UWP XAML Framework, das in Windows 10 existiert, und nicht die entkoppelte Version, die sich im WinUI-3-NuGet-Paket befindet. Um WinUI 3 zu nutzen, muss die sogenannte „Project Reunion“-Visual-Studio-Erweiterung installiert werden, die die Projektvorlagen für WinUI-3-Anwendungen enthält. Nachdem sie installiert wurden, lassen sich zwei Arten von WinUI-3-Anwendungen erstellen:

 

  • Blank App (WinUI 3 in UWP)
  • Blank App, Packaged (WinUI 3 in Desktop)

 

Beide Optionen erstellen eine WinUI-3-Anwendung, die das WinUI-3-NuGet-Paket referenziert. Das bedeutet, dass sowohl das UWP XAML Framework als auch die UWP XAML Controls aus dem NuGet-Paket stammen. Es bedeutet auch, dass man für beide Varianten weitestgehend den identischen XAML- und C#-Code schreibt, da sie dasselbe UWP XAML Framework und auch dieselben UWP XAML Controls aus dem NuGet-Paket verwenden. Der Unterschied zwischen den beiden Projektvorlagen ist das darunterliegende App-Modell. Denn mit WinUI 3 hat man plötzlich die Wahl zwischen UWP und Win32 (auch als Desktop bezeichnet). Und da muss man sich entscheiden:

 

  • WinUI 3 in UWP – erstellt eine UWP-App mit WinUI 3, die quasi im UWP-App-Modell gehostet wird. D. h., sie lässt sich auf verschiedensten Windows-10-Geräten installieren, es gibt die Sandbox, und die App schont aufgrund des Activation- und Lifecycle-Managements die Batterie des Geräts, wenn sie nicht im Vordergrund ist.
  • WinUI in Desktop – erstellt eine Win32-App mit WinUI 3. Sie läuft auf dem PC und hat keine Sandbox. Somit kann sie alles machen, was auch eine WPF- oder eine Windows-Forms-Anwendung machen kann, wie beispielsweise das komplette Dateisystem lesen.

 

Das bedeutet, dass WinUI 3 den Entwicklern von Desktopanwendungen für Windows 10 folgende Auswahlmöglichkeiten gibt:

 

  • Programmiersprache: .NET/C# oder C++
  • App-Modell: UWP oder Win32

 

Das heißt auch im Umkehrschluss, dass das UWP-App-Modell weiterhin ein Teil von Windows 10 bleibt und auch als Teil von Windows 10 weiterentwickelt wird. WinUI 3 enthält lediglich das UWP XAML Framework und die UWP XAML Controls, nicht jedoch das UWP-App-Modell zum Hosten einer UWP-App.

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

Hinweis

WinUI 3 ist die moderne Variante des UWP UI Frameworks. Das Erstellen einer UWP-App mit der Projektvorlage Blank App (Universal Windows) ist der alte Weg für eine UWP-App. Neue UWP-Apps sollten mit WinUI 3 und der Projektvorlage Blank App (WinUI 3 in UWP) erstellt werden.

 

Was ist Project Reunion?

WinUI 3 ist Teil des sogenannten Project Reunion [3]. Das Projekt von Microsoft hat zum Ziel, sämtliche Windows-APIs über NuGet-Pakete bereitzustellen. Neben WinUI ist auch die sogenannte C#/WinRT Library Teil des Project Reunion. C#/WinRT stellt eine C#-Projektion der nativen, in C++ geschriebenen WinRT dar, die das moderne Windows API von Windows 10 ist und oft auch als UWP API bezeichnet wird. C#-Projektion bedeutet, dass sich die in C++ geschriebene WinRT in C# so verwenden lässt, als sei sie in C# geschrieben worden. Auch die C#/WinRT [4] ist Open Source auf GitHub zu finden. Project Reunion versucht also, Windows-Entwicklern alle Ressourcen als NuGet-Pakete zur Verfügung stehen – unabhängig davon, ob sie mit .NET/C# oder mit C++ programmieren, und unabhängig davon, ob sie UWP oder Win32 als Hostingmodell verwenden. Und WinUI 3 selbst ist Teil dieses Project Reunion.

Die Umgebung für WinUI 3 aufsetzen

Um mit WinUI 3 loszulegen, muss die neueste Visual-Studio-Version installiert werden. Dafür sind im Visual Studio Installer die Workloads .NET Desktop Development und Universal Windows Platform Development auszuwählen. Anschließend sind die Projektvorlagen für WinUI 3.0 zu installieren. Diese Projektvorlagen sind Teil der Project Reunion Visual Studio Extension [5]. Sie lässt sich in Visual Studio über das Hauptmenü Extensions installieren. Abbildung 1 zeigt die installierte Extension in Visual Studio 2019. Diese Schritte zum Aufsetzen der Umgebung lassen sich in der offiziellen Dokumentation nachlesen [6].

 

Abb. 1: Die Project Reunion Extension enthält die WinUI-3-Projektvorlagen

Die WinUI-3-Projektvorlage für Desktop-Apps

Mit der installierten Project Reunion Extension sind die WinUI-3-Projektvorlagen in Visual Studio verfügbar. Abbildung 2 zeigt die Projektvorlage zum Erstellen einer WinUI-3-Desktopanwendung.

 

Abb. 2: Die Projektvorlage zum Erstellen einer WinUI-3-Desktop-App

 

WinUI 3 kann für Desktopanwendungen bereits produktiv eingesetzt werden. Doch wie Abbildung 2 zeigt, gibt es noch keine Projektvorlage für eine „WinUI 3 in UWP“-App. Diese App-Variante ist erst im Preview-Status und kommt später dazu. In diesem Abschnitt wird also folglich eine „WinUI in Desktop“-App erstellt. Die Vorlage heißt Blank App, Packaged (WinUI 3 in Desktop). „Packaged“ bedeutet, dass die Anwendung wie eine UWP-Anwendung paketiert wird, womit sie sich unter Windows 10 sauber installieren und deinstallieren lässt. UWP-Apps haben die Dateiendung .appx und Windows 10 weiß, wie Anwendungen mit dieser Dateiendung installiert und deinstalliert werden. Das dahinterliegende Format wird als MSIX bezeichnet. Das Spannende ist, dass sich nicht nur UWP-Apps, sondern auch klassische Win32-Apps in einem MSIX-Paket verpacken lassen. Dazu hat Visual Studio die eigene Projektvorlage Windows Application Packaging Project. Damit lassen sich WPF- oder auch Windows-Forms-Anwendungen als MSIX-Paket verpacken. Dieses Paket hat dann die Dateiendung .msix, es handelt sich dabei jedoch um das exakt gleiche Format wie das einer UWP-.appx-Datei. Eine WinUI-3-Desktop-App ist somit eine Win32-Anwendung, die automatisch über ein MSIX-Paket bereitgestellt wird. Genau dafür steht das „Packaged“ in der Projektvorlage.

 

 

 

Ein WinUI-3-Projekt erstellen

Im Folgenden wird eine „WinUI 3 in Desktop“-Anwendung mit dem Namen WindowsDeveloperApp erstellt. Abbildung 3 zeigt die erstellte Solution, die zwei Projekte enthält. Das erste Projekt ist das eigentliche Anwendungsprojekt, das in den Dateien MainWindow.xaml und MainWindow.xaml.cs den Code des Hauptfensters enthält. Das zweite Projekt ist das Package-Projekt, das das MSIX-Paket erstellt. Unter Applications ist zu sehen, dass es das Anwendungsprojekt referenziert.

 

Abb. 3: Die erstellte Solution enthält zwei Projekte

 

Das Package-Projekt ist jenes, das man in Visual Studio als Startprojekt festlegt. Wird es gestartet, wird die WinUI-3-Anwendung kompiliert, in ein MSIX-Paket verpackt, auf dem Windows-10-Rechner installiert und ausgeführt.

Die einzelnen Dateien

Ein WinUI-3-Projekt ist wie eine UWP-App und wie eine WPF-Anwendung aufgebaut. Es gibt die Dateien App.xaml und App.xaml.cs, die zusammen die Application-Klasse definieren. Daneben gibt es die Dateien MainWindow.xaml und MainWindow.xaml.cs, die zusammen die MainWindow-Klasse definieren, die das Hauptfenster der Anwendung repräsentiert.

Listing 1 zeigt den Inhalt der MainWindow.xaml-Datei. Sie enthält standardmäßig ein StackPanel mit einem Button, der den Text „Click Me“ enthält. Dem Button wurde zudem der Name myButton gegeben, um aus der Code-Behind-Datei (MainWindow.xaml.cs) auf ihn zugreifen zu können. Darüber hinaus wurde ein Event Handler für das Click-Event angegeben.

 

Listing 1: Die MainWindow.xaml-Datei

<Window
  x:Class="WindowsDeveloperApp.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:WindowsDeveloperApp"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">
 
  <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
  </StackPanel>
</Window>

 

Listing 2 zeigt die Code-Behind-Datei MainWindow.xaml.cs. Darin ist eine MainWindow-Klasse definiert, die von Window ableitet. Window selbst stammt aus dem Namespace Microsoft.UI.Xaml, der zu WinUI 3 gehört. Die MainWindow-Klasse enthält neben einem Konstruktor den Event Handler für das Click-Event des Buttons. Darin wird die Content Property des Buttons auf den Wert Clicked gesetzt. Wird somit die Anwendung ausgeführt, wird ein Button mit dem Text „Click Me“ angezeigt, und wird dieser Button geklickt, ändert sich der Text auf „Clicked“. Abbildung 4 zeigt die laufende Anwendung in Aktion. Dabei wurde der Button bereits angeklickt, wodurch er den Text „Clicked“ enthält.

 

Listing 2: Die MainWindow.xaml.cs Datei

using Microsoft.UI.Xaml;
namespace WindowsDeveloperApp
{
  public sealed partial class MainWindow : Window
  {
    public MainWindow()
    {
      this.InitializeComponent();
    }
 
    private void myButton_Click(object sender, RoutedEventArgs e)
    {
      myButton.Content = "Clicked";
    }
  }
}

 

Abb. 4: Die erstellte WinUI-App

 

Listing 3 zeigt den Inhalt der App.xaml.cs-Datei. Darin ist zu sehen, wie die OnLaunched-Methode der Application-Klasse überschrieben wird. Diese Methode wird aufgerufen, wenn die Anwendung gestartet wird. In der OnLaunched-Methode wird eine Instanz des MainWindow erstellt, mit der Activate-Methode aktiviert und somit angezeigt.

 

Listing 3: Die App.xaml.cs Datei

public partial class App : Application
{
  public App()
  {
    this.InitializeComponent();
  }
 
  protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
  {
    m_window = new MainWindow();
    m_window.Activate();
  }
 
  private Window m_window;
}

 

In der WinUI-Anwendung lässt es sich jetzt wie in einer UWP-Anwendung programmieren. Allerdings ist der große Unterschied aus Sicht eines Entwicklers, dass es sich hier um eine Win32-Anwendung handelt, da die Projektvorlage WinUI in Desktop genutzt wurde. Somit lässt sich beispielsweise mit den .NET-Dateiklassen aus dem Namespace System.IO das komplette Dateisystem einlesen, falls das notwendig sein sollte. Auch basiert die Anwendung auf .NET 5.0. Das bedeutet, dass sämtliche .NET-5.0-Klassenbibliotheken und somit auch .NET-Standard-2.1-Klassenbibliotheken für diese Anwendung zur Verfügung stehen und genutzt werden können.

Zum Programmieren lässt sich genau wie in UWP und WPF das Model-View-ViewModel-Pattern einsetzen. Entwickler können folglich die aus UWP und WPF bewährten Best Practices auch in WinUI nutzen. Wenn man sich noch einmal bewusst macht, dass WinUI nichts anderes als das UWP UI Framework ist – einfach entkoppelt von Windows 10 –, dann klingt es logisch, dass sämtliche Funktionen aus UWP auch in WinUI vorhanden sind.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Ausblick und Fazit

WinUI 3.0 ist mit Project Reunion 0.5 bereit für den produktiven Einsatz. Microsoft plant das Release von Project Reunion 1.0 laut Roadmap [7] in etwa im Oktober 2021, d. h., sehr wahrscheinlich fällt dieses Release mit dem Erscheinen von .NET 6 zusammen, das für November 2021 geplant ist.

WinUI 3.0 als solches stellt die Zukunft zum Entwickeln moderner Windows-Anwendungen mit .NET/C# und auch mit C++ dar. Insbesondere die Flexibilität bezüglich der Programmiersprache (C# oder C++) und die Flexibilität bezüglich des App-Modells (UWP oder Win32) machen WinUI zu einer fantastischen Wahl. Microsoft positioniert WinUI 3.0 auch als native UI-Plattform von Windows 10. Somit ist es klar, dass dieses UI Framework vorangetrieben wird und hier auch die Innovationen im Windows-Desktop stattfinden werden. Doch WPF-Entwickler können sich entspannt zurücklehnen. Das bestehende Know-how kann mit WinUI weitestgehend wiederverwendet werden, da sowohl WinUI als auch WPF zum Erstellen von Benutzeroberflächen XAML verwenden – und da finden sich in beiden Frameworks sehr ähnliche Konzepte für etwa Layout, Styling und Data Binding. Auch Windows-Forms-Entwickler können sich freuen, denn auch bestehende WPF- und Windows-Forms-Anwendungen lassen sich mit WinUI 3 Controls modernisieren. Die Zukunft wird somit sehr interessant und spannend für Entwickler, die Windows-Desktopanwendungen mit .NET oder mit C++ programmieren möchten.

 

Links & Literatur

[1] Windows Terminal: https://www.microsoft.com/de-de/p/windows-terminal/9n0dx20hk701

[2] WinUI GitHub Repo: https://github.com/microsoft/microsoft-ui-xaml

[3] Project Reunion GitHub Repo: https://github.com/microsoft/microsoft-ui-xaml

[4] C#/WinRT GitHub Repo: https://github.com/microsoft/CsWinRT 

[5] Project Reunion Visual Studio Extension: https://marketplace.visualstudio.com/items?itemName=ProjectReunion.MicrosoftProjectReunion

[6] Dokumentation zum Aufsetzen der Umgebung für WinUI 3: https://docs.microsoft.com/windows/apps/winui/winui3

[7] WinUI Roadmap: https://github.com/microsoft/microsoft-ui-xaml/blob/master/docs/roadmap.md

The post Von UWP zu WinUI: Eine spannende Reise in die Zukunft der Windows-Anwendungsentwicklung appeared first on BASTA!.

]]>
Asynchronität in C# und Go: Effektive Programmierung für moderne Anwendungen https://basta.net/blog/asynchronitaet-mit-csharp-und-go/ Tue, 04 May 2021 09:22:46 +0000 https://basta.net/?p=82399 Wie können wir asynchron Nachrichten zwischen Verarbeitungsschritten austauschen? Dazu existieren in C# und Go verschiedene Ansätze – ein guter Anlass, einen Blick über den Tellerrand zu wagen und sich die Programmiersprache Go einmal näher anzusehen. Bevor wir damit starten, rufen wir uns zunächst die TPL Dataflow Library in Erinnerung.

The post Asynchronität in C# und Go: Effektive Programmierung für moderne Anwendungen appeared first on BASTA!.

]]>
Nebenläufige Programmierung ist heutzutage die Regel, nicht die Ausnahme. Daten werden gelesen oder empfangen, asynchron innerhalb des Prozesses verarbeitet und die Ergebnisse werden danach ausgegeben oder an einen Empfänger gesendet. Natürlich lässt sich die Kommunikation zwischen den Threads eines Prozesses über Puffer im Speicher und Synchronisationsobjekte wie Locks oder Semaphore lösen. Dieses Programmiermodell ist jedoch fehleranfällig und führt nicht selten zu ineffizienten Algorithmen.

Die meisten Programmierplattformen, die ich kenne, enthalten auf Programmiersprachen- oder Framework-Ebene alternative Mechanismen, um asynchron Messages zwischen Verarbeitungsschritten auszutauschen. In diesem Artikel vergleiche ich die Ansätze von C# und Go, wobei der Schwerpunkt auf den Besonderheiten von Go liegt. Der Message-Austausch zwischen den sogenannten Goroutines über sogenannte Channels ist eine charakteristische Eigenschaft der Sprache Go. Dieser Artikel soll C#-Entwicklerinnen und Entwicklern einen Blick über den Tellerrand bieten und vielleicht sogar Lust darauf machen, im einen oder anderen Projekt Go auszuprobieren.

TPL Dataflow Library

Bevor wir auf Go und seine Channels zu sprechen kommen, möchte ich kurz in Erinnerung rufen, was C# und .NET in Sachen asynchronem Message-Austausch innerhalb von Prozessen eingebaut hat: die TPL (Task Parallel Library) Dataflow Library [1]. Diese Bibliothek basiert auf den folgenden Grundprinzipien:

  • Source Blocks (ISourceBlock<T>) sind Datenquellen. Man kann Messages von ihnen lesen.

  • Target Blocks (ITargetBlock<T>) empfangen Messages. Man kann Messages auf sie schreiben.

  • Propagator Blocks (IPropagatorBlock<TIn, TOut>) sind gleichzeitig Source und Target Blocks. Sie verarbeiten Daten in irgendeiner Form (Projizieren, Filtern, Gruppieren etc.).

Die Blöcke kann man flexibel zu Pipelines kombinieren. .NET kommt mit vielen vordefinierten Blöcken (Namespace: System.Threading.Tasks.Dataflow), die man je nach Anwendungsfall zu einer Pipeline zusammenstellt.

Listing 1 zeigt exemplarisch, wie eine Datenverarbeitung mit der TPL Dataflow Library programmiert wird. Ein Producer erzeugt Daten. In der Praxis würden diese beispielsweise aus Dateien oder Datenbanken geladen beziehungsweise über das Netzwerk empfangen. Im Beispiel werden Zufallsdaten in einen BufferBlock geschrieben. Er kann je nach Anwendungsfall als Source-, Propagator- oder Target-Block eingesetzt werden. Der Transformer verarbeitet die Daten und gibt das Verarbeitungsergebnis an den nächsten Schritt der Pipeline weiter. Im Beispiel wird der Mittelwert aus Zahlen in einem Array berechnet. In der Praxis können hier beliebig komplexe Logiken zur Verarbeitung ablaufen. Eine Besonderheit der TPL Dataflow Library ist, dass sie sich automatisch um die Parallelisierung der Message-Verarbeitung kümmert, wenn der jeweilige Algorithmus das erlaubt. Im Beispiel wird MaxDegreeOfParallelism angegeben, wodurch mehrere Transformer parallel laufen. Die Verarbeitung kann dadurch entsprechend beschleunigt werden. Der Consumer ist im Beispiel der letzte Schritt der Pipeline. Er erwartet das Eintreffen von Messages und verarbeitet sie.

Listing 1: TPL Dataflow Pipeline

using System;
using static System.Console;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
 
// Create a buffer (=target block) into which we can write messages
var buffer = new BufferBlock<byte[]>();
 
// Create a transformer (=source and target block) that processes data.
// For demo purposes, we specify a degree of parallelism. This allows
// .NET to run multiple transformers concurrently.
var transform = new TransformBlock<byte[], double>(Transform, new() { MaxDegreeOfParallelism = 5 });
 
// Link buffer with transformer
buffer.LinkTo(transform);
 
// Start asynchronous consumer
var consumerTask = ConsumeAsync(transform);
 
// Start producer
Produce(buffer);
 
// Producer is done, we can mark transformer as completed.
// This will stop the consumer after it will have been finished
// consuming buffered messages.
transform.Complete();
 
// Wait for consumer to finish and print number of processed messages
var bytesProcessed = await consumerTask;
WriteLine($"Processed {bytesProcessed} messages.");
 
/// <summary>
/// Produces values and writes them into <c>target</c>
/// </summary>
static void Produce(ITargetBlock<byte[]> target)
{
  // Here we generate random bytes. In practice, the producer
  // would e.g. read data from disk, receive data over the network,
  // get data from a database, etc.
  var rand = new Random();
  for (int i = 0; i < 100; ++i)
  {
    var buffer = new byte[1024];
    rand.NextBytes(buffer);
 
    // Send message into target block
    WriteLine("Sending message");
    target.Post(buffer);
  }
 
  // Mark as completed
  target.Complete();
}
 
/// <summary>
/// Transforms incoming message (byte array -> average value)
/// </summary>
static double Transform(byte[] bytes)
{
  // For debug purposes, we print the thread id. If you run the program,
  // you will see that transformers run in parallel on multiple threads.
  WriteLine($"Transforming message on thread ${Thread.CurrentThread.ManagedThreadId}");
  return bytes.Average(val => (double)val);
}
 
/// <summary>
/// Consumes message
/// </summary>
static async Task<int> ConsumeAsync(ISourceBlock<double> source)
{
  var messagesProcessed = 0;
 
  // Await incoming message
  while (await source.OutputAvailableAsync())
  {
    // Receive message
    var average = await source.ReceiveAsync();
 
    // Process message. In this demo we are just printing its content.
    WriteLine($"Consumed message, average value is {average}");
    messagesProcessed++;
  }
 
  return messagesProcessed;
}

Die TPL Dataflow Library ist gut geeignet für CPU- oder I/O-intensive Algorithmen, bei denen eine große Datenmenge idealerweise in mehreren, parallel laufenden Threads verarbeitet werden soll. Als Entwicklerin oder Entwickler muss man sich nicht mit Shared Memory, Thread-Synchronisation und manuellem Scheduling der Aufgaben herumplagen. Das geschieht automatisch im Hintergrund durch die Library.

Auftritt: Go

Bevor wir ins Thema Go Channels einsteigen, zunächst ein paar einleitende Worte zu dieser Programmiersprache für die, die mit ihr noch keine Berührungspunkte hatten. Go stammt aus dem Hause Google. Die Grundidee hinter Go ist, eine Programmiersprache zu haben, die besonders flott beim Kompilieren, einfach in der Nutzung und performant ist, was den generierten Code betrifft. Go wird im Gegensatz zu C# in Maschinensprache übersetzt. Es gibt keine Intermediate Language. Es handelt sich trotzdem wie bei C# um eine Managed Language inklusive Garbage Collector.

Der auffälligste Unterschied zwischen C# und Go, der jeder Entwicklerin und jedem Entwickler beim Umstieg sofort auffällt, ist die Einfachheit der Sprache. Go hat im Vergleich zu C# nur einen Bruchteil an Schlüsselwörtern. Ein einprägsames Beispiel dafür ist der Increment-Operator (++). In Go ist er ein Statement, keine Expression. Etwas wie if (++x > 5) gibt es in Go nicht. Das Go-Entwicklungsteam ist sehr zurückhaltend, neue Sprachfeatures hinzuzufügen, die nur dazu dienen, Code kürzer zu machen. Es wird nicht als Nachteil empfunden, wenn man als Entwickler ein paar Zeilen mehr Code schreiben muss, wenn im Gegenzug die Sprache dadurch einfach und schlank gehalten werden kann. Ein weiteres Beispiel, das die Philosophie hinter Go klar macht, sind Generics. Es gibt Stand heute in Go keine Generics, obwohl die Sprache schon über 10 Jahre alt und speziell im Bereich Cloud-Computing und Containertechnologie weit verbreitet ist. Generics (inklusive Generic Channels) sind gerade erst in Planung und werden voraussichtlich nächstes Jahr zu Go hinzugefügt.

Kommt man von C#, erfordert das eine gewisse Bereitschaft zum Umdenken. Gerade in den letzten Sprachversionen von C# wurde vieles hinzugefügt, was nur dazu dient, kürzeren und prägnanteren Code zu schreiben. Go ist im Vergleich zu C# meiner Erfahrung nach eine Art „Coding Detox“. Man arbeitet mit einer auf das Wesentliche reduzierten Sprache, die sich langsam, aber stetig weiterentwickelt und langfristige Stabilität bietet. Wenn man sich auf diesen Grundgedanken einlässt, ist Entwickeln mit Go eine angenehme Erfahrung.

Dieser Artikel ist keine generelle Einleitung in Go. Wir konzentrieren uns auf zwei charakteristische Sprachfunktionen, Goroutines und Channels. Die Beispiele sind so einfach gehalten, dass auch Leserinnen und Leser ohne Go-Know-how ohne Weiteres folgen können.

Goroutines

Wir wollen uns in diesem Artikel mit dem Austauschen von Nachrichten zwischen nebenläufig ausgeführten Programmteilen in einem Prozess beschäftigen. Daher müssen wir uns ansehen, welche Analogie es in Go für C# Tasks gibt. Das Grundkonstrukt für nebenläufige Threads in Go-Programmen sind sogenannte Goroutines. Wie C# Tasks sind Goroutines viel leichtgewichtiger als Betriebssystem-Threads, es kann daher viel mehr Goroutines geben als darunterliegende Threads. Goroutines brauchen nur wenig Speicher (wenige KB). Das Scheduling wird durch die Go Runtime erledigt.

Schluss mit der Theorie, werfen wir einen Blick auf ein Codebeispiel. Listing 2 zeigt ein ganz einfaches Beispiel einer Goroutine. Achten Sie beim Durchsehen des Codes darauf, dass in der main-Methode dem ersten Aufruf von sayHello das Schlüsselwort go vorangestellt ist. Dadurch startet die aufgerufene Methode in einer eigenen Goroutine und ist nebenläufig zum Hauptprogramm.

Listing 2: Goroutine

package main
 
import (
  "fmt"
  "time"
)
 
func sayHello(source string) {
  fmt.Printf("Hello World from %s!\n", source)
  time.Sleep(5 * time.Millisecond)
}
 
func main() {
  go sayHello("goroutine")
  sayHello("direct call")
 
  time.Sleep(10 * time.Millisecond)
}

Es gäbe noch eine Menge über Goroutines zu sagen. In diesem Artikel wollen wir uns aber in Folge mit Channels beschäftigen. Die erwähnten Grundlagen von Goroutines sind für das Verständnis von Channels ausreichend. Wer Go in der Praxis einsetzen möchte, ist gut beraten, noch etwas Zeit in das Lesen der Dokumentation über Goroutines zu investieren. Aber keine Angst, das ist kein sprichwörtliches Rabbit Hole, in das man abtauchen muss, um tagelang Feinheiten des Goroutine-Schedulers von Go zu studieren. Die Grundphilosophie von Go ist, dass die Sprache einfach sein soll und man für ihre Verwendung die Implementierungsdetails nicht zu kennen braucht. Aus eigener Erfahrung kann ich sagen, dass der Einstieg in Go für erfahrene C#-Entwicklerinnen und Entwickler nicht schwer ist. Wer C# Tasks verstanden hat, kann in kürzester Zeit Anwendungen mit Goroutines schreiben.

Channels

Channels in Go dienen dazu, Daten zwischen Goroutines auszutauschen. Channels sind typsicher und man erspart sich durch ihre Verwendung die manuelle Synchronisation nebenläufiger Prozesse beim Zugriff auf gemeinsame Speicherbereiche. Insofern haben Channels in Go eine gewisse Ähnlichkeit mit der C# Dataflow Library. Der große Unterschied ist, dass Channels viel tiefer in die Programmiersprache Go und die zugehörigen Bibliotheken integriert sind als Dataflow Blocks in C# und .NET.

Listing 3 zeigt an einem einfachen Beispiel, wie mit Go Channels programmiert wird. Beachten Sie beim Durchsehen des Codes die Codekommentare. Sie weisen auf wichtige Dinge hin, die für das Verständnis der Channels wichtig sind.

Listing 3: Grundlagen von Go Channels

package main
 
import (
  "fmt"
  "time"
)
 
// Note that our function receives a 
// write-only channel. The channel will be used
// to send back the result.
func getValueAsync(result chan<- int) {
  // Simulate some longer running operation. Could
  // be reading data from disk, accessing DB or
  // network, etc.
  time.Sleep(10 * time.Millisecond)
 
  // Return result and print some debug output
  fmt.Println("Before sending result")
  result <- 42
  fmt.Println("After sending result")
}
 
func doSomethingComplex(done chan<- bool) {
  // Simulate a longer operation again
  time.Sleep(10 * time.Millisecond)
 
  // This time, we do not return a meaningful
  // result. We only signal that processing has
  // been completed by sending something into
  // the channel.
  done <- true
}
 
func main() {
  // Create a channel for receiving an async result
  result := make(chan int)
 
  // Run operation asynchronously
  go getValueAsync(result)
 
  // Wait for result by receiving from channel
  fmt.Println("Before receiving result")
  fmt.Println(<-result)
  fmt.Println("After receiving result")
 
  // Create another channel and start 
  // another Goroutine
  done := make(chan bool)
  go doSomethingComplex(done)
 
  // The next statement is not interested in the
  // Goroutine’s result. We just want to block this
  // method’s execution until something has been 
  // sent to the channel.
  <-done
  fmt.Println("Complex operation is done")
}

Wie man bereits an diesem ersten Beispiel sieht, gibt es zwar gewisse Ähnlichkeiten zwischen der TPL Dataflow Library und den Go Channels, der typische Anwendungsbereich ist jedoch unterschiedlich. Die Dataflow Library ist im Speziellen interessant, wenn man Anwendungen hat, bei denen größere Datenmengen asynchron abgearbeitet werden sollen und der Algorithmus von automatischer Parallelisierung profitiert. Bei Go Channels spielt die automatisierte Parallelverarbeitung keine so große Rolle. Natürlich gibt es Anwendungsbereiche, bei denen beide Lösungsansätze einsetzbar sind. Die beim Design und bei der Entwicklung vordergründig wichtigsten Ziele waren aber nicht ident.

Blocking Channels

Wenn man keine besonderen Vorkehrungen trifft, blockieren Channels die lesende Goroutine, falls der Channel keinen Wert enthält, und die schreibende Goroutine, falls bereits ein Wert im Channel steckt und noch nicht empfangen wurde. „Blockieren“ bedeutet in diesem Fall nicht, dass der darunterliegende Betriebssystem-Thread blockiert ist. Blockiert wird nur die Goroutine. Der Go-interne Scheduler wird versuchen, die Zeit zu nutzen und übergibt die Kontrolle an Goroutines, die gerade nicht blockiert sind und ausgeführt werden könnten. Die Eigenschaft des Blockierens von Channels kann verwendet werden, um Goroutines zu synchronisieren. Was ist aber, wenn man ein Producer-Consumer-Pattern umsetzen möchte, bei dem der Producer manchmal etwas schneller Daten liefert als Consumer sie verarbeiten können? Für diese Zwecke gibt es Buffered Channels.

Buffered Channels

Listing 4 zeigt Buffered Channels im Einsatz. Der Code enthält wieder viele Kommentare, die auf wichtige Aspekte hinweisen. Besondere Aufmerksamkeit verdient die for/range-Schleife in der main-Methode. Man verwendet sie in Go, um über die mit Hilfe des Channels empfangenen Messages zu iterieren. Das ist nur der Anfang der Integration von Channels in die Sprache Go. Channels sind fundamentaler Bestandteil der Sprache und sie werden an allen Ecken und Enden in Go-Bibliotheken eingesetzt. Das ist ein weiterer Unterschied zwischen der TPL Dataflow Library in C# und den Channels in Go. Die Dataflow Library wird in C# für ganz spezielle Anwendungsfälle eingesetzt. Natürlich ist das API der Library so gestaltet, dass man sie angenehm mit C# nutzen kann. Von einer tiefen Integration in die Sprachsyntax von C# kann man aber ganz im Gegensatz zu Channels in Go nicht sprechen.

Listing 4: Buffered Channels

package main
 
import (
  "fmt"
  "math/rand"
  "time"
)
 
// Note that our function receives a write-only channel.
// The channel will be used to send back the result.
func producer(result chan<- int) {
  // Send 50 values through channel
  for i := 0; i < 20; i++ {
    // Simulate a longer operation by waiting. In practice,
    // this would be e.g. an I/O operation.
    time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
 
    // Send back result. Note that sending will be blocked
    // if buffered channel is currently full.
    fmt.Println("Sending")
    result <- rand.Intn(42)
  }
 
  // Close channel to signal caller that no further
  // values can be expected.
  close(result)
}
 
func main() {
  // Call async operation that produces values
  values := make(chan int, 10)
  go producer(values)
 
  // Note that we can use for/range to iterate over
  // values coming in through a channel. The loop will block
  // if no value is currently available in the channel.
  for val := range values {
    fmt.Printf("Received %d\n", val)
    time.Sleep(1 * time.Second)
  }
}

Non-Blocking Channel Operations

Ein weiteres Beispiel für die Sprachintegration von Channels in Go ist das select-Statement. Entwicklerinnen und Entwickler, die aus C# kommen, werden überrascht sein, denn das select-Statement hat in Go eine andere Funktion als in C#. Es wird verwendet, um Werte aus verschiedenen Channels zu empfangen. Wenn es einen default-Zweig enthält, wird es zu einer Non-Blocking Channel Operation. Das Codebeispiel in Listing 5 zeigt das Prinzip. Das erste select-Statement in main enthält einen default-Zweig. Der Code kann daher auch bei Nichtvorhandensein eines Wertes im Channel weiterlaufen. Das zweite select-Statement enthält Empfangsoperationen für zwei verschiedene Channels. Ausgeführt wird der Zweig, dessen Channel als erstes einen Wert empfängt.

Listing 5: Non-Blocking Channel Operations

package main
 
import (
  "fmt"
  "time"
)
 
func getValueAsync(result chan int) {
  time.Sleep(10 * time.Millisecond)
 
  fmt.Println("Before sending result")
  result <- 42
  fmt.Println("After sending result")
}
 
func main() {
  // Create channel and trigger asynchronous operation
  result := make(chan int)
  go getValueAsync(result)
 
  // Wait for some milliseconds
  time.Sleep(5 * time.Millisecond)
 
  // Note that we are using Go’s select statement here.
  // It is non-blocking because of the default case.
  select {
  case m := <-result:
    fmt.Printf("We have a value: %d\n", m)
  default:
    fmt.Println("Sorry, no value")
  }
 
  // Empty channel and restart async operation
  <-result
  go getValueAsync(result)
 
  // Use select statement without default case. Here
  // we combine it with time.After, which is a timer sending
  // a value in a channel after a configurable amount of time.
  select {
  case m := <-result:
    fmt.Println(m)
  case <-time.After(5 * time.Millisecond):
    fmt.Println("timed out")
  }
}

Um mögliche Einsatzbereiche des switch-Statements zu verdeutlichen, wurde in Listing 6 ein Beispiel entwickelt, bei dem eine zentrale Goroutine verschiedene mathematische Operationen anbietet. Die Operationen werden nicht direkt aufgerufen, sondern durch Senden einer Message an einen Channel ausgelöst. Anwenden könnte man dieses Entwurfsmuster beispielsweise in einer Webanwendung, bei der eine einzelne Goroutine Dienste anbietet, die von den parallellaufenden Goroutines, die für die Abarbeitung eingehender HTTP Requests gestartet wurden, aufgerufen werden.

Listing 6: Asynchrone Nachrichtenverarbeitung

package main
 
import "fmt"
 
// Create structures for operation parameters
type binaryOperationParameter struct {
  x int
  y int
}
 
// Create structure with channels used to trigger
// asynchronous operations
type operationChannels struct {
  add     chan binaryOperationParameter
  sub     chan binaryOperationParameter
  square chan int
  negate chan int
  exit     chan bool
}
 
// Helper methods to create channels
func createChannels() operationChannels {
  return operationChannels{
    add:     make(chan binaryOperationParameter),
    sub:     make(chan binaryOperationParameter),
    square: make(chan int),
    negate: make(chan int),
    exit:     make(chan bool),
  }
}
 
func calculator(channels operationChannels, result chan<- int) {
  // Run until exit operation is received
  for {
    select {
      case p := <-channels.add:
        result <- p.x + p.y
      case p := <-channels.sub:
        result <- p.x - p.y
      case p := <-channels.square:
        result <- p * p
      case p := <-channels.negate:
        result <- -p
      case <-channels.exit:
        close(result)
        fmt.Println("Good bye from calculator")
        return
    }
  }
 
}
 
func main() {
  // Start calculator in a separate Goroutine
  channels := createChannels()
  result := make(chan int)
  go calculator(channels, result)
 
  // Async add
  channels.add <- binaryOperationParameter{x: 21, y: 21}
  fmt.Printf("21 + 21 = %d\n", <-result)
 
  // Async square
  channels.square <- 2
  fmt.Printf("2^2 = %d\n", <-result)
 
  // Send exit message and wait until result channel is closed
  channels.exit <- true
  <-result
 
  fmt.Println("We are done")
}

Fazit

Wenn ich mit C#-Entwicklerinnen und -Entwicklern über Go spreche und von meiner Begeisterung für Channels und die damit verbundenen Sprachkonstrukte in Go erzähle, bekomme ich oft als Gegenargument zu hören, dass .NET mit der TPL Dataflow Library im Wesentlichen das Gleiche eingebaut habe. Zugegeben, beide Technologien haben Ähnlichkeiten. Ich stimme allerdings der Aussage, dass sie die gleichen Probleme lösen und sich beim Programmieren gleich anfühlen, nicht zu. Das Besondere an den Channels in Go ist die tiefe Integration in die Programmiersprache. Seit ich speziell im Bereich von Web-APIs Go einsetze, habe ich das zu schätzen gelernt und ich hoffe, dass ich manche Leserinnen und Leser auf Go neugierig machen konnte. Es geht dabei nicht darum, C# durch Go zu ersetzen, sondern neue Sichtweisen kennenzulernen, seine Komfortzone hin und wieder zu verlassen und bei manchen Projekten auf neue Technologien zurückzugreifen, die sich im jeweiligen Fall besonders gut eignen.

Links & Literatur

[1] https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library

The post Asynchronität in C# und Go: Effektive Programmierung für moderne Anwendungen appeared first on BASTA!.

]]>
Das ist doch nicht normal: Herausforderungen in der agilen Softwareentwicklung verstehen https://basta.net/blog/das-ist-doch-nicht-normal/ Fri, 09 Apr 2021 13:16:48 +0000 https://basta.net/?p=82210 Wir Menschen sind üblicherweise gut im Erkennen von Anomalien: Oft reicht ein schneller Blick auf Monitoringcharts, um ein Performanceproblem zu erkennen (oder im besten Fall vorherzusagen). Eine Kurve steigt unnatürlich rasch an, ein Wert fällt unter ein gewünschtes Minimum oder es gibt Schwankungen, die rational nicht erklärbar sind. Manches davon würde sich technisch durch ein simples if automatisiert erkennen lassen, aber mehr Spaß macht es mit dem neuen Metrics Advisor der Azure Cognitive Services.

The post Das ist doch nicht normal: Herausforderungen in der agilen Softwareentwicklung verstehen appeared first on BASTA!.

]]>
Entwickeln Sie eine Anwendung, in der zeitbasierte Daten gespeichert werden? Bestellungen, Ratings, Kommentare, Terminvereinbarungen, Zeitbuchungen, Reparaturen oder Kundenkontakte? Haben Sie detaillierte Logdateien über die Anzahl und Dauer der Zugriffe? Hand aufs Herz: Wie schnell würde Ihnen auffallen, wenn sich Ihre Systeme (oder Ihre Benutzer) anders verhalten als gedacht? Vielleicht versucht einer Ihrer Mandanten die Software gerade mit viel zu vielen Daten zu fluten, oder ein Produkt in Ihrem Webshop geht „durch die Decke“? Vielleicht gibt es Performanceprobleme in bestimmten Browsern oder unnatürliche CPU-Spitzen, die eine nähere Betrachtung verdienen? Der Metrics Advisor aus den Azure Cognitive Services stellt einen KI-unterstützten Service zur Verfügung, der Ihre Daten überwacht und bei Anomalieverdacht Alarm schlägt.

Was ist normal?

Die große Herausforderung dabei ist, zu definieren, was überhaupt eine Anomalie darstellt. Stellen Sie sich ein ganzes Regal voller Entwicklerzeitschriften vor, nur eine Sportzeitschrift ist darunter. Mit Recht könnte man also behaupten, die Sportzeitschrift sei eine Anomalie. Vielleicht sind zufällig aber auch alle Zeitschriften im A4-Format, nur zwei in A5 – eine weitere Anomalie. Für eine automatisierte Erkennung von Anomalien ist es somit wichtig, aus Erfahrungen zu lernen und zu verstehen, welche Anomalien tatsächlich Relevanz besitzen – und wo ein Fehlalarm vorliegt, der zukünftig vermieden werden soll.

Im Fall von zeitbasierten Daten – und darum geht es beim Metrics Advisor ausschließlich – gibt es mehrere Ansätze zur Erkennung von Anomalien. Der einfachste Weg ist die Definition von harten Grenzen: Alles unter oder über einem gewissen Schwellwert wird als Anomalie betrachtet. Dafür braucht es kein Machine Learning und keine künstliche Intelligenz, die Regeln sind schnell implementiert und klar nachvollziehbar. Bei Monitoringdaten wird das vielleicht genügen: Sind 70 Prozent des Speicherplatzes belegt, will man reagieren. Oft verläuft die (Daten-)Welt aber nicht in starren Bahnen, manchmal ist die relative Veränderung entscheidender als der tatsächliche Wert: Wenn innerhalb der letzten drei Stunden ein signifikanter Anstieg oder eine Abnahme von mehr als 10 Prozent erfolgt ist, soll eine Anomalie erkannt werden. Ein Beispiel könnte man aus dem Finanzbereich nehmen. Ändert sich Ihr privater Kontostand von 20 000 € auf 30 000 €, so ist das vermutlich als Anomalie zu werten. Ändert sich ein Firmenkonto von 200 000 € auf 210 000 €, ist das nicht der Rede wert. Gut an diesem Beispiel erkennbar: Die Einordnung, was eine Anomalie ist, ändert sich möglicherweise über die Zeit. Bei der Gründung eines Start-ups sind 100 000 € viel Geld, bei einem Großkonzern eine Randnotiz. Was aber, wenn Ihre Daten saisonalen Schwankungen unterliegen oder einzelne Tage wie Wochenenden oder Feiertage sich deutlich anders verhalten? Auch hier ist die Einordnung gar nicht so trivial. Ist eine Grippewelle in den Wintermonaten erwartbar und nur im Sommer eine Anomalie oder soll jeder Anstieg der Infektionszahlen erkannt werden? Sie sehen, die Frage der Anomalieerkennung ist unabhängig vom Tooling eine zum Teil sehr subjektive – und nicht alle Entscheidungen können von der Technik übernommen werden. Machine Learning kann allerdings helfen, aus historischen Daten zu lernen und normale Schwankungen von Anomalien zu unterscheiden.

Metrics Advisor

Der Metrics Advisor ist ein neuer Service aus der Reihe der Azure Cognitive Services und vorerst nur in einer Preview-Version verfügbar. Intern wird ein weiterer Service verwendet, nämlich der Anomaly Detector (ebenfalls aus den Cognitive Services). Der Metrics Advisor ergänzt diesen um zahlreiche API-Methoden, ein Web-Frontend zur Verwaltung und Ansicht der Data Feeds, Root-Cause-Analysen und Alert-Konfigurationen. Um experimentieren zu können, benötigen Sie eine Azure Subscription. Dort können Sie eine Metrics-Advisor-Ressource erstellen; die Verwendung ist in der Preview-Phase komplett kostenlos.

Das Beispiel, mit dem ich Ihnen die grundsätzliche Vorgehensweise demonstrieren möchte, verwendet Daten von Google Trends. Ich habe für zwei Suchbegriffe („Impfstoff“ und „Influenza“) die wöchentlichen Google-Trends-Scores der letzten fünf Jahre für vier Länder (USA, China, Deutschland, Österreich) ausgewertet und heruntergeladen und möchte versuchen, etwaige Anomalien in diesen Daten zu erkennen. Die gesamte Administration des Metrics Advisors kann über das zur Verfügung gestellte REST API durchgeführt werden, ein schnellerer Einstieg gelingt über das bereitgestellte Web-Frontend, den sogenannten Workspace.

Data Feeds

Zu Beginn erzeugen wir einen neuen Data Feed, der die Basisdaten für die Auswertung bereitstellt. Als Datenquellen stehen out of the box verschiedenste Azure Services und Datenbanken zur Verfügung: Application Insights, Blob Storage, Cosmos DB, Data Lake, Table Storage, SQL Database, Elastic Search, Mongo DB, PostgreSQL – und einige mehr. In unserem Beispiel habe ich die Google-Trends-Daten in eine SQL-Server-Datenbank geladen. Die Tabelle hat neben dem Primärschlüssel noch vier weitere Spalten: das Datum, das Land und die Scores für Impfstoff und Influenza. Im Metrics Advisor muss nun (neben dem Connection String) ein SQL-Statement angegeben werden, mit dem alle Werte für ein bestimmtes Datum abgefragt werden können. Der Service wird nämlich auch in Zukunft regelmäßig unsere Datenbank aufsuchen, um die neuen Daten abzuholen und zu analysieren. Die Häufigkeit, in der diese Aktualisierung passieren soll, wird über die Granularität eingestellt: Die Daten können jährlich, monatlich, wöchentlich, täglich, stündlich oder in noch kürzeren Zeiträumen (die kleinste Einheit sind 300 Sekunden) ausgewertet werden. Abhängig von der gewählten Granularität gibt Microsoft auch Empfehlungen, wie viele historische Daten bereitgestellt werden sollen. Wählen wir einen 5-Minuten-Takt, dann reichen Daten der letzten vier Tage. In unserem Fall, einer wöchentlichen Analyse, werden bereits vier Jahre empfohlen. Nach dem Klick auf Verify and get schema wird das SQL-Statement abgesetzt und die Struktur unserer Datenquelle ermittelt. Wir sehen die in Abbildung 1 gezeigten Spalten und müssen die Bedeutung zuordnen: Welche Spalte beinhaltet den Timestamp? Welche Spalten sollen als Metrik analysiert werden – und wo befinden sich zusätzliche Fakten (Dimensionen), die mögliche Ursachen für Anomalien sein könnten?

 

Abb. 1: Konfiguration des Data Feed

 

Bevor die Daten nun tatsächlich importiert werden, gilt es noch, eine Einstellung zu betrachten: die roll up settings. Für eine spätere Ursachenanalyse (Root Cause Analysis) ist es notwendig, einen mehrdimensionalen Cube zu bauen, der je Dimension aggregierte Werte berechnet (also in unserem Fall pro Woche auch eine Aggregation über alle Länder). So kann später bei Anomalien untersucht werden, welche Dimensionen bzw. Ausprägungen ursächlich für die Wertveränderung zu sein scheinen. Sollten sich die Aggregationen nicht ohnehin bereits in unserer Datenquelle befinden, kann der Metrics Advisor zur Berechnung der Daten aufgefordert werden. Die einzige Entscheidung, die wir dabei treffen müssen, ist die Art der Aggregation (Summe, Durchschnitt, Min, Max, Anzahl). Hier hinkt unser Beispiel etwas: Wir wählen Durchschnitt aus, der Wert der USA fließt aber somit mit dem gleichen Faktor ein wie der Wert des kleinen Österreich. Sie sehen: oft scheitert man an der Datenqualität oder muss aufpassen, dass Aussagen nicht auf falschen Berechnungen beruhen.

Abschließend starten wir den Import, der je nach Datenmenge durchaus mehrere Stunden in Anspruch nehmen kann. Der Status des Imports kann auch im Workspace nachverfolgt werden, einzelne Zeiträume können jederzeit neu geladen werden.

Analyse und Feintuning

Sobald der Datenimport abgeschlossen ist, können wir einen ersten Blick auf unsere Ergebnisse werfen. Das wesentliche Ziel des Metrics Advisors ist die Analyse und Erkennung neuer Anomalien – also die Untersuchung, ob der jeweils neueste Datenpunkt eine Anomalie darstellt oder nicht. Dennoch werden auch die historischen Daten betrachtet. Abhängig von der Granularität blickt der Service einige Stunden bis Jahre in die Vergangenheit und versucht auch dort, Anomalien zu kennzeichnen. In unserem Fall (Daten aus fünf Jahren, wöchentliche Aggregation) liefert die sogenannte Smart Detection Ergebnisse für die vergangenen sechs Monate und markiert einzelne Zeitpunkte als Anomalie (Abb. 2).

 

Abb. 2: Datenvisualisierung inkl. Anomalien

 

Nun gilt es, einen Blick auf die Vorschläge zu werfen: Sind die identifizierten Anomalien tatsächlich relevant? Ist die Erkennung zu sensibel oder zu tolerant? Es gibt einige Möglichkeiten, um die Erkennungsrate zu verbessern. Erinnern wir uns an den Beginn dieses Artikels: Die große Herausforderung besteht darin, zu definieren, was überhaupt eine Anomalie darstellt.

Vermutlich fällt Ihnen im Workspace sehr rasch ein prominent platzierter Schieberegler auf. Damit können wir die Sensitivität steuern. Je höher der Wert, desto kleiner wird der Bereich, der normale Punkte enthält. Wir bekommen diese Grenzen auch innerhalb der Charts als hellblaue Fläche visualisiert. Manchmal ist es sinnvoll, nicht gleich beim ersten Auftreten einer Anomalie zu warnen, sondern erst, wenn über einen gewissen Zeitraum mehrere Anomalien erkannt wurden. Wir können den Metrics Advisor so konfigurieren, dass eine bestimmte Anzahl von Punkten rückwirkend betrachtet wird und eine Anomalie erst als solche gilt, wenn von diesen Punkten ein bestimmter Prozentsatz als Anomalie erkannt wurde. Zum Beispiel soll ein kurzes Performanceproblem toleriert werden, aber wenn in den letzten 15 Minuten 70 Prozent der Messwerte als Anomalie erkannt wurden, ist insgesamt von einem Problem zu sprechen.

Je nach Anwendungsfall kann es sinnvoll sein, die Smart Detection durch manuelle Regeln zu ergänzen. Mit Hilfe eines Hard Threshold können eine Unter- oder Obergrenze sowie ein Wertebereich festgelegt werden, der als Anomalie gelten soll. Der Change Threshold bietet die erwähnte Möglichkeit, eine prozentuelle Veränderung zu einem oder mehreren Vorgängerpunkten als Anomalie zu werten. Durch die Art der Verknüpfung (or/and) der verschiedenen Regeln beeinflussen wir die Erkennung: Eine Anomalie soll beispielsweise nur dann als solche erkannt werden, wenn die Smart Detection zuschlägt und der Wert über 30 liegt. Wir können mehrere dieser Konfigurationen zusammenstellen und benennen. Zusätzlich ist es möglich, für einzelne Dimensionen besondere Regeln zu hinterlegen.

Abhängig von unseren Einstellungen werden nun also mehr oder weniger Anomalien in den Daten entdeckt, der Metrics Advisor versucht anschließend, diese in sogenannte Incidents zu überführen. Ein Incident kann aus einer einzigen Anomalie bestehen, oft werden aber auch zusammenhängende Anomalien und damit ganze Zeitbereiche unter einem gemeinsamen Incident angeführt. Im Incident Hub stehen Tools zur näheren Betrachtung zur Verfügung: Wir können die gefundenen Incidents filtern (nach Zeit, Kritikalität und Dimension), eine erste automatische Ursachenanalyse starten (siehe „Root cause“ in Abb. 3) und durch mehrere Dimensionen einen Drill-down durchführen, um Erkenntnisse zu sammeln.

 

Abb. 3: Incident-Analyse

 

Feedback

Der vielleicht größte Vorteil beim Einsatz von künstlicher Intelligenz für die Anomalieerkennung liegt in der Möglichkeit, durch Feedback zu lernen. Selbst wenn Sensitivität und Schwellwerte gut eingestellt wurden, wird der Service manchmal falsch liegen. Genau für diese Datenpunkte kann dann aber über das API oder das Portal Feedback gegeben werden: Wo wurde fälschlicherweise eine Anomalie erkannt? Wo wurde eine Anomalie übersehen? Der Service nimmt dieses Feedback entgegen und versucht, zukünftig ähnlich gelagerte Fälle korrekter zuzuordnen. Auch zeitliche Perioden versucht der Service zu erkennen – und lässt sich eines Besseren belehren, wenn wir einen Zeitbereich markieren und diesen als Periode rückmelden.

Für vorhersehbare Anomalien, die zeitliche Gründe haben (Feiertage, Wochenenden, zyklisch wiederkehrende Ereignisse), gibt es eigene Möglichkeiten zur Konfiguration. Diese sollten daher nicht nachträglich als Feedback gemeldet, sondern als sogenannte Preset Events hinterlegt werden.

Alerts

Wir sollten nun an einem Punkt angekommen sein, an dem Daten regelmäßig importiert werden und die Anomalieerkennung hoffentlich zuverlässig funktioniert. Die beste Erkennung hilft aber nichts, wenn wir zu spät davon erfahren. Daher sollten Alert Configurations eingerichtet werden, die aktiv über Anomalien benachrichtigen. Derzeit stehen drei Kanäle zur Auswahl: per E-Mail, als WebHook oder als Ticket in Azure DevOps. Vor allem die WebHook-Variante bietet spannende Möglichkeiten der Integration: Wir können die erkannten Anomalien in unserer eigenen Anwendung anzeigen oder einen Workflow mit Hilfe der Azure Logic Apps triggern. Vielleicht starten wir als erste automatisierte Maßnahme einfach die betroffene Web-App neu.

Praktisch scheinen auch die Snooze-Settings: Ein Alert kann automatisch dafür sorgen, dass für einen konfigurierbaren Zeitraum danach keine Alerts mehr gesendet werden. Das vermeidet, dass man in der Früh mit 500 E-Mails im Posteingang aufwacht, die alle denselben Inhalt haben.

Resümee

Der Metrics Advisor bietet einen spannenden und leichten Einstieg in die Welt der Anomalieerkennung zeitbasierter Daten. Langjährige Data Scientists werden möglicherweise andere Mittel und Wege bevorzugen (und vielleicht am Paper unter https://arxiv.org/abs/1906.03821 interessiert sein), aber für Anwendungsentwickler, die mit passenden Daten erste Versuche starten wollen, stellt dieser Service eine potente Einstiegsdroge dar. Der Preview-Status ist derzeit vor allem noch im Webportal und in der mangelnden Dokumentationsqualität des REST API ersichtlich; gute konzeptionelle Dokumentation ist aber bereits vorhanden.

Viel Spaß beim Ausprobieren und Experimentieren mit Ihren eigenen Datenquellen.

The post Das ist doch nicht normal: Herausforderungen in der agilen Softwareentwicklung verstehen appeared first on BASTA!.

]]>
TypeScript 4.1: Was Entwickler:innen wissen müssen https://basta.net/blog/breaking-changes-in-typescript-4/ Thu, 18 Mar 2021 11:13:07 +0000 https://basta.net/?p=82046 TypeScript 4.1 ist da und bringt einige Erweiterungen und neue Sprachfeatures, aber keine bahnbrechenden Änderungen. Wie immer gilt bei TypeScript, dass nicht im klassischen Stil der semantischen Versionierung gearbeitet wird. Release 4.1 bietet Entwicklern also keine Pause hinsichtlich der möglicherweise nötigen Anpassungen am Code. Machen wir gemeinsam eine Reise von TypeScript 4.0 bis zur aktuellen Version 4.1. Was gibt es Neues in der vierten Generation?

The post TypeScript 4.1: Was Entwickler:innen wissen müssen appeared first on BASTA!.

]]>
Breaking Changes in TypeScript 4

Die vierte Generation kann jedoch nicht ohne Breaking Changes auskommen. TypeScript verwendet zwar ein Nummerierungsformat, das an die semantische Versionierung erinnert, folgt diesem Schema jedoch nicht. Jede neue Version kann Breaking Changes und große Featureänderungen mitbringen. Das TypeScript-Team begrenzt solche Eingriffe in die Sprache nicht auf die Versionen, die mit einer vollen Zahl nummeriert werden.

Zu den Breaking Changes gehören: Eine spezielle Deklarationsdatei lib.d.ts wird mit jeder Installation von TypeScript geliefert. Diese Datei enthält die Umgebungsdeklarationen für verschiedene DOM-Funktionen und -Typen. Hier wurde document.origin entfernt, das bisher für alte Versionen des Internet Explorers notwendig war. Alternativ steht self.origin zur Verfügung, das aber manuell ausgetauscht werden muss. Ebenfalls wurde Reflect.enumerate; entfernt. Zu den weiteren möglichen Anpassungen gibt es keine näheren Angaben, da diese von den automatisch erzeugten DOM-Typen abhängen.

TypeScript gibt jetzt immer einen Fehler aus, wenn eine Eigenschaft in einer abgeleiteten Klasse deklariert wird, die einen Getter oder Setter der Basisklasse überschreibt. Beim Verwenden der strictNullChecks-Option, wird ein Fehler geworfen, wenn mittels delete-Operator ein Objekt entfernt wird, das nicht vom Type any, unknown, never oder optional ist.

Ebenfalls als Breaking Change gelistet ist, dass resolve in Promises künftig mindestens einen Wert übergeben bekommen muss. Bisher waren die Parameter hier optional zu setzen. Das ist jetzt nicht mehr der Fall: Codestellen, die resolve ohne Parameter nutzen, geben in Zukunft einen Fehler aus.

Support für die neuen JSX Factories von React

JSX steht für JavaScript XML. Damit können wir HTML-Elemente in JavaScript für React schreiben, ohne die createElement()– und/oder appendChild()-Funktion dafür aufrufen zu müssen. Ab TypeScript 4.1 werden die Factory-Funktionen jsx und jsxs von React 17 unterstützt. Dafür stehen zwei neue Optionen für die jsx-Compiler-Option zur Verfügung: react-jsx und react-jsxdev. Diese Optionen sind für Produktions- bzw. Entwicklungskompilierungen vorgesehen. Oft können sich die Optionen von einem zum anderen erstrecken.

Mehr Performance und besserer Auto-Import-Support

Darüber hinaus verbessert TypeScript 4 die Bearbeitungsszenarien in Visual Studio Code und Visual Studio 2017 und 2019. Ein neuer Teilbearbeitungsmodus beim Start behebt langsame Startzeiten, insbesondere bei größeren Projekten. Eine intelligentere Funktion für den automatischen Import erledigt die zusätzliche Arbeit in Editorszenarien, um Pakete einzuschließen, die im Abhängigkeitsfeld von package.json aufgeführt sind. Informationen aus diesen Paketen werden verwendet, um die automatischen Importe zu verbessern, ohne die Typprüfung zu ändern.

Variadische Tupeltypen

Mit Tupeltypen können wir ein Array mit einer festen Anzahl von Elementen ausdrücken, deren Typ bekannt ist, aber nicht identisch sein muss. Beispielsweise möchten wir einen Wert als ein Paar aus einem String und einer Number darstellen, wie es in Listing 1 gezeigt wird.

let x: [string, number]; 
x = ["hello", 10]; // OK

// Initialize it incorrectly
x = [10, "hello"]; // Error

Als variadische Funktion bezeichnet man eine Funktion, deren Parameteranzahl nicht bereits in der Deklaration festgelegt ist. In TypeScript 4 können Tupeltypen für variadische Funktionen eingesetzt werden. Wie kann man sich diesen Mix jetzt nur genauer vorstellen? Betrachten wir hierbei Listing 2, das bereits ein gültiger TypeScript-Code ist, aber noch nicht optimal gestaltet ist.

Die concat-Funktion läuft ohne Probleme, aber wir verlieren hierbei die Typisierung und müssten das nachträglich manuell beheben, wenn wir an anderer Stelle genaue Werte erhalten möchten. Bisher war es unmöglich, eine solche Funktion vollständig zu Typisieren, um dieses Problem zu vermeiden.

function concat(
  numbers: number[],
  strings: string[]
): (string | number)[] {
  return [...numbers, ...strings];
}

let values = concat([1, 2], ["hi"]);
let value = values[1]; // infers string | number, but we *know* it's a number (2)

// TS does support accurate types for these values though:
let typedValues = concat([1, 2], ["hi"]) as [number, number, string];
let typedValue = typedValues[1] // => infers number, correctly

Listing 3 zeigt, dass wir mit der variadischen Deklaration der Tupeltypen die fehlende Typisierung gelöst bekommen. Sie erfolgt über die Generic-Deklaration, die an den spitzen Klammern zu erkennen ist. Wir können ein unbekanntes Tupel ([… T]) beschreiben oder diese verwenden, um teilweise bekannte Tupel ([string, … T, boolean, … U]) zu beschreiben. TypeScript kann hinterher die Typen für diese Platzhalter ableiten, sodass wir nur die Gesamtform des Tupels beschreiben und damit Code schreiben können, ohne von den spezifischen Details abhängig zu sein.

function concat<N extends number[], S extends string[]>(
  numbers: [...N],
  strings: [...S]
): [...N, ...S] {
  return [...numbers, ...strings];
}

let values = concat([1, 2], ["hi"]);
let value = values[1]; // => infers number
const val2 = values[1]; // => infers 2, not just any number

Beschriftete Tupelelemente

Das TypeScript-Team hat die Labeled Tuple Elements zur besseren Lesbarkeit eingeführt. Wenn wir einen Typen beschriften, müssen wir allen Typen ein Etikett geben, wie in Listing 4 zu sehen ist. Der wesentliche Vorteil ist nur beim Blick auf die Deklaration spürbar und erspart unnötige Codekommentare.

function foo(x: [first: string, second: number, ...rest: any[]]) {
  // a is string type
  // b is number type
  const [a, b] = x;
}

Implizite Typisierung von Eigenschaften

Eine besondere Stärke von TypeScript ist das implizierte Erkennen von Typen, das leider ebenfalls seine Grenzen hat. Wie zum Beispiel bei Funktionsparametern, die immer noch eine explizite Typisierung benötigen. Anders sieht es jetzt mit dem neuen Feature aus, der Property Type Inference. Wurde ein Wert für eine Klasseneigenschaft über den Konstruktor gesetzt, hat diese nicht automatisch den Typ erhalten. Hierbei musste man explizit den Typ dazu deklarieren. In Listing 5 sehen wir, dass jetzt TypeScript in der vierten Version vom Konstruktor aus eine implizite Typisierung der Eigenschaften durchführen kann.

class Foo {
  label; // Zeigt als Typ 'number | boolean' an.

  constructor(param: boolean) {
    if (param) {
      this.label = 123;
    } else {
      this.label = false;
    }
  }
}

Logische Zuweisungsoperatoren

TypeScript 4.0 hat bereits die logischen Zuweisungsoperatoren (Logical Assignment Operators) implementiert, die erst in ECMAScript 2021 als Standard veröffentlicht werden. Die Syntax kann auch rückwärts kompiliert werden, damit sie auch in älteren Browserumgebungen verwendet werden kann. In Listing 6 sind einige Beispiele aufgelistet. Heutzutage ist die letzte Option hier wahrscheinlich die nützlichste, es sei denn, wir behandeln ausschließlich Boolesche Werte. Diese Null-Koaleszenz-Zuweisung eignet sich perfekt für Standard- oder Fallback-Werte, bei denen a möglicherweise keinen Wert hat.

a ||= b
// Entspricht: a = a || b

a &&= b
// Entspricht: a = a && b

a ??= b
// Entspricht: a = a ?? b

Catch-Typ als unknown-Typ festlegen

Der unknown-Typ ist das typsichere Gegenstück zum any-Typ. Der Hauptunterschied zwischen den beiden Typen besteht darin, dass unknown weniger zulässig ist als any: Der unknown-Typ kann nur dem any-Typ und sich selbst zugewiesen werden. Dadurch sollen Anwender gezwungen sein, zurückgegebene Werte zu prüfen.

Bisher war der Fehlertyp aus einem catch-Block auf any begrenzt und konnte nicht direkt einem anderen Typen zugewiesen werden. Ab sofort können wir den Typ jedoch zur größeren Sicherheit auf den unknown-Typ ändern. Wir können ihn jedoch nicht direkt in einen anderen benutzerdefinierten Typ ändern. Das kann man nur im Funktionsblock vornehmen, wie Listing 7 zeigt.

function error() {
  try {
    throw new Error();
  } catch (e: unknown) {

    // Error
    // Object is of type unknown
    console.log(e.toUpperCase());
    
    if (typeof e === 'string') {
      // Das funktioniert jetzt
      console.log(e.toUpperCase());
    }
  }
}

@deprecated-Unterstützung

In JavaScript können wir über Codekommentare mit der @deprecated-Annotation aus JSDoc auf eine veraltete Implementierung hinweisen. Einige Codeeditoren können folglich bei der Autovervollständigung darauf hinweisen, dass die jeweilige Klasse oder Funktion möglichst nicht mehr zum Einsatz kommen soll. Ab sofort kann TypeScript beim Transpilieren ebenfalls darauf hinweisen und Codeeditoren wie Visual Studio Code weisen zusätzlich mit einer besseren Visualisierung darauf hin, wie in Abbildung 1 zu sehen ist. Mit TypeScript 4.1 wird gleichzeitig die @see-Annotation unterstützt.



Abb. 1: @deprecated-Unterstützung

Template Literal Types

Seit TypeScript 1.8 gibt es die Unterstützung von String Literal Types. Diese sind äußerst leistungsfähig, wenn wir streng typisierten APIs Sicherheit bieten möchten. Literaltypen wurden später erweitert, um auch numerische, boolesche und Enum-Literale zu unterstützen. Ein Beispiel zeigt Abbildung 2. Hier wird ein Beatles Type erzeugt, der nur die festgelegten Stringwerte unterstützt. In Zeile 4 wird ersichtlich, dass TypeScript darauf hinweist, dass dieser Stringwert nicht erlaubt ist. Einen weiteren Vorteil bietet die IntelliSense-Unterstützung, wie in Zeile 6 ersichtlich wird. Das sorgt für hervorragende Typsicherheit und Produktivität.



Abb. 2: String Literal Types

TypeScript 4.1 unterstützt jetzt Template Strings als Typen. Template Strings sind Stringsymbole, die über mehrere Zeilen gehende Zeichenketten sowie eingebettete JavaScript-Ausdrücke ermöglichen. Sie werden anstelle von doppelten bzw. einfachen Anführungszeichen von zwei Gravis-Akzentzeichen (`; französisch: Accent grave, englisch: Backtick) eingeschlossen. Sie können Platzhalter beinhalten, die durch das Dollarsymbol gefolgt von geschweiften Klammern gekennzeichnet sind (${expression}).

In Listing 8 wird der große Vorteil von Template Literal Types verdeutlicht. Aus zwei unterschiedlichen String Literal Types wird ein generischer Typ mit einem Template String erzeugt. Das erspart einiges an zusätzlichem Implementierungsaufwand und bietet zusätzlich eine hohe Flexibilität.

type Suit =
  "Hearts" | "Diamonds" |
  "Clubs" | "Spades";
type Rank =
  "Ace" | "Two" | "Three" | "Four" | "Five" |
  "Six" | "Seven" | "Eight" | "Nine" | "Ten" |
  "Jack" | "Queen" | "King";

type Card = `${Rank} of ${Suit}`;

const validCard: Card = "Three of Hearts";
const invalidCard: Card = "Three of Heart"; // Compiler Error

Eine erweiterte Form der Template Strings sind Tagged Template Strings. Mit ihnen kann die Ausgabe von Template Strings mit einer Funktion geändert werden. TypeScript 4.1 hat ebenfalls vier zusätzliche Tagged Helper implementiert: Capitalize, Uncapitalize, Uppercase und Lowercase. In Listing 9 sehen wir den Capitalize Tagged Helper im Einsatz.

interface Person {
  name: string;
  company: string;
  age: number;
}

// Erzeugt: "getName" | "getCompany" | "getAge"
type PersonAccessorNames = `get${Capitalize<keyof Person>}`;

Key Remapping in Mapped Types

Wir können auf dem Beispiel des Tagged Template Strings Helper aufbauen, indem wir es mit zugeordneten Typen kombinieren. Wenn wir im vorherigen Beispiel einen Typ für die Accessoren generieren möchten, können wir mit einem zugeordneten Typ beginnen (Listing 10). Durch keyof wird ein neuer Typ erstellt, wobei die ursprünglichen Datenelemente einer Funktion zugeordnet sind, die ihren Typ zurückgibt.

Mit der neuen as-Klausel können wir Funktionen wie Template Literal Types nutzen, um auf einfache Weise neue Eigenschaftsnamen basierend auf alten zu erstellen. Schlüssel können ebenfalls gefiltert werden.

interface Person {
  name: string;
  company: string;
  age: number;
}

type PersonAccessors = {
  [K in keyof Person as `get${Capitalize<K>}`]: () => Person[K];
}

/* PersonAccessors hat jetzt folgende Typ-Signatur:

{
  getName: () => string;
  getAge: () => number;
  getRegistered: () => boolean;
}

*/

Rekursive bedingte Typen

Ein weiterer Neuzugang in der aktuellen TypeScript Version 4.1 sind rekursive bedingte Typen. Rekursive bedingte Typen sind genau das, was der Name andeutet: conditional Types, die auf sich selbst verweisen. Sie bieten eine flexiblere Handhabung von bedingten Typen, indem sie sich innerhalb ihrer Zweige referenzieren können. Diese Funktion erleichtert das Schreiben rekursiver Aliasse. Listing 11 zeigt ein Beispiel für das Auspacken eines tief verschachtelten Promise mit Hilfe von async/await.

Es ist jedoch zu beachten, dass TypeScript mehr Zeit für die Typprüfung rekursiver Typen benötigt. Microsoft warnt, dass sie verantwortungsbewusst und sparsam eingesetzt werden sollten.

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

/// Wie `promise.then(...)`, aber genauer in Typen.
declare function customThen<T, U>(
  p: Promise<T>,
  onFulfilled: (value: Awaited<T>) => U
): Promise<Awaited<U>>;

Überprüfte indizierte Zugriffe

Mit Indexsignaturen in TypeScript 4.1 können wir auf beliebig benannte Eigenschaften zugreifen, wie in Listing 12 gezeigt wird. Hier sehen wir, dass eine Eigenschaft, auf die zugegriffen wird und die weder den Namenspfad noch die Namensberechtigungen hat, den Typ string oder number haben sollte.

Ein neues Transpiler Flag, —noUncheckedIndexedAccess, stellt einen Knoten bereit, auf dem jeder Eigenschaftszugriff (wie options.path) oder indizierter Zugriff (wie options [“foo bar baz”]) als potenziell undefiniert betrachtet wird. Das heißt, wenn wir im letzten Beispiel auf eine Eigenschaft wie options.path zugreifen müssen, müssen wir ihre Existenz überprüfen oder einen Nicht-Null-Assertion-Operator (das !-Zeichen als Postfix) verwenden.

Das —noUncheckedIndexedAccess Flag ist nützlich, um viele Fehler abzufangen, kann jedoch enorm viel Code verursachen. Aus diesem Grund wird es nicht automatisch durch das —strict Flag aktiviert.

interface Options {
  path: string;
  permissions: number;

  // Zusätzliche Eigenschaften werden von dieser Indexsignatur erfasst.
  [propertyName: string]: string | number;
}

function checkOptions(options: Options) {
  options.path; // string
  options.permissions; // number

  // Diese sind auch alle erlaubt!
  // Sie haben folgende Typen 'string | number'.
  options.yadda.toString();
  options["foo bar baz"].toString();
  options[Math.random()].toString();
}

Pfade ohne baseUrl

Vor TypeScript 4.1 musste der baseUrl-Parameter deklariert werden, um Pfade in der Datei tsconfig.json verwenden zu können. In der neuen Version ist es möglich, die Option path ohne baseUrl anzugeben. Das behebt das Problem, dass beim automatischen Import schlechte Pfade vorhanden sind.

checkJs impliziert jetzt allowJs

Wenn man ein JavaScript-Projekt hat und die checkJs-Option verwendet, um Fehler in .js-Dateien zu melden, sollte ebenfalls allowJs deklariert werden, um damit JavaScript-Dateien kompilieren zu können. Mit TypeScript 4.1 ist das nicht mehr der Fall. checkJs impliziert jetzt standardmäßig allowJs.

Fazit

Keines der gezeigten Sprachfeatures ist für sich allein genommen eine riesige Änderung, aber insgesamt wird so das Leben von TypeScript-Entwicklern verbessert, mit einigen großartigen Verbesserungen bezüglich der Typsicherheit und der Entwicklererfahrung insgesamt.

Aus der Community kommen sogar einige unterhaltsame Beispiele. Diese berechnen einen Typ, der die Lösung für ein Problem darstellt. Der Compiler wirft zwar einige Fehler, da zu viel Rekursion stattfindet, aber es sind eben nur Funprojekte zur Veranschaulichung der neuen Sprachfeatures.



Abb. 3: Der Sudoku Type Solver

Abbildung 3 zeigt einen Code, der einen Typ generiert, der ein Sudoku-Rätsel löst. In diesem Beispiel werden die String Literal Types “true” und “false” verwendet, um generische Typen zu erstellen, die als pseudobedingte Funktionen fungieren.



Abb.4: Der 12 Days of Christmas Type Solver

Abbildung 4 zeigt ein weiteres Spaßprojekt, das die Texte des klassischen Weihnachtsliedes „The 12 Days of Christmas“ generiert. Passend zum Artikel bietet der Autor einen Einstieg in JavaScript und TypeScript mit kostenlosen Videos auf YouTube. Viel Spaß beim Ansehen und mit TypeScript in der vierten Generation.

The post TypeScript 4.1: Was Entwickler:innen wissen müssen appeared first on BASTA!.

]]>
C# 9: Innovative neue Funktionen für die moderne Softwareentwicklung https://basta.net/blog/csharp-9-coole-neue-features/ Tue, 02 Mar 2021 09:27:17 +0000 https://basta.net/?p=81939 C# 9 bringt wieder jede Menge an Neuerungen mit. In seiner Session von der BASTA! Spring 2021 zeichnet Christian Nagel die spannendsten Features nach und zeigt, wie diese gewinnbringend in eigenen Anwendungen genutzt werden können.

The post C# 9: Innovative neue Funktionen für die moderne Softwareentwicklung appeared first on BASTA!.

]]>
C# 9 ermöglicht es, wieder weniger Code zu schreiben. Was steckt hinter Records? Welche Features von Records können auch außerhalb von Records eingesetzt werden? Pattern Matching geht in die dritte Generation. Was gibt es da Neues und was steckt dahinter? Wie unterstützen Libraries die neuen C#-Elemente? Das und weitere coole Features von C# werden in dieser Session gezeigt.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

The post C# 9: Innovative neue Funktionen für die moderne Softwareentwicklung appeared first on BASTA!.

]]>
.NET 5.0 ist da: Alles, was Sie über den neuen Release wissen müssen https://basta.net/blog/net-5-0-ist-erschienen/ Mon, 22 Feb 2021 09:53:09 +0000 https://basta.net/?p=81703 Am 10. November 2020 wurde .NET 5.0 von Microsoft veröffentlicht. Es ist noch nicht die angekündigte Vereinigung aller .NET-Varianten, aber es bringt viele schöne Neuerungen. Daher lohnt es sich, das finale Release genauer unter die Lupe zu nehmen.

The post .NET 5.0 ist da: Alles, was Sie über den neuen Release wissen müssen appeared first on BASTA!.

]]>
Der Windows Developer hatte bereits in Ausgabe 11.2020 ausführlich über die Previews 1 bis 7 von .NET 5.0 berichtet. Dieser Beitrag behandelt ergänzend dazu die Verbesserungen, die in Preview 8 sowie den beiden Release-Candidate-Versionen und der Endfassung erschienen sind. Eigentlich sollte die Preview-8-Version schon „feature complete“ sein; das hat bei Microsoft allerdings nicht geklappt, denn selbst in Release Candidate 2 wurden noch Neuerungen und Breaking Changes ausgeliefert.

Die Erkenntnisse aus Preview 1 bis 7 seien hier kurz zusammengefasst:

  • Technisch ist .NET 5.0 der Nachfolger von .NET Core 3.1. Der Begriff „Core“ entfällt und die Versionsnummer 4.0 wird übersprungen, um zu suggerieren, dass .NET 5.0 auch der Nachfolger von .NET Framework 4.8 ist – was aber nur Marketing ist.
  • Die ursprünglich für .NET 5.0 angekündigte Integration von Xamarin in .NET Core findet erst in .NET 6.0 statt.
  • .NET 5.0 bringt zahlreiche Leistungsverbesserungen im Just-in-Time-Compiler, im Garbage Collector und vielen Basisklassen.
  • .NET 5.0 unterstützt nun auch Windows auf ARM64-Prozessoren.
  • Der Target Framework Moniker (TFM) ist „net5.0“, ggf. gefolgt von der Zielplattform, z. B. „net5.0-windows“ (für Windows-Forms- und WPF-Anwendungen). Weitere Zielplattformen wie „-ios“ und „-android“ kommen aber erst mit der Integration von Xamarin in .NET 6.0.
  • .NET 5.0 enthält die Version 9.0 von C# mit der neuen Klassenart Record, der verkürzten Schreibweise new() zur Instanziierung, Erweiterungen bei Pattern Matching und Source Generators. Visual Basic .NET wird nicht mehr weiterentwickelt.
  • Für Windows Forms gibt es in .NET 5.0 ein neues Steuerelement TaskDialog.
  • Native Interoperabilität erlaubt den Aufruf von .NET-5.0-Code aus beliebigen Programmiersprachen.
  • Interoperabilität zu WinRT ist nicht mehr Teil der .NET-Laufzeitumgebung, sondern wird durch ein NuGet-Paket angeboten.

Der Rest dieses Beitrags beschäftigt sich mit Neuerungen, die Microsoft seit Preview 7 in .NET 5.0 eingeführt hat. Für .NET 5.0 benötigen Sie mindestens Visual Studio Version 2019 16.8, das zeitgleich mit .NET 5.0 erschienen ist.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Blazor 5.0

Die meisten Neuerungen kurz vor Schluss hat das SPA-Web-Framework ASP.NET Core Blazor erhalten. Seit Preview 7 ist Blazor 5.0 überhaupt erst in .NET 5.0 integriert, seit Preview 8 gibt es Neuerungen. Beide Blazor-Varianten, also Blazor Server und Blazor WebAssembly, tragen nun dieselbe Versionsnummer: 5.0. Blazor-WebAssembly-basierte Anwendungen melden sich in .NET 5.0 nun nicht mehr mit „Mono 6.13.0“, sondern mit „.NET 5.0.0“, wenn man die Eigenschaft System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription abfragt. Das liegt daran, dass Blazor WebAssembly jetzt die Klassenbibliotheken von .NET 5.0 verwendet. Die Runtime ist allerdings immer noch Mono, was man beim Kompilieren sieht: mono_wasm_runtime_ready. Die Angleichung der Klassenbibliotheken bedeutet aber nicht, dass nun alle .NET-Klassen in Blazor WebAssembly funktionieren: Weiterhin wirken die Einschränkungen der Browser-Sandbox und der WebAssembly VM. So sind zum Beispiel Dateisystemzugriffe, Multi-Threading, direkte Datenbankzugriffe und andere Netzwerkprotokolle als HTTP/HTTPS weiterhin nicht erlaubt.

Zur Leistungssteigerung von Blazor WebAssembly hatte Microsoft das Komponentenrendering, die JSON-Serialisierung und die Interoperabilität mit JavaScript in Preview 7 beschleunigt (Abb. 1). Neue Blazor-Funktionen gibt es erst seit .NET 5.0 Preview 8.

Abb. 1: Leistungsverbesserungen bei Blazor WebAssembly (Quelle: Microsoft)

 

Wichtigste Neuerung für Blazor WebAssembly 5.0 ist die in der ersten Version (mit Versionsnummer) schmerzlich vermisste Möglichkeit, Anwendungsteile nachzuladen. Lazy Loading von DLLs aus referenzierten Projekten ist nun möglich, wie Abbildung 2 zeigt. Das neu eingeführte Pre-Rendering bei Blazor WebAssembly lässt die erste Seite schneller erscheinen. Auch das Nachladen von JavaScript-Dateien bei Bedarf wird unterstützt. Zudem gibt es CSS-Isolation: Jede Razor Component kann eine eigene CSS-Datei (Komponentenname.razor.css) besitzen, und die dort enthaltenen Styles gelten nur für diese Komponente. JavaScript-Nachladen und CSS-Isolation funktionieren auch in Blazor Server.

Abb. 2: Lazy Loading einer DLL in Blazor WebAssembly 5.0

 

Für beide Blazor-Varianten gibt es neue Steuerelemente: <InputRadio> und <InputRadioGroup> sowie <InputFile> zum Dateiupload. Auch kann man nun den Fokus über ElementRef.FocusAsync() auf ein HTML-Element setzen; bisher brauchte man dazu JavaScript. Mit dem Zusatzpaket Microsoft.AspNetCore.Components.Web.Extensions kann man auch die Inhalte des <head>-Tag (z. B. Title, Link und Meta) beeinflussen. Die Schnittstelle IAsyncDisposable wird unterstützt, neu ist auch die Schnittstelle IComponentActivator. Das Paket Microsoft.AspNetCore.ProtectedBrowserStorage, das es schon seit August 2019 als experimentelles Paket gibt, soll offiziell einsatzreif werden (aber nur für Blazor Server). Blazor 5.0 bietet seit Release Candidate 1 eine Komponente <Virtualize>, die aus einer Objektmenge nur die sichtbaren Elemente rendert. Grundsätzlich kann der Entwickler beim Rendering von Blazor-WebAssembly-basierten Anwendungen nun wählen, ob eine statische HTML-Seite vorab erzeugt werden soll. Microsoft hat die Ausführungsgeschwindigkeit von Blazor WebAssembly insgesamt deutlich verbessert (vgl. Leistungsmessungen im Blogeintrag). Ebenso unterstützt Blazor nun das Browserereignis ontoggle für das Tag <details>. Die Klasse MouseEventArgs hat nun die Zusatzeigenschaften OffsetX und OffsetY.

Weitere Funktionen, die für Blazor 5.0 ursprünglich geplant waren, darunter AoT-Kompilierung, SVG-Unterstützung sowie Pflichtparameter in Komponenten, wurden leider gestrichen. Die AOT-Kompilierung soll in Blazor 6.0 kommen: „For .NET 6, we expect to ship support for ahead-of-time (AoT) compilation to WebAssembly, which should further improve performance.“

Entity Framework Core 5.0

Entity Framework Core hat kurz vor Schluss von Version 5.0 noch zwei seit der ersten Version vermisste und in der Vergangenheit (Version 3.x) schon angekündigte, aber dann verschobene Funktionen implementiert:

  • Version 5.0 des OR-Mappers erlaubt seit Preview 8 nun wieder das Table-per-Type-Mapping (TPT), mit dem jede Klasse in einer Vererbungshierarchie eine eigene Datenbanktabelle erhält. Bisher setzte Microsoft auf die Zusammenfassung einer ganzen Hierarchie in einer Tabelle (Table-per-Hierarchy, TPH).
  • Seit Release Candidate 1 gibt es wieder eine Abstraktion von N:M-Zwischentabellen. Bisher mussten Entwickler N:M-Beziehungen im Objektmodell wie im Datenbankmodell als zwei einzelne 1:N-Beziehungen modellieren. Nun kehrt die aus dem klassischen Entity Framework bekannte Abstraktion zurück, dass zwei Objekte direkt eine Many-to-Many-Beziehung besitzen dürfen und der OR-Mapper das transparent auf eine Zwischentabelle in der Datenbank abbildet.

 

Die TPT-Vererbung und die Abstraktion von N:M-Zwischentabellen erleichtern die Migration bestehender Anwendungen vom klassischen Entity Framework 6.x sehr.

Es gibt aber seit Preview 7 noch weitere neue Funktionen in Entity Framework Core 5.0. So ist es nun möglich, dass der OR-Mapper für eine einzige Entitätsklasse beim Lesen der Daten eine Sicht (View), beim Schreiben aber eine Tabelle nutzt. Eine einzelne .NET-Klasse kann jetzt durch Indexer Properties Basis für mehrere verschiedene Entitätstypen sein. Solche Shared Type Entities erlauben es, zur Laufzeit der Anwendung Tabellen aus der Datenbank abzubilden, die es zur Entwicklungszeit noch gar nicht gab. Shared Type Entities sind die Basis für die N:M-Abstraktion. Table-Valued-Functions können jetzt nicht nur wie bisher mit FromSqlRaw() bzw. FromSqlInterpolated() aufgerufen werden, sondern der Entwickler kann sie auch direkt auf .NET-Methoden abbilden. Die in Preview 6 neu eingeführten Split Queries können nicht nur pro Abfrage mit AsSplitQuery(), sondern auch zentral mit UseQuerySplittingBehavior() konfiguriert werden. Die vorher schon mögliche Abbildung von Typen auf beliebige SQL-Befehle (Defining Queries) ist mit ToSqlQuery() mächtiger geworden.

Neu ist auch, dass Tabellen mit ExcludeFromMigrations() aus den Schemamigrationen ausgeschlossen werden können. Schemamigrationen führt der OR-Mapper nun im Standard in Transaktionen aus. Per Kommandozeilenbefehl (dotnet ef migrations list) oder Commandlet (Get-Migration) kann man sich ausstehende Migrationen auflisten lassen. Für Schemamigration in SQLite-Datenbanken ist nun auch ein Sichern des Inhalts und Neuanlegen der Tabelle als Schemamigrationsstrategie möglich. Ebenso hat Microsoft in Entity Framework Core 5.0 noch drei Ereignisse ergänzt, die beim Speichern in der Kontextklasse ausgelöst werden: SavingChanges(), SavedChanges() und SaveChangesFailed(). Einfluss auf den Speichervorgang kann der Entwickler in einem sogenannten SaveChangesInterceptor nehmen. Die Größe der in einem Update-, Insert- oder Delete-Batch zur Datenbank gesendeten Befehle hat Microsoft nun im Standard auf 42 (sic!) gesetzt. Diese Zahl ist nicht willkürlich, sondern ist angeblich das gemessene Optimum. Zur Diagnose des Verhaltens des OR-Mappers kann der Betreiber einer Anwendung nun folgendermaßen Event Counter des OR-Mappers abrufen: dotnet counters monitor Microsoft.EntityFrameworkCore.

Click-Once-Deployment

Click-Once-Deployment ist ein seit Langem etabliertes und in der .NET-Welt sehr beliebtes Verfahren zur Verbreitung von .NET-Anwendungen. Es existiert seit Version 2.0 des klassischen .NET Frameworks. Es erlaubt die einfache Verteilung und automatische Aktualisierung von Windows-Anwendungen über Netzwerklaufwerke und Webserver. Die Installation einer Click-Once-basierten Anwendung ist für jeden Windows-Benutzer ohne Administratorrechte möglich und erfolgt im lokalen Benutzerverzeichnis. Das Click-Once-Deployment war nicht in .NET Core 1.0 bis 3.1 enthalten und nach der Microsoft-Ankündigung des Jahres 2019, dass die Übernahme von Funktionen aus dem klassischen .NET Framework nach .NET Core abgeschlossen sei, galt somit auch das Click-Once-Deployment als ausgestorbene Technik. Im Juli 2020 hatte Microsoft dann aber auf GitHub verkündet, dass das beliebte Verfahren in .NET 5.0 nun doch zurückkehren soll.

Click-Once-Deployment ist seit Oktober 2020 nun nicht nur für .NET 5.0, sondern auch für .NET Core 3.0 und 3.1 verfügbar. Ein Click-Once-Deployment-Paket kann der Entwickler über Visual Studio (ab Version 16.8 Preview 5) über Deploy | Folder | Click-Once (Abb. 3 und 4) oder das Kommandozeilenwerkzeug dotnet-mage für das .NET SDK CLI erstellen (Listing 1).

 
dotnet tool install -g Microsoft.DotNet.Mage
cd t:\NET5WPF\bin\Debug\net5.0-windows\
md files
mage.net -al NET5WPF.exe -td files  
mage.net -new application -t files\NET5WPF.manifest -fd files -v 1.0.0.1
mage.net -new Deployment -install true -pub "NET5WPF Publisher" -v 1.0.0.1 -Appmanifest files\NET5WPF.manifest -t NET5WPF.application

Gegenüber dem originalen Click-Once-Deployment in .NET Framework gibt es allerdings Einschränkungen. So sind Programmaktualisierungen nur beim Anwendungsstart, nicht jedoch beim Beenden oder durch Benutzeraktion im Betrieb möglich.

Abb. 3: Click-Once-Deployment für .NET Core 3.x und .NET 5 in Visual Studio ab Version 16.8 Preview 5

 

Abb. 4: Einstellungen für das Click-Once-Deployment für .NET Core 3.x und .NET 5 in Visual Studio ab Version 16.8 Preview 5

 

Support

Bei .NET 5.0 ist zu beachten, dass es sich dabei nicht um eine Long-Term-Support-Version handelt wie bei .NET Core 2.1 und .NET Core 3.1, sondern um eine Current-Version, für die es nur drei Monate nach dem Erscheinen der Folgeversion Support gibt. Microsoft hat bisher kein .NET 5.1 angekündigt, sondern nur für November 2021 ein .NET 6.0. Wenn das so bleibt, gibt es für .NET 5.0 Unterstützung bis zum Februar 2022. Auch .NET Core 3.0 war eine Current-Version; der Support endete schon im März 2020, da .NET Core 3.1 im Dezember 2019 erschien. Inzwischen weist Visual Studio die Entwickler in der Target-Framework-Auswahl eines Projekts auf die Supportrichtlinien hin (Abb. 5). Erst .NET 6.0 soll wieder einen Long-Term-Support mit drei Jahren Unterstützung erhalten. Jede gerade Version in ungeraden Jahren (also dann wieder .NET 8.0 im Jahr 2023) soll eine Long-Term-Support-Version werden. Die zehn Jahre Support, die es für das klassische .NET Framework gab, gibt es in der neuen agilen .NET-Welt leider nicht mehr.

Abb. 5: Visual Studio weist auf die Supportzyklen hin

 

Sonstige Neuerungen

Beim Application Trimming im Rahmen von dotnet publish hat Microsoft in .NET 5.0 den neuen Modus <TrimMode>Link</TrimMode> eingeführt. Im Gegensatz zum bisherigen <TrimMode>copyused</TrimMode> werden nicht nur ungenutzte Assemblies, sondern per Tree Shaking auch ungenutzte Typen und Mitglieder entfernt. Das Trimming schaltet man wie bisher mit <PublishTrimmed>true</PublishTrimmed> ein; copyused ist dann Standard. Das Trimming nimmt einige Zeit in Anspruch.

Zur Problemdiagnose können Entwickler in .NET 5.0 auch Dump-Dateien von .NET-Prozessen auf macOS erstellen und Linux-Dumps in Windows auswerten. Die Formatierung von Ausgaben des ConsoleFormatter ist jetzt anpassbar. Ein neuer JSON Console Logger liefert Ausgaben im JSON-Format. Mit dem neuen .NET-CLI-basierten Kommandozeilenwerkzeug dotnet-runtimeinfo kann man sich Basisinformationen über das Betriebssystem und die aktuelle .NET-Version liefern lassen.

Fazit und Ausblick

Microsoft hat kurz vor Schluss noch einige schöne Funktionen bei .NET 5.0 ergänzt. Aber es sind leider auch einige Punkte nicht fertig geworden und wurden auf .NET 6.0 verschoben. Sehr schmerzhaft ist das Fehlen der Ahead-of-Time-Kompilierung, insbesondere für Blazor WebAssembly. Dort wird also weiterhin .NET Intermediate Language in den Browser geladen und interpretiert. Das bedeutet, dass beim Anwendungsstart mehrere MB in den Browser gehievt werden müssen und die Ausführungsgeschwindigkeit weiterhin nicht an Web-Frameworks wie Angular heranreicht. .NET 5.0 ist nicht der große Wurf, der er ursprünglich sein sollte. .NET 5.0 ist eher ein „.NET Core 4.0“. Aber Microsoft signalisiert mit der Wahl der Versionsnummer 5.0 klar, dass die Ära von .NET Framework und .NET Core damit beendet ist.

Natürlich muss man nicht zwingend migrieren. Das .NET Framework 4.8 will Microsoft weiterhin mit Patches versorgen und „für immer“ mit Windows ausliefern. .NET Core 2.1 wird noch bis zum 21.08.2021, .NET Core 3.1 bis 03.12.2022 unterstützt. Aufgrund der Nähe von .NET Core 3.1 zu .NET 5.0 laufen einige Innovation aus .NET 5.0 auch auf .NET Core 3.1, z. B. C# 9.0 und das portierte Click-Once-Deployment.

Weiterführende Information zum Thema finden Sie am BASTA!-C#-Day


● C# 9 – what’s the cool stuff?

● C# Records Inside Out

The post .NET 5.0 ist da: Alles, was Sie über den neuen Release wissen müssen appeared first on BASTA!.

]]>
C# Source Generators: Wie Sie Ihre Entwicklungsprozesse mit automatisiertem Code beschleunigen https://basta.net/blog/c-source-generators/ Tue, 05 Jan 2021 12:30:20 +0000 https://basta.net/?p=81292 C# Source Generators dienen der automatischen Erstellung von Code, was zum Beispiel bei Routinearbeiten hilfreich sein kann, wenn Codeteile nach einem fixen Schema erstellt werden sollen. In diesem Artikel wird gezeigt, wie C# Source Generators eingesetzt werden können und was sie von bestehenden Optionen zur Codegenerierung unterscheidet.

The post C# Source Generators: Wie Sie Ihre Entwicklungsprozesse mit automatisiertem Code beschleunigen appeared first on BASTA!.

]]>
Ein wichtiger Trick, um beim Entwickeln von Code produktiver zu werden, ist, Routinetätigkeiten zu vermeiden. Code, den man ohne großes Nachdenken nach einem gewissen Schema schreiben kann, könnte man wahrscheinlich generieren, statt ihn manuell zu erstellen. In Visual Studio gibt es dafür ein Werkzeug, das recht weit verbreitet ist: T4 Templates. T4 Templates können von Visual Studio zur Entwicklungszeit ausgeführt werden. Alternativ kann man T4 in sein eigenes Programm einbetten und zur Laufzeit Code generieren (z. B. SQL Statements). Leider hat T4 jedoch erhebliche Nachteile. Erstens ist es eng an Visual Studio gebunden. Die Zeit, in der jede C#-Entwicklerin zum Schreiben von C# Visual Studio verwendet hat, ist vorbei. Manche bevorzugen Visual Studio Code oder IDEs von Drittanbietern, wie beispielsweise Rider. Zweitens ist T4 veraltet, kann nur mit Mühe in .NET-Core-Projekten eingesetzt werden und wird nicht mehr aktiv weiterentwickelt. Es muss also eine Alternative her.

Wie auch beim Compiler geht Microsoft im Bereich Codegenerierung weg von Lösungsansätzen, die an eine bestimmte IDE gebunden sind. Wenn Code zur Entwicklungs- oder Übersetzungszeit generiert werden soll, gibt es eigentlich nur einen Platz, an den eine solche Komponente gut hinpasst: Roslyn. Microsoft hat für Roslyn vor einigen Monaten die neue C#-Source-Generator-Funktion als Preview vorgestellt. Sie löst Werkzeuge wie T4 zum Generieren von Code zur Übersetzungszeit ab. Code- oder Textgenerierung zur Laufzeit ist kein Thema für die neuen Source Generators.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Routinearbeiten automatisieren

Bevor wir uns ansehen, wie C# Source Generators funktionieren, behandeln wir mögliche Einsatzszenarien. Das erste und offensichtlichste Einsatzszenario ist, Codeteile, die keine originäre Logik abbilden, sondern nach einem fixen Schema erstellt werden, automatisch zu generieren. Ein typisches Beispiel ist das in C# gut bekannte INotifyPropertyChanged-Interface. Es ist langweilig und zeitraubend, für eine C#-Klasse mit normalen Properties INotifyPropertyChanged zu implementieren. Man braucht dafür keine Kreativität, es ist einfach nur eine Menge Tipparbeit. Diese Aufgabe könnte ein Generator übernehmen. Ein weiteres Beispiel ist die Generierung von Modellklassen auf Basis von strukturierten Dateien wie zum Beispiel CSV, XML oder JSON. Viele Leserinnen und Leser haben sicherlich schon die diversen Internetseiten zum Generieren von C#-Klassen aus solchen Dateien genutzt, weil man sich die Arbeit des manuellen Erstellens des entsprechenden Codes sparen will. Auch in einem solchen Szenario könnte ein C# Source Generator das Codegenerieren übernehmen.

Erfahrene C#-Entwicklerinnen und -Entwickler könnten an dieser Stelle einwenden, dass man schon jetzt Code generieren kann, unter anderem mit den Klassen aus dem Namespace System.Reflection.Emit oder mit Hilfe von Expression Trees. Ideal sind solche Ansätze aber nicht. Erstens sind sowohl Reflection.Emit als auch Expression Trees komplex zu verwenden und zweitens erfolgt die Codegenerierung zur Laufzeit. Das beeinflusst die Performance negativ.

Dependency Injection und AoT Compilation

Neben dem Automatisieren von Routinetätigkeiten gibt es noch weitere Einsatzbereiche von Codegenerierung, die nicht so offensichtlich sind. Ein Beispiel ist Dependency Injection (kurz DI). Die DI-Magie von ASP.NET basiert heute zu großen Teilen auf Reflection. Controller werden zur Laufzeit über Reflection gesucht. Die Konstruktorparameter werden mit Hilfe von Reflection untersucht und die notwendigen Werte werden zur Laufzeit bereitgestellt. Funktional ist das einwandfrei. Es hat jedoch zwei wesentliche Nachteile: Erstens braucht der Einsatz von Reflection zur Laufzeit CPU-Zeit und verlangsamt daher den Start einer Webanwendung. Dieser Performancenachteil ist zwar nicht riesig, er macht sich aber speziell bei Cloud-Native-Lösungen bemerkbar, bei denen häufig Serverinstanzen neu starten (z. B. Cold Start bei Serverless Functions, Scale Out bei Autoscaling).

Zweitens erschwert Reflection das Optimieren des Codes durch einen Linker beim Ahead-of-Time-Übersetzen (kurz AOT). Der Compiler könnte beim Einsatz von AOT Teile des Programms, die nicht verwendet werden, entfernen und dadurch die Größe der Anwendung reduzieren. Wenn aber Reflection zum Einsatz kommt, tut sich der Compiler schwer, zu erkennen, was zur Laufzeit benötigt wird und was nicht. Durch Generierung von Source Code könnte man einen grundlegend neuen Ansatz für DI verfolgen. Die Abhängigkeiten könnten zur Übersetzungszeit analysiert und der sich ergebende Code könnte generiert werden. Reflection würde zur Laufzeit komplett entfallen und der Compiler könnte unnötige Codeteile problemlos entfernen. In anderen Programmiersprachen wird dieser Ansatz zum Teil bereits eingesetzt (vgl. Wire-Komponente von Google für Go).

Grundlagen

Das Entwickeln eines C# Source Generators hat Ähnlichkeit mit dem Programmieren eines Roslyn Analyzers. Wer daher einen Generator schreiben möchte, sollte mit dem Roslyn API vertraut sein. Aus Platzgründen enthält dieser Artikel keine Einleitung in die Programmierung mit Roslyn. In einem Artikel wäre das auch unmöglich zu schaffen, man bräuchte eine ganze Artikelserie. Wer noch nie mit Roslyn entwickelt hat, braucht aber keine Angst zu haben. Die wichtigsten Konzepte werde ich erklären und die Codebeispiele sind einfach gehalten. Bei der Planung einer eventuellen Generatorentwicklung sollte man aber nicht vergessen, Zeit für die Einarbeitung in die Roslyn-Grundlagen einzuplanen. Technisch gesehen ist ein Generator eine .NET Standard 2.0 Class Library, die zumindest die NuGet-Pakete Microsoft.CodeAnalysis.Analyzers und Microsoft.CodeAnalysis.CSharp referenziert. Zum Zeitpunkt des Schreibens dieses Artikels war die C#-Source-Generator-Funktion noch in der Preview-Phase. Man musste daher die Preview-Pakete verwenden, um Generatoren programmieren zu können.

In dem Assembly, in dem man Code durch den Generator erzeugen will, referenziert man die Generator Class Library wie einen Roslyn Analyzer. Wenn C# Source Generators einmal etabliert sein werden, wird man sich die Generatoren, die man möchte, so von NuGet holen, wie man das heute mit Roslyn Analyzers macht. Da wir in diesem Artikel hinter die Kulissen von Generatoren schauen wollen, gehe ich davon aus, dass wir eine Solution haben, in der sowohl der Generator als auch das Programm enthalten sind, in dem der Code generiert werden soll. In diesem Fall referenziert man den Analyzer mit einer Projektreferenz mit den Optionen OutputItemType=”Analyzer” und ReferenceOutputAssembly=”false“. Abbildung 1 zeigt die Projektkonfiguration eines Generators aus einem Beispielprojekt von mir.

Abb. 1: Projektkonfiguration für C# Source Generator

 

Der Roslyn-C#-Compiler erkennt, dass ein Source Generator referenziert wurde und führt ihn im Rahmen des Kompilierens der Anwendung automatisch aus. Visual Studio erkennt die Referenz auf den Generator ebenfalls und lässt ihn schon während des Editierens laufen. Dadurch erhält man in Visual Studio IntelliSense für den generierten Code. In der Praxis gibt es hier aber noch eine Menge Verbesserungsbedarf. Bei meinen bisherigen Versuchen mit der aktuellen Visual-Studio-2019-Preview-Version funktionierte IntelliSense in Verbindung mit den C# Source Generators nur bedingt. Auch für das Debuggen von Generatoren gibt es noch keine befriedigende Lösung. Das kann man zum Teil umgehen, indem man sich für die Logik des Generators eigene Unit-Tests schreibt und sie bei Bedarf mit dem Debugger startet. Alles in allem muss man sich aber auf einige Ecken und Kanten einstellen, wenn man schon jetzt in die Entwicklung von Generatoren einsteigt.

 

 

 

Entwicklung eines Generators

Der Generator selbst ist eine C#-Klasse, die das Interface ISourceGenerator implementiert und die mit dem Attribut Generator versehen ist. ISourceGenerator verlangt zwei zu implementierende Methoden:

  • Initialize: Diese Methode enthält üblicherweise dann Code, wenn der generierte Code auf bestehendem, selbst geschriebenem Code aufbaut. Ein Beispiel dafür wäre ein Generator, der INotifyPropertyChanged-kompatible Properties auf Basis bestehender, mit einem bestimmten Attribut versehener Properties erzeugt. In der Initialize-Methode kann man ein Visitor-Objekt vom Typ ISyntaxReceiver registrieren, das Roslyn für jede Syntax Node im bestehenden C#-Programm aufruft. Man kann die Syntax Node untersuchen und sich jene Knoten merken, die für die spätere Codegenerierung (siehe Execute-Methode) relevant sind. Wenn der generierte Code unabhängig vom restlichen C#-Code ist (z. B. Generieren einer C#-Modellklasse, die auf Basis einer CSV-Datei erzeugt wird), bleibt die Initialize-Methode leer.
  • Execute: In dieser Methode passiert das eigentliche Generieren des C#-Codes. Über das Roslyn API kann man bei Bedarf auf den Syntax Tree sowie das semantische Modell (z. B. Symbole mit den zugehörigen Typinformationen) des manuell geschriebenen Codes zugreifen. Auf die Ergebnisse der Codeanalyse in der oben erwähnten Initialize-Methode hat man ebenfalls Zugriff. Aber Achtung: Man bekommt nicht den generierten Code anderer Generatoren, die ebenfalls angewandt werden, und man muss damit rechnen, dass im C#-Code eventuell Fehler enthalten sind.

Lassen Sie uns zum Starten einen Blick auf ein „Hello World“-Beispiel werfen. Listing 1 enthält einen einfachen Generator, der eine statische Klasse HelloWorld mit einer statischen Methode SayHello generiert. Diese Methode gibt eine Liste aller .cs-Dateien des Projekts auf der Konsole aus. In diesem einfachen Beispiel lassen wir die Initialize-Methode leer. In Execute verwenden wir einen StringBuilder, um den generierten Code zusammenzubauen. Als Grundlage für das Codegenieren dienen uns die Roslyn Syntax Trees, auf die wir über GeneratorExecutionContext.Compilation.SyntaxTrees Zugriff haben. Im Programm, das den Generator referenziert, können wir den generierten Code mit HelloWorldGenerated.HelloWorld.SayHello() aufrufen und sehen dadurch die Liste der .cs-Dateien unseres Projekts auf dem Bildschirm.

 
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace SourceGeneratorSamples
{
  [Generator]
  public class HelloWorldGenerator : ISourceGenerator
  {
    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
      // Start building C# code
      var sourceBuilder = new StringBuilder(@"
using System;
namespace HelloWorldGenerated
{
  public static class HelloWorld
  {
    public static void SayHello() 
    {
      Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");

      // Get a list of syntax trees (.cs files)
      var syntaxTrees = context.Compilation.SyntaxTrees;

      // Add output of each file path for each syntax tree
      foreach (SyntaxTree tree in syntaxTrees)
      {
        sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");");
      }

      // Close generated code block
      sourceBuilder.Append(@"
    }
  }
}");

      // Inject the created source
      context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
    }
  }
}

Die Initialize-Methode

Jetzt, da wir die Grundlagen kennen, bauen wir einen Generator, der erstens auf bestehendem Code aufbaut und zweitens das semantische Modell des bestehenden Codes analysiert. Der Code, den ich in diesem Teil des Artikels zeige, ist Teil eines größeren Beispiels, das ich für .NET- und C#-Workshops und Trainings entwickelt habe. Interessierte Leserinnen und Leser finden es auf GitHub.

In diesem Beispiel geht es darum, Computerspieler für ein klassisches Schiffe-versenken-Spiel zu identifizieren. Man erkennt Computerspielerklassen daran, dass sie von der Basisklasse PlayerBase abgeleitet sind. Natürlich könnte man das Problem lösen, indem man zur Laufzeit über Reflection die entsprechenden Klassen heraussucht. Über die Probleme, die dieser Lösungsansatz mit sich bringt, habe ich oben schon geschrieben. In diesem Artikel möchten wir über einen C# Source Generator automatisch eine Datenstruktur erzeugen, in der alle Computerspieler referenziert sind. Reflection ist dadurch zur Laufzeit nicht mehr notwendig.

Listing 2 zeigt den ersten Teil unseres Generators. Im Mittelpunkt steht die zuvor schon erwähnte Initialize-Methode, die im „Hello World“-Beispiel noch leer war. Listing 2 zeigt, wie man in Initialize einen Syntax Receiver registriert. In unserem Syntax Receiver prüfen wir bei jeder Klassendeklaration, ob die Klasse eine Basisklasse hat. Die Klassen, bei denen das der Fall ist, sind potenziell zu berücksichtigende Computerspieler. Wir sammeln alle Kandidatenklassen in einer Liste, auf die wir später in Execute zurückgreifen werden.

 
namespace NBattleshipCodingContest.PlayersGenerator
{
  using Microsoft.CodeAnalysis;
  using Microsoft.CodeAnalysis.CSharp;
  using Microsoft.CodeAnalysis.CSharp.Syntax;
  using Microsoft.CodeAnalysis.Text;
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;

  /// <summary>
  /// Generates a static list of battleship players.
  /// </summary>


  /// <remarks>
  /// This generator makes runtime reflection for identifying battleship player no
  /// longer necessary. Leads to faster startup.
  /// </remarks></span>
  [Generator]
  public class PlayersGenerator : ISourceGenerator
  {
    /// <summary>
    /// Visitor class that finds possible players.
    /// </summary>

    internal class SyntaxReceiver : ISyntaxReceiver
    {
      public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();

      public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
      {
        // Players must derive from PlayerBase -> candidate classes must
        // have at least one base type.
        if (syntaxNode is ClassDeclarationSyntax cds && cds.BaseList != null && cds.BaseList.Types.Any())
        {
          CandidateClasses.Add(cds);
        }
      }
    }

    public void Initialize(GeneratorInitializationContext context) =>
      context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());

    ...
  }
}

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

Die Execute-Methode

Listing 3 enthält die Execute-Methode unseres Generators. Hier einige wichtige Hinweise zum Code:

  • Über GeneratorExecutionContext.SyntaxReceiver können wir auf unseren Syntax Receiver (siehe Listing 2) zugreifen und kommen dadurch zur Liste möglicher Computerspielerklassen.
  • Um zu überprüfen, ob eine Klasse die richtige Basisklasse hat, reicht der Blick auf den Syntax Tree nicht aus. Es könnte zum Beispiel sein, dass es auch in anderen Namespaces eine PlayerBase-Klasse gibt. Im Syntax Tree wäre nicht zu erkennen, ob wir es mit dem richtigen Typ zu tun haben. Aus diesem Grund sucht sich der Code in Listing 3 das entsprechende Symbol aus dem semantischen Modell von Roslyn mit Hilfe von GetTypeByMetadataName bzw. GetDeclaredSymbol.
  • Um den Code etwas interessanter zu gestalten, ermögliche ich es, Computerspieler über das Attribut Ignore auszuschließen. Dadurch zeigt Listing 3, wie man im Generator überprüfen kann, ob ein Attribut vorhanden ist. Diese Anforderung findet man meiner Erfahrung nach häufig bei Generatoren.
 
public void Execute(GeneratorExecutionContext context)
{
  if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.CandidateClasses == null)
  {
    throw new InvalidOperationException("Wrong syntax receiver or receiver never ran. Should never happen!");
  }

  // Begin creating the source we'll inject into the users compilation
  StringBuilder sourceBuilder = new(@"
namespace NBattleshipCodingContest.Players
{
using System;

public static class PlayerList
{
public static readonly PlayerInfo[] Players = new PlayerInfo[] 
{
");

  // Mandator base class for players
  var playerBaseSymbol = context.Compilation.GetTypeByMetadataName("NBattleshipCodingContest.Players.PlayerBase");
  if (playerBaseSymbol == null)
  {
    return;
  }

  // Attribute used to ignore specific players (e.g. players that crash or are not finished)
  var playerIgnoreSymbol = context.Compilation.GetTypeByMetadataName("NBattleshipCodingContest.Players.IgnoreAttribute");
  if (playerIgnoreSymbol == null)
  {
    return;
  }

  var playerClasses = new List<INamedTypeSymbol>();
  foreach (var f in receiver.CandidateClasses)
  {
    var model = context.Compilation.GetSemanticModel(f.SyntaxTree);
    if (model.GetDeclaredSymbol(f) is not INamedTypeSymbol ti)
    {
      continue;
    }

    // Check whether the class has the correct base class
    if (!ti.BaseType?.Equals(playerBaseSymbol, SymbolEqualityComparer.Default) ?? false)
    {
      continue;
    }

    // Check whether the class has no ignore attribute
    if (!ti.GetAttributes().Any(a => a.AttributeClass?.Equals(playerIgnoreSymbol, SymbolEqualityComparer.Default) ?? false))
    {
      // Found a player
      playerClasses.Add(ti);
    }
  }

  // Add all players to generated code
  sourceBuilder.Append(string.Join(",", playerClasses.Select(c =>
  {
    var displayString = c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
    return @$"new PlayerInfo(""{displayString}"", () => new {displayString}())";
  })));

  // finish creating the source to inject
  sourceBuilder.Append(@"
};
}
}");

 // inject the created source into the users compilation
  context.AddSource("PlayersGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}

Die Listings in diesem Artikel sind im Vergleich zum Code auf GitHub etwas vereinfacht. Ich habe hier die Unit-Tests weggelassen. Interessierte Leserinnen und Leser finden den Testcode im GitHub Repository.

 

 

 

Fazit

Aus meiner Sicht schließt Microsoft mit den C# Source Generators eine wichtige Lücke, die es in Roslyn und C# seit dem De-facto-Wegfall von T4 Templates gibt. Ich vermute, dass ich in meinen Projekten in Zukunft viele Anwendungsfälle für C# Source Generators finden werde. Im Lauf der Zeit erwarte ich mehr und mehr fertige Generatoren von Microsoft und von Drittanbietern und es wird seltener werden, dass man sich individuelle Generatoren selbst programmieren muss. Es ist außerdem wahrscheinlich, dass Microsoft Generatoren intern in Frameworks wie ASP.NET nutzen wird, um Reflection-basierenden Code zu reduzieren.

Jetzt ist die richtige Zeit, um mit den Generatoren zu experimentieren und sich mit dem API vertraut zu machen. Ein Nebeneffekt ist, dass man gezwungen ist, eventuelle Wissenslücken in Sachen Roslyn API zu schließen. Wer sich schon jetzt an eigene Generatoren heranwagt, der muss aber ein wenig Frust aushalten, da das Visual Studio Tooling noch stark verbesserungswürdig ist. Das ändert aber nichts an der Sache, dass C# Source Generators großes Potenzial für zukünftige Projekte haben.

The post C# Source Generators: Wie Sie Ihre Entwicklungsprozesse mit automatisiertem Code beschleunigen appeared first on BASTA!.

]]>
Container in der Azure Cloud: Best Practices und Strategien für die optimale Nutzung https://basta.net/blog/container-die-azure-edition/ Tue, 17 Nov 2020 13:56:44 +0000 https://basta.net/?p=80968 Viele Anwendungen bewegen sich in Richtung eines containerbasierten Ansatzes, und das aus gutem Grund: Container ermöglichen es uns, die Umgebungsanforderungen unserer Anwendung von der Anwendung selbst zu trennen.

The post Container in der Azure Cloud: Best Practices und Strategien für die optimale Nutzung appeared first on BASTA!.

]]>
Anstatt uns Gedanken darüber zu machen, wie wir unsere Anwendung so konfigurieren, dass sie auf verschiedenen Zielumgebungen läuft, können wir mit Hilfe von Containern explizit angeben, welche Art von Umgebung unsere Anwendung benötigt, und diese Umgebung dann automatisch für uns erstellen lassen. Von unserer lokalen Entwicklungsumgebung über das private Rechenzentrum unseres Unternehmens bis hin zu einem öffentlichen Cloud-Angebot wie Azure können Container uns dabei helfen, sicherzustellen, dass genau die gleiche Umgebung für das Hosting unserer Anwendung erstellt wird. Der Traum eines jeden Entwicklers! Wie aber sind wir hier gelandet? Dazu ist es hilfreich, uns zunächst anzuschauen, wie Softwareentwicklung vor dem Aufkommen von Containern aussah.

Bevor Container immer beliebter wurden, herrschten im standardmäßigen Workflow der Softwareentwicklung noch einige Probleme vor. Insbesondere die beiden nachfolgend beschriebenen waren ziemlich unbequem und erschwerten uns die Arbeit als Entwickler erheblich.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Fallstrick #1: Multiple Zielumgebungen

Der erste Fallstrick ist einer der ältesten Programmiererwitze überhaupt, vielleicht sogar älter als der über das Beenden von Vim: Das allzu häufige Problem von Anwendungen, die zwar auf unserer eigenen Maschine laufen, aber nirgendwo sonst. Als Anwendungen komplexer wurden und Entwickler Zugang zu mehr Entwicklungsumgebungen erhielten, stießen wir auf Probleme, wenn es darum ging, alles an einer Vielzahl von Orten gleichermaßen zum Laufen zu bringen. Bisweilen funktioniert eine Anwendung auf dem eigenen Rechner gut, vielleicht sogar auch bei QA und Staging – aber sobald sie in der Produktion eingesetzt wird, schlägt sie aus irgendeinem Grund fehl. Beim Debugging sieht man die Ursache in einer subtilen Abhängigkeit von der eigenen Produktionsumgebung. Vielleicht hat man eine Library-Version, z. B. Entity Framework, auf Version 6.3 aktualisiert, aber die entsprechende Abhängigkeit von .NET Core nicht auf 3.1 aktualisiert. Oder vielleicht wurde die falsche Konfigurationseinstellung angewendet, wodurch ein dev-Wert anstelle eines prod-Werts blieb. Diese Art subtiler Unterschiede in den Umgebungen bereitete Kopfschmerzen und führte nicht selten zu stundenlangem undurchsichtigem Debugging.

Ein weiteres häufiges Szenario: Neueinstellungen von Mitarbeitern. Ich weiß, dass einige Teams das inzwischen sehr gut machen, aber ich erinnere mich an die Tage, an denen neue Kollegen eingestellt wurden, und wie der Prozess der Einrichtung ihrer Entwicklungsumgebung eine ganze Woche dauern konnte. Manchmal erhalten sie den Rechner eines Vorgängers, und wenn wir Glück haben, bleiben die meisten Abhängigkeiten unserer Codebasis bestehen. Am wahrscheinlichsten ist jedoch, dass unsere neuen Kollegen einen völlig neuen oder komplett gelöschten Rechner erhalten und wir ihn von Grund auf neu einrichten müssen. Denken Sie jetzt an das letzte Mal, als Sie einem Kollegen helfen mussten, Ihre Codebasis von Grund auf neu einzurichten. Es sollte keine Überraschung sein, dass wir, nachdem wir uns mit unseren eigenen Umgebungen vertraut gemacht haben, alle gelernten Macken, zusätzlichen Schritte und die vollständige Liste der Abhängigkeiten vergessen, die erforderlich sind, um die Codebasis startklar zu machen. Diese Art von Szenarien war für Container wie geschaffen.

Fallstrick #2: Portabilität

In einer idealen Welt könnten sich alle Entwickler auf die Erstellung von Anwendungen für eine einzige Plattform konzentrieren. Die Realität sieht jedoch so aus, dass viele Anwendungen mehrere Plattformen und Geräte unterstützen müssen. Und nun, da Cloud-Anbieter immer häufiger als Hosts für unsere Anwendungen auftreten, sind Lift and Shifts in die Cloud auf die Portabilität dieser Anwendungen angewiesen. Vor der Einführung von Containern wurde das durch mühevoll erstellte Konfigurationsskripte, die Verwaltung vieler Umgebungsvariablen oder im schlimmsten Fall durch redundante und replizierte Codebasen erreicht, die auf eine bestimmte Plattform zugeschnitten waren. Zwar funktionierte das, aber es wurden viele Stunden damit verbracht, diese Kompatibilitäten für spezifische Umgebungen zu entwerfen und zu planen. Noch mehr Stunden wurden für die Fehlersuche bei Problemen aufgewendet, die durch diese unterschiedlichen Umgebungen verursacht wurden.

Was genau sind Container?

Es gibt viele Möglichkeiten, Container zu beschreiben, doch diese halte ich für die simpelste: „Ein Container ist ein unabhängiges Paket von Software und deren Abhängigkeiten.“ Im Zusammenhang mit der Softwareentwicklung erlaubt uns diese Definition, uns auf die praktische Anwendbarkeit von Containern zur Lösung der zuvor diskutierten Probleme zu konzentrieren. Schlüsseln wir diese Definition einmal auf: Was meinen wir, wenn wir von Abhängigkeiten sprechen? Das sind all die Dinge, die die Anwendung erfordert, um ihre eigene Umgebung zu bauen und darin zu laufen. Das beinhaltet natürlich unseren Quellcode, aber auch die spezifischen Laufzeiten und bestimmte Bibliotheksversionen sowie alle Konfigurationseinstellungen, die unsere Anwendung benötigt. Diese Abhängigkeiten werden dann zu einem unabhängigen Paket gebündelt. Das Paket ist portabel, vorhersehbar und zuverlässig, weil es alles enthält. Zu diesem Zeitpunkt kümmert es uns nicht und es spielt auch keine Rolle, wo dieses Paket eingesetzt wird; das Paket ist selbsterhaltend. Diese Option hat sich bei der Lösung der kniffligen Probleme, die wir besprochen haben, als effektiv erwiesen. Vorbei sind die Zeiten des umgebungsbezogenen Troubleshootings und Debuggings. Neue Kollegen haben nicht mehr eine lange Anlaufzeit, um ihre Entwicklungsumgebung korrekt einzurichten. Durch die Containerisierung der Anwendungen muss sich das Entwicklungsteam mit vielen der Problemszenarien nicht mehr herumplagen.

Container-Services in Azure

Falls Sie sich jetzt fragen, wie das Ganze zu bewerkstelligen ist, zeige ich Ihnen gerne, wie das geht – und zwar mit den Azure Container Services. Im Speziellen bietet Azure die folgenden vier an:

  • Azure Container Registry
  • Azure Container Instance
  • Azure Web App für Container
  • Azure Kubernetes Service

Wir werden nachfolgend besprechen, was jeder dieser Services ist bzw. nicht ist, und entsprechende Demos anschauen.

Azure Container Registry

Genauso, wie wir unseren Quellcode versionieren und verteilen können, können wir das mit Container-Images tun. Das sind die statischen Dateien, die als Snapshots für unsere eigentlichen Container dienen. Sie enthalten die Anweisungen und Abhängigkeiten, die man zum Bau eines Containers benötigt. Man kann sich Container-Images als Rezepte für einen Kuchen und Container als tatsächliche Instanzen dieses Kuchens vorstellen. Aber zurück zur Repo-Analogie: Azure Container Registries sind die Repos für Container-Images. Sie sind eine Möglichkeit, Container-Images mit anderen zu teilen und zu verteilen, insbesondere innerhalb eines Teams. Wer mit Docker Hub, der öffentlich zugänglichen Open-Source-Quelle für beliebte Basis- und offizielle Images, vertraut ist, kann sich Azure Container Registry in Analogie zu den privaten Repositories von Docker Hub vorstellen. Und genau das ist es, was eine Azure Container Registry ist: ein privater, verwalteter Registry-Dienst. Eine praktische Tatsache der Azure Container Registry ist, dass sie auf der Open-Source-Implementierung von Docker Registry 2.0 basiert. Das bedeutet, dass die Container Registry Docker-kompatible Images sowie grundsätzlich alle Images und Artefakte unterstützt, die der OCI-Spezifikation (Open Container Initiative) entsprechen.

Wie die meisten Azure-Dienste ist auch die Azure Container Registry (zum Zeitpunkt des Schreibens dieses Artikels) in drei Editionen verfügbar:

  • Basis
  • Standard
  • Premium

Basis: Die Basisedition ist die kostengünstigste und eignet sich am besten für persönliche Projekte und kleine Demos wie die, die später in diesem Artikel zu finden ist. Obwohl diese Option alle Kernfunktionen der Standard- und Premiumeditionen bietet, eignet sie sich mit ihren Speicher- und Durchsatzangeboten besser für Prototypen und den Einstieg in die Arbeit mit Containern.

Standard: Diese am häufigsten gewählte Edition dürfte für die meisten Teams passend sein. Sie bietet mehr Speicherplatz und einen höheren Durchsatz als die Basisedition und sollte für die meisten Produktionsszenarien ausreichen.

Premium: Es ist immer wieder interessant zu sehen, was die kostenintensivste Option von den anderen abhebt. Diese Edition ist am besten für Teams geeignet, die sich vollends Containern widmen. Sie ist auch diejenige, die man in Erwägung ziehen sollte, wenn man ein paar zusätzliche Features benötigt, um containerbasierte Lösungen zu unterstützen.

Zunächst einmal bietet die Premiumedition den höchsten Durchsatz, wodurch sie sich perfekt für Docker Pulls über mehrere simultane Nodes eignet. Wer schon ein paar Images gesehen hat, weiß, dass sie manchmal recht groß sein können, sodass zusätzliche Leistung für Szenarien mit hohem Datenaufkommen äußerst wertvoll ist. Darüber hinaus ist die Premiumedition die einzige Edition, die Georeplikation, Content Trust (für die Signierung von Image-Tags mit dem Content-Trust-Modell von Docker), Private Link (mit 10 privaten Endpoints) und vieles mehr unterstützt. Die vollständige Aufschlüsselung der verschiedenen Editionen der Azure Container Registry bietet weitere Informationen. Und schließlich können, so wie auch Repos das Ziel von Continuous-Integration-Workflows sind, Container-Registries das Ziel für Containerentwicklungsworkflows sein. Die meisten Softwareentwicklungsworkflows sind an diesem Punkt ziemlich standardisiert (oder nähern sich diesem Standard an): Entwickler genehmigen einige Änderungen in einem Pull Request, führen diese Änderungen in ihrem Hauptentwicklungszweig zusammen, triggern einen Build, führen einige Tests durch und automatisieren möglicherweise das Deployment, nachdem ein erfolgreicher Build ein Build-Artefakt ergeben hat. Ähnliche Aufgaben im Containerentwicklungsworkflow können mit Azure Container Registry Tasks automatisiert werden, auch bekannt als ACR Tasks. Zu den häufig automatisierten Aufgaben gehören das Rebuildung von Images, wenn sich die Base Images geändert haben, und das Patchen des Betriebssystems und der Frameworks auf Docker-Container.

Tutorial: Azure Container Registry via Azure CLI erstellen

Anstatt nur darüber zu reden, wie großartig Azure Container Registries sind, können wir nun eines erstellen – und das geht recht schnell. Achtung: Um dieser und den weiteren Demos zu folgen, werden ein Azure-Account und ein Azure-Abonnement sowie das auf den Computer heruntergeladene Azure CLI benötigt. Zudem werden etwas Vertrautheit mit Docker und einige Docker Images auf dem Computer vorausgesetzt. Die Schritte sind wie folgt:

1. Auf einem Terminal seiner Wahl in Azure einloggen; das sollte zu einer Weiterleitung in den Browser führen, wo das Einloggen und Authentifizieren mit Azure erforderlich sind:

 
az login

2. Eine Resource Group erstellen:

 az group create 
--location <REPLACE-WITH-LOCATION> 
--name <REPLACE-WITH-RESOURCE-GROUP-NAME> 
--subscription <REPLACE-WITH-YOUR-SUBSCRIPTION>

An dieser Stelle müssen die genannten Parameter mit den eigenen ersetzt werden. Beispielsweise sieht mein Befehl wie folgt aus:

  az group create 
--location westus2 
--name rg_westus2_registry 
--subscription adrienne-demos

Diese Resource Group wird unserer Container-Registry gewidmet. Das ist eine gute Praxis, die man im Auge behalten sollte, zumal es verbreitet ist, mehrere Ressourcen in einer Resource Group zu haben. Der Grund dafür, dass wir unsere Container-Registry in einer eigenen Resource Group halten wollen, ist, dass wir das versehentliche Löschen von gemeinsam genutzten Container-Images verhindern wollen. Wird die Container-Registry zusammen mit etwas weniger Permanentem gruppiert, z. B. einer Azure-Container-Instanz, ist es wahrscheinlicher, dass die Image-Sammlung beim Löschen der Containerinstanz unbeabsichtigt gelöscht wird. Das vergisst man leicht, also behalten wir die Container-Registry einfach in einer separaten Resource Group, um sicherzustellen, dass deren Löschung explizit erfolgt.

3. Zum Schluss erstellen wir die Container-Registry:

az acr create 
--resource-group <REPLACE-WITH-NEWLY-CREATED-RESOURCE-GROUP> 
--name <REPLACE-WITH-REGISTRY-NAME> 
--sku basic

Mein Befehl sieht beispielsweise so aus:

az acr create
--resource-group rg_westus2_registry
--name adrienneDemoRegistry
--sku basic

Wenn das erfolgreich war, erhalten wir ein großes Objekt als Ausgabe. Innerhalb dieses Objekts suchen wir den Key loginServer und merken uns dessen Wert. Das ist der vollqualifizierte Registry-Name, der in den späteren Tutorials benötigt wird. Mein vollqualifizierter Registry-Name ist beispielsweise adrienneDemoRegistry.azurecr.io. Glückwunsch, Sie haben soeben Ihre erste eigene Azure Container Registry erstellt! Jetzt sind wir bereit, Images zu erhalten.

Tutorial: Push eines Image zur Azure Container Registry

Bevor wir mit unserer Registry arbeiten können, müssen wir uns zunächst einloggen:

az acr login --name <REPLACE-WITH-YOUR-REGISTRY-NAME>

Auf meinem Computer habe ich bereits einige Docker Images, die ich verwenden möchte. Sollten keine auf dem eigenen Computer vorhanden sein, kann das aci-helloworld-Image heruntergeladen und für den Rest des Tutorials verwendet werden. Wenn man einige Images bezogen hat, können verfügbare Images durch Eingabe des folgenden Docker-Befehls aufgelistet werden:

docker images

Suchen wir nun nach dem Image, das wir verwenden möchten, und taggen es mit dem vollqualifizierten Namen unseres Azure-Container-Registry-Servers:

docker tag <REPLACE-WITH-NAME-OF-IMAGE> <YOUR-REGISTRY-NAME>.azurecr.io/<REPLACE-WITH-NAME-OF-IMAGE>:1

Zum Beispiel sieht mein Befehl so aus:

docker tag aci-hello-demo adrienneDemoRegistry.azurecr.io/aci-hello-demo:1

Es fällt auf, dass hinter dem Image-Namen :1 steht. Dabei handelt es sich um einen Stable Tag. Wir taggen das Image so, weil es die erste Major-Version dieses Image – die zufälligerweise ein Base Image ist – sein wird, das wir zur Registry pushen. Es ist eine gute Idee, Stable Tags für Images zu verwenden, die Updates und Patches erhalten sollen. Stable Tags sollten in Deployments allerdings vermieden werden.

Schließlich pushen wir das getaggte Image zu unserer Registry:

docker push <YOUR-REGISTRY-NAME>.azurecr.io/<YOUR-IMAGE-NAME>:1

Mein Befehl sieht beispielsweise so aus:

docker push adrienneDemoRegistry.azurecr.io/aci-hello-demo:1

Das Image sollte nun in unserer Registry sein. Wir können das mit dem folgenden az-list-Befehl prüfen:

az acr repository list --name <YOUR-REGISTRY-NAME> --output table

Wir haben nun verteilbare Images, aber wo verteilen wir sie? Und wo sind die Container? Hier kommen die anderen beiden Azure Container Services ins Spiel.

Azure Container Instance

Nachdem wir einen Ort zum Speichern und Verteilen unserer Images erstellt haben, gibt es verschiedene Möglichkeiten, sie zu Containern zu deployen. Die erste Option ist Azure Container Instance. Um etwas möglichst schnell zum Laufen bringen oder zu testen, oder wenn man zum ersten Mal mit Containern experimentiert, ist Azure Container Instance die erste Wahl in Azure. Da es vollkommen von Azure gemanagt ist, stellt es auch den einfachsten Weg dar, einen Container in Azure zu betreiben. Wenn einem also nichts an der unterliegenden Infrastruktur seiner Container liegt oder man sich nicht darum kümmern will, sind Azure Container Instances das Mittel der Wahl. Wie bei vielen anderen Rechenleistungen muss man auch hier nur für die Ressourcen zahlen, die man beim Ausführen einer Azure Container Instance verwendet. Wenn man das im Hinterkopf behält, ist es am besten, diese Container für kleinere Projekte und Single Applications zu verwenden. Anwendungen können in ihrem Leistungsverbrauch allerdings stark variieren. Um sich daran anzupassen, bietet Azure Container Instance die Möglichkeit, die exakten CPU-Kerne und den Speicher einzugeben, den man benötigt. Das ist entscheidend, wenn es um den Feinschliff der Ressourcenoptimierung und damit schließlich um die Kosteneinsparung geht.

Kommen wir nun zum praktischen Teil dieses Tutorials und beginnen wir mit dem Deployment eines Containers.

Tutorial: Eine Azure Container Instance erstellen und ein Image zu einem Container deployen

Zuerst werden wir eine weitere Resource Group erstellen, um die Containerinstanz zu beinhalten:

az group create --name <REPLACE-WITH-RESOURCE-GROUP-NAME> --location <REPLACE-WITH-LOCATION>

Mein Befehl sieht beispielsweise so aus:

az group create --name rg_westus2_aci --location westus2

Jetzt müssen wir nur noch einen Container erstellen und das gewünschte Image deployen. Wir können das in einem einzigen Befehl mit ein paar Parametern erledigen, wie in Listing 1 gezeigt wird.

 az group create 
--location <REPLACE-WITH-LOCATION> 
--name <REPLACE-WITH-RESOURCE-GROUP-NAME> 
--subscription <REPLACE-WITH-YOUR-SUBSCRIPTION>

An dieser Stelle stellt sich vielleicht einigen die Frage, woher man Registry-Nutzernamen und -Passwort bezieht. Da wir unser Image aus unserer eigenen privaten Azure Container Registry beziehen, werden wir einige Credentials im Befehl in Listing 1 angeben müssen, um Zugriff auf die Registry zu erhalten. Die Best Practice hierfür ist, ein neues Azure Active Directory Service Principal zu erstellen und mit pull-Rechten zu konfigurieren (Listing 2). Man kann auch einen bestehenden Service Principal mit zugewiesener acrpull-Rolle nutzen (Listing 3).

 # Source: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-aci#create-a-service-principal
#!/bin/bash

# Modify for your environment.
# ACR_NAME: The name of your Azure Container Registry
# SERVICE_PRINCIPAL_NAME: Must be unique within your AD tenant
ACR_NAME=<container-registry-name>
SERVICE_PRINCIPAL_NAME=acr-service-principal

# Obtain the full registry ID for subsequent command args
ACR_REGISTRY_ID=$(az acr show --name $ACR_NAME --query id --output tsv)

# Create the service principal with rights scoped to the registry.
# Default permissions are for docker pull access. Modify the '--role'
# argument value as desired:
# acrpull:     pull only
# acrpush:     push and pull
# owner:       push, pull, and assign roles
SP_PASSWD=$(az ad sp create-for-rbac --name http://$SERVICE_PRINCIPAL_NAME --scopes $ACR_REGISTRY_ID --role acrpull --query password --output tsv)
SP_APP_ID=$(az ad sp show --id http://$SERVICE_PRINCIPAL_NAME --query appId --output tsv)

# Output the service principal's credentials; use these in your services and
# applications to authenticate to the container registry.
echo "Service principal ID: $SP_APP_ID"
echo "Service principal password: $SP_PASSWD"
 # Source: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-aci#use-an-existing-service-principal
#!/bin/bash

# Modify for your environment. The ACR_NAME is the name of your Azure Container
# Registry, and the SERVICE_PRINCIPAL_ID is the service principal's 'appId' or
# one of its 'servicePrincipalNames' values.
ACR_NAME=mycontainerregistry
SERVICE_PRINCIPAL_ID=<service-principal-ID>

# Populate value required for subsequent command args
ACR_REGISTRY_ID=$(az acr show --name $ACR_NAME --query id --output tsv)

# Assign the desired role to the service principal. Modify the '--role' argument
# value as desired:
# acrpull:     pull only
# acrpush:     push and pull
# owner:       push, pull, and assign roles
az role assignment create --assignee $SERVICE_PRINCIPAL_ID --scope $ACR_REGISTRY_ID --role acrpull

Wenn wir den gewünschten Service Principal konfiguriert haben, notieren wir uns die Service Principal ID und das Service-Principal-Passwort, denn das werden die Werte sein, die wir in den Parametern —registry-username und —registry-password eintragen.

Der Befehl wird mit den eigenen Parametern vervollständigt und dann ausgeführt. Zum Beispiel sieht mein Befehl aus, wie in Listing 4 gezeigt.

 az container create 
  -g rg_westus2_aci
  --name aci-hello-adrienne
  --image adrienneDemoRegistry.azurecr.io/aci-hello-demo:1 
  --dns-name-label hello-adrienne
  --ports 80 
  --registry-username AdrienneServicePrincipalId 
  --registry-password AdrienneServicePrincipalPassword

Durch das Ausführen dieses Befehls erstellt Azure den Container, findet das spezifizierte Image und deployt es via einer Azure Container Instance zum Container. Warten wir einen Moment, und wenn wir ein weiteres großes Objekt als Ausgabe erhalten, wissen wir, dass der Befehl erfolgreich verlaufen ist. In diesem Objekt finden wir den fqdn-Schlüssel und kopieren dessen Wert. Wenn er in den Browser eingefügt wird, sollten wir die Anwendung betrachten können, die innerhalb eines Containers und somit innerhalb einer Azure Container Instance läuft.

Nach Beenden dieser Demo dürfen wir nicht vergessen, die Containerinstanz zu löschen. Aus diesem Grund war es praktisch, eine separate Resource Group zu erstellen. Wir können jetzt einfach die Containerinstanz löschen, aber die Container-Registry und somit auch Container-Images behalten. Das wird umso wichtiger, je mehr Images in der Container-Registry gespeichert oder mit anderen Teammitgliedern geteilt werden.

So löschen wir die Containerinstanz:

az group delete --name <YOUR-CONTAINER-RESOURCE-GROUP>

Azure Web App für Container

Für langlaufende Anwendungen oder zusätzliches Tooling ist Azure Web App für Container das Mittel der Wahl. Hier können wir die Azure-App-Service-Features nutzen, die möglicherweise schon bekannt sind, sowie auch die besser vorhersehbaren Preismodelle, die in App Services enthalten sind. Dinge wie Deployment Slots für Anwendungsupdates, integrierte Load Balancer, Unterstützung für Traffic-Manager zur Ermöglichung von Multi-Region Failover und vieles weitere werden von Azure Web Apps für Container unterstützt, nicht aber von Azure Container Instances.

Ein weiteres Szenario, für das Azure Web App für Container besser geeignet ist, sind Always-on-Umgebungen, z. B. dedizierte Dev- oder Testumgebungen. Das vorhersehbare Preismodell und flexible Skalierungsoptionen sind dafür geradezu prädestiniert.

Azure Kubernetes Service

Und schließlich gibt es noch Azure Kubernetes Service oder kurz AKS. Diese Option ist am besten für Teams geeignet, die sich ganz dem containerbasierten Ansatz verschrieben haben und sich bereits mit Kubernetes auskennen. Zum Betreiben multipler Container und wenn man einen Hands-off-Ansatz verfolgt, um Cluster zu managen, ist AKS eine großartige Option. Es nimmt dem Team viel an Komplexität und Overhead und übergibt diese stattdessen an Azure. Mühevolle Aufgaben wie Maintenance und Health Monitoring werden von Azure übernommen. AKS selbst ist kostenlos. Man hat einzig die finanzielle Verantwortung für die tatsächlichen Ressourcen, die der Cluster verbraucht. Unter der Haube sind die AKS Nodes in unserem Cluster lediglich Azure VMs, sodass Instanzen, Speicher und Netzwerkressourcen, die wir verwenden, in Rechnung gestellt werden. AKS ist eine solide Wahl, wenn man die flexibelste und zukunftsträchtigste gemanagte Containerumgebung nutzen möchte. Es ist als Kubernetes-konform CNCF-zertifiziert und folgt vielen allgemeinen regulatorischen Compliance-Anforderungen, darunter SOC, ISO, PCI DSS und HIPAA.

Die Qual der Wahl

Wenn Sie sich noch unsicher sind, welcher Azure Container Service für Sie der richtige ist, können Sie sich in Abbildung 1 einen Überblick über die Unterschiede verschaffen.

Abb. 1: Azure Container Services im Vergleich

 

Das war’s schon! Ich hoffe, dass ich Ihnen hiermit die Azure Container Services etwas näherbringen konnte.

The post Container in der Azure Cloud: Best Practices und Strategien für die optimale Nutzung appeared first on BASTA!.

]]>
Electron.NET von A bis Z: Cross-Plattform-Apps mit .NET und Electron entwickeln https://basta.net/blog/electron-net-von-a-bis-z/ Wed, 28 Oct 2020 07:47:18 +0000 https://basta.net/?p=80814 Das Open-Source-Framework Electron.NET feiert seinen dreijährigen Geburtstag und in der Zwischenzeit hat sich einiges getan. Fast 80 000 Downloads auf NuGet, fast 5 000 Sterne auf GitHub, über 400 bekannte Projekte – und sogar Microsoft nutzt es für die Desktoplösung von Blazor. Dieser Artikel geht auf die neuen Features ein und zeigt zusätzliche Tipps und Tricks, die häufig nachgefragt werden.

The post Electron.NET von A bis Z: Cross-Plattform-Apps mit .NET und Electron entwickeln appeared first on BASTA!.

]]>
Allgemeines zu Electron.NET

Das GitHub-Team hat 2013 das Electron Framework veröffentlicht, um mit JavaScript Cross-Platform-Desktopanwendungen bereitstellen zu können, die auf Windows, Mac und Linux laufen. Eine Vielzahl bekannter Anwendungen läuft auf diesem Framework, zum Beispiel Visual Studio Code, Microsoft Teams, Discord, Postman und viele weitere. Electron.NET nutzt unter der Haube das native Electron Framework, ermöglicht allerdings das Ausführen von .NET-Core-Anwendungen und stellt eine Brücke zum API mittels C# zur Verfügung. .NET-Entwickelnde bleiben damit in ihrem gewohnten Ökosystem und müssen sich nicht mit JavaScript beschäftigen. Ein kompletter Einstieg in Electron.NET wurde bereits im Windows Developer veröffentlicht und kann jetzt kostenlos online gelesen werden. Alternativ gibt es dazu passend eine Live-Stream-Aufzeichnung auf YouTube [3]. Dieser Artikel zeigt einige neue Features sowie wertvolle Tipps und Tricks.

Der Aufbau der Versionierung

Mit der Veröffentlichung von Electron.NET 5.22.12 haben wir eine neue Versionierung eingeführt. Die aktuelle Version lautet 9.31.2 (Stand 10.08.2020). Die erste Hauptversionsnummer mit der Zahl neun steht für die darunterliegende native Electron-Version. Die zweite Nebenversionsnummer mit der Zahl 31 steht für den Support von .NET Core 3.1. Die letzte Revisionsnummer enthält eigene Erweiterungen und Fehlerbehebungen. Diese wird automatisch auf eins zurückgesetzt, wenn eine neue native Electron-Hauptversion erscheint.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Der Splash-Screen-Support

Der Ladevorgang von Electron.NET benötigt etwas mehr Zeit, als man es von herkömmlichen Desktopanwendungen gewohnt ist. Ein Grund ist der lange Initialisierungsvorgang von ASP.NET Core. Electron selbst ist sehr schnell einsatzbereit, wobei Electron.NET die Wartezeit mit einem Begrüßungsbildschirm (Splash Screen) gefühlt verkürzen kann. Nebenbei bemerkt ist der Startvorgang von Electron.NET seit der Version 8.31.1 um bis zu ~25-36 Prozent schneller geworden – und wir arbeiten stetig daran, ihn noch weiter zu beschleunigen.

Für den Splash Screen benötigen wir eine Bilddatei. Der ideale Ort für die Bilddatei ist das wwwroot-Verzeichnis. Unterstützte Bildformate sind JPEG, WEBP, GIF, PNG, APNG, 2D Canvas, XBM, BMP und ICO. In der electron.manifest.json-Datei werden dann der Pfad und der Name der Bilddatei festgelegt (Listing 1). Electron.NET erkennt automatisch, dass ein Splash Screen hinterlegt wurde und zeigt diesen beim nächsten Start an. Die Splash-Screen-Größe wird automatisch der Bildgröße angepasst und kann aktuell nicht selbst verändert werden.

{
  "executable": "ElectronNetTest",
  "splashscreen": {
    "imageFile": "/wwwroot/img/my-splashscreen.jpg"
  },
  "singleInstance": false,
  ...
}

 

Sollte beim Startvorgang der ASP.NET-Core-Anwendung eine Exception ausgelöst werden, wird der Splash Screen nicht beendet und die Anwendung wird nicht geladen. Electron.NET erfährt jedoch nichts von dieser Exception und wartet darauf, bis die Anwendung fertig initialisiert wurde. Ist die Anwendung über das Terminal gestartet worden, hilft hierbei die Tastenkombination STRG + C, um sie manuell zu beenden. Alternativ klickt man auf den Splash Screen und beendet diesen mit der Tastenkombination ALT + F4. Eine eigene Lösung im Code kann diesem unschönen Verhalten entgegenwirken, wie Listing 2 zeigt.

 

try
{
  // ASP.NET Startup code...
  throw new Exception("No money!");
}
catch (Exception)
{
  Electron.App.Exit();
  throw;
}

 

Unterschiedliche Manifest-Dateien

Die electron.manifest.json-Datei ist ein wichtiger Bestandteil von Electron.NET. Sie beherbergt alle notwendigen Einstellungen, damit die beiden Welten .NET und das native Electron zusammenfinden. Zusätzlich konfiguriert man hier die Details einer App, wie zum Beispiel den Namen der Anwendung, die Herausgeberinformationen, die Version, den Splash Screen, das Anwendungsicon und vieles Weitere. Häufig soll eine Anwendung aus unterschiedlichen Konfigurationen erzeugt werden, z. B. eine Desktopanwendung mit einer Standard- und einer Professional-Edition. Dazu passend sollen auch die unterschiedlichen Splash Screens erscheinen.

Um eine weitere Manifest-Datei zu erzeugen, wird ebenfalls das Electron.NET CLI benötigt. Dazu geben wir in der Konsole innerhalb des Projektverzeichnisses folgenden Befehl ein:

electronize init /manifest test

In dem Projekt wurde jetzt neben der Standard-electron.manifest.json-Datei eine zusätzliche Datei namens electron.manifest.test.json angelegt. In Abbildung 1 wird gezeigt, dass diese beiden Dateien in Visual Studio eingebettet sind.

 

Abb. 1: Unterschiedliche Manifest-Dateien

 

Wenn wir jetzt in der neuen Manifest-Datei ein anderes Splash-Screen-Bild hinterlegen, wird dieses verwendet, wenn die Anwendung mit folgendem Befehl gestartet wird:

electronize start /manifest electron.manifest.test.json

Das Erzeugen der Desktopanwendung mit der neuen Datei wird dann über folgenden Befehl ermöglicht:

electronize build /target win /manifest electron.manifest.test.json

 

 

 

Eigenen TypeScript-Code ausführen

Der besondere Vorteil von Electron.NET ist, dass kein TypeScript/JavaScript-Code benötigt wird. Dennoch gibt es eine Vielzahl von npm-Paketen, die Electron oder das darunter laufende Node.js bereichern. Um diese ebenfalls in Electron.NET nutzen zu können, existiert das HostHook API. Tatsächlich war dieses Feature eins der gefragtesten in der Community.

HostHook aktivieren

Für den eigenen TypeScript/JavaScript-Code oder das zusätzliche Installieren von npm-Paketen wird ein eigenes isoliertes Webprojekt innerhalb der ASP.NET-Core-Anwendung benötigt. Über das Electron.NET CLI wird es durch electronize add hosthook mit einem eigenen Ordner namens ElectronHostHook angelegt.

Eigenen TypeScript-Code hinterlegen und ein npm-Paket installieren

Das ElectronHostHook-Verzeichnis kann jetzt mit einem Editor, wie zum Beispiel Visual Studio Code, geöffnet werden. Dieser bietet eine perfekte Unterstützung für die Webentwicklung von TypeScript. In einem kleinen Beispiel soll gezeigt werden, wie mittels TypeScript und Node.js eine Excel-Datei erzeugt wird. Für das Erzeugen von Excel-Dateien eignet sich die Node.js-Bibliothek ExcelJS. Sie wird installiert, indem wir per Terminal innerhalb des ElectronHostHook-Verzeichnisses npm install exceljs eingeben.

Wir legen dann eine neue TypeScript-Datei mit dem Namen excelCreator.ts an. Der Code aus Listing 3 erzeugt eine Excel-Datei mit einigen Beispieldaten.

import * as Excel from "exceljs";
import { Workbook, Worksheet } from "exceljs";

export class ExcelCreator {
  async create(path: string): Promise<string> {
    const workbook: Workbook = new Excel.Workbook();
    const worksheet: Worksheet = workbook.addWorksheet("My Sheet");
    worksheet.columns = [
      { header: "Id", key: "id", width: 10 },
      { header: "Name", key: "name", width: 32 },
      { header: "Birthday", key: "birthday", width: 10, outlineLevel: 1 }
    ];
    worksheet.addRow({ id: 1, name: "John Doe", birthday: new Date(1970, 1, 1) });
    worksheet.addRow({ id: 2, name: "Jane Doe", birthday: new Date(1965, 1, 7) });

    await workbook.xlsx.writeFile(path + "\\sample.xlsx");

    return "Excel file created!";
  }
}

 

Initialisierung und Kommunikation zum HostHook API von TypeScript

Im ElectronHostHook-Verzeichnis befindet sich die index.ts-Datei (Listing 4). Sie wird automatisch beim Starten des nativen Electron ausgeführt. Der Konstruktor stellt hierbei sogar die Verbindung zur Electron IPC (Inter-Process Communication) und Electron-App-Instanz zur Verfügung. Die Basisklasse Connector sorgt dafür, dass eine onHostReady()-Funktion aufgerufen wird, wenn der native Electron-Prozess einsatzbereit ist. Innerhalb dieser Funktion kann dann für die IPC TypeScript-/JavaScript-Code hinterlegt werden. In diesem Beispiel soll dann der Code zum Ausführen des eigenen ExcelCreator erfolgen. Ist die asynchrone Arbeit erledigt, wird das Ergebnis mittels der done-Callback-Funktion an die IPC zurückgeliefert.

// @ts-ignore
import * as Electron from "electron";
import { Connector } from "./connector";
import { ExcelCreator } from "./excelCreator";

export class HookService extends Connector {
  constructor(socket: SocketIO.Socket, public app: Electron.App) {
    super(socket, app);
  }

  onHostReady(): void {
    // execute your own JavaScript host logic here
    this.on("create-excel-file", async (path, done) => {
      const excelCreator: ExcelCreator = new ExcelCreator();
      const result: string = await excelCreator.create(path);

      done(result);
    });
  }
}

 

TypeScript mit C# und dem HostHook API ausführen

Über die statische Property Electron.HostHook kann in C# auf das HostHook API zugegriffen werden. Die CallAsync-Methode dient einer asynchronen Verarbeitung, bei der eine Antwort erwartet wird. Der Code aus Listing 5 zeigt, wie in C# der eigene TypeScript-/JavaScript-Code ausgelöst wird und wie wir an das Ergebnis gelangen.

var mainWindow = Electron.WindowManager.BrowserWindows.First();
var options = new OpenDialogOptions
{
  Properties = new OpenDialogProperty[] 
  {
    OpenDialogProperty.openDirectory
  }
};
var folderPath = await Electron.Dialog.ShowOpenDialogAsync(mainWindow, options);

var resultFromTypeScript = await Electron.HostHook.CallAsync<string>("create-excel-file", folderPath);
Electron.IpcMain.Send(mainWindow, "excel-file-created", resultFromTypeScript);

 

Parameter mit dem Command-Line-Support verarbeiten

Für das Setzen von Einstellungen in Desktopanwendungen gibt es die Möglichkeit, das Binary mittels Parametern zu starten. Seit Electron.NET 7.30.2 gibt es ebenfalls die Möglichkeit, mit Parametern zu arbeiten, und zwar mit dem Command-Line-Support. Bei einem meiner Kundeneinsätze gab es die Anforderung, dass sich die Chrome Developer Tools automatisch öffnen sollen, wenn die Anwendung mit dem folgenden Parameter gestartet wurde:

myapp.exe /args --debug=true

Die Chrome Developer Tools sind die Entwicklertools aus Chromium, die mit zahlreichen Funktionen zum Gestalten, Editieren, Testen, Analysieren und Korrigieren von Webanwendungen zur Verfügung stehen. In Listing 6 wird gezeigt, wie mit der statischen Property Electron.App.CommandLine auf die Parameter zugegriffen wird. Eine Überprüfung auf die Existenz eines Parameters wird mit der HasSwitchAsync-Methode durchgeführt. Anschließend erhalten wir den Wert mit der GetSwitchValueAsync-Methode. Zur Entwicklungszeit kann die App via electronize start /args –debug=true zusätzliche Parameter erhalten. In Abbildung 2 sehen wir die Desktopanwendung mit geöffneten Chrome Developer Tools.

Task.Run(async () => {
  var mainWindow = await Electron.WindowManager.CreateWindowAsync();

  if (await Electron.App.CommandLine.HasSwitchAsync("debug"))
  {
    string result = await Electron.App.CommandLine.GetSwitchValueAsync("debug");
    if (Convert.ToBoolean(result))
    {
      mainWindow.WebContents.OpenDevTools();
    }
  }
});

 

Abb. 2: Desktop-App startet mit den Chrome Developer Tools

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

Automatisch den Cache löschen

Ein natürliches Electron-Verhalten ist das Aufrechthalten des internen Browsercaches, wodurch ebenfalls einiges an Verarbeitungszeit eingespart wird. Das ist gerade zur Entwicklungszeit jedoch etwas unglücklich, da eigene Änderungen nicht ersichtlich werden. Sollte man über ein solches Problem stolpern, kann der Cache nun via Parameter automatisch gelöscht werden:

electronize start /clear-cache

Alternativ muss bei Electron oder in Kombination mit Electron.NET der folgende API-Befehl ausgeführt werden, der mit dem neuen clear-cache-Parameter erspart bleibt:

await browserWindow.WebContents.Session.ClearCacheAsync();

 

Ausbruch aus der Sandbox

Die Browser-Sandbox ist einer der Gründe, weshalb Webanwendungen so erfolgreich geworden sind und auch im Enterprise ein Zuhause gefunden haben. Allerdings ist sie auch eine Art Zwangsjacke für Entwickler, die in ihren Möglichkeiten stark beschränkt werden. Die Browser-Sandbox isoliert die Webanwendung vom eigentlichen Hostsystem, sodass auf keinen lokalen Prozess zugegriffen werden kann. Auch der Zugriff auf das Dateisystem ist nicht ohne Benutzeraktion ohne weiteres möglich. Weitere HTML5-Features wie die Standortermittlung via GPS, der Zugriff auf die Kamera und das Anzeigen von Benachrichtigungen sind ohne Benutzergenehmigung nicht möglich. Das ist für einige Businessanwendungen hinderlich, sodass auch keine Entscheidung für Progressive Web Apps gefällt werden kann. Das Besondere an Electron ist, dass Chromium die Option anbietet, die Sandbox zu deaktivieren. Wir können also wie ein Magier aus der Zwangsjacke ausbrechen.

Electron besteht aus unterschiedlichen Prozessen. Der Main-Prozess (Hauptprozess) ist eine Node.js-Instanz. Bei Electron.NET wird von Node.js unsere ASP.NET-Core-Anwendung gestartet und gemanagt. Im Prinzip haben wir hierbei keine Sandbox und können auf dem Hostsystem alles machen, was der angemeldete Benutzer ebenfalls darf. Sobald bei Electron ein sichtbares Fenster erzeugt und angezeigt wird, handelt es sich um einen getrennten Prozess namens Renderer-Prozess. Dieser ist die eigentliche Chromium-Instanz, die per Standard in der Sandbox aktiv ist. Soll jetzt eines der eben genannten HTML5-Features eingesetzt werden, hätten wir wieder unsere Einschränkungen. Deshalb wird beim Erzeugen eines neuen Fensters zusätzlich eine Option benötigt, um die Websecurity zu deaktivieren. In Listing 7 wird gezeigt, wie die notwendigen BrowserWindowOptions dazu aussehen. Selbstverständlich sollte die Sandbox auch nur dann deaktiviert werden, wenn wirklich ein Ausbruch aus der Sandbox vom Renderer-Prozess notwendig ist.

Task.Run(async () => {
  var options = new BrowserWindowOptions
  {
    WebPreferences = new WebPreferences
    {
      WebSecurity = false
    }
  };

  await Electron.WindowManager.CreateWindowAsync(options);
});

 

Das Beenden der App verhindern

In der Regel benötigt eine Desktopanwendung kein Fenster. Ein Screenshottool wie Snagit läuft beispielsweise im Hintergrund und wird erst durch globale Shortcuts aktiv. Das laufende Programm kann dann meist ebenfalls über das System Tray geöffnet werden.

Das Standardverhalten von Electron.NET ist, dass der Main-Prozess ohne Fenster im Hintergrund laufen kann, ohne dass sich das Programm gleich wieder beendet. Anders verhält es sich, wenn das erste Hauptfenster erzeugt wurde. Hier wird beim Beenden standardmäßig die gesamte Anwendung beendet. Um das Standardverhalten beeinflussen zu können, kann das Electron.App.WillQuit Event abonniert werden. Über die Event Arguments wird dann ein komplettes Beenden der Anwendung bei einem Aufruf der PreventDefault-Methode verhindert.

In Listing 8 wird ein Code gezeigt, der ein Hauptfenster öffnet. Wird dieses geschlossen, erhalten wir alle zehn Sekunden einen Abfragedialog, ob die Anwendung beendet werden soll. Bei der Auswahl von No wird der Beenden-Prozess gestoppt. Bei Yes wird sich die Anwendung komplett beenden.

Im Task Manager werden im Hintergrund die Prozesse mit einigen Electron-Instanzen sowie eine Instanz, die den gleichen Namen wie das eigene Programm trägt, angezeigt. Ist die Anwendung korrekt beendet worden, sind diese Instanzen nicht mehr ersichtlich. Es gibt Ausnahmefälle, die ein händisches Beenden erfordern, wenn das Standardverhalten automatisch beeinflusst wurde.

Task.Run(async () =>
{
  var mainWindow = await Electron.WindowManager.CreateWindowAsync();
  mainWindow.OnClose += () =>
  {
    var timer = new Timer();
    timer.Interval = 10000;
    timer.Elapsed += (s, e) => Electron.App.Quit();
    timer.Start();
  };

  Electron.App.WillQuit += async (e) => 
  {
    var options = new MessageBoxOptions("Exit the application?")
    {
      Type = MessageBoxType.question,
      Title = "Question",
      Buttons = new string[] { "Yes", "No" }
    };

    var result = await Electron.Dialog.ShowMessageBoxAsync(options);

    if (result.Response == 1)
    {
      e.PreventDefault();
    }
  };
});

 

Deployment

Das Erzeugen der nativen Binaries für die unterschiedlichen Plattformen erfolgt über das Electron.NET CLI mit dem Build-Befehl electronize build. Unter der Haube setzt Electron.NET auf das Open-Source-Projekt electron builder. Es erstellt nicht nur die notwendigen Binaries in beliebigen Formaten, sondern erzeugt ebenfalls die passende Installationsdatei gepackt als 7z, zip, tar.xz, tar.7z, tar.lz, tar.gz, tar.bz2 und dir (ungepacktes Verzeichnis) für alle Plattformen:

  • macOS: dmg, pkg, mas
  • Linux: AppImage, snap, Debian Package (deb), rpm, freebsd, pacman, p5p, apk
  • Windows: nsis (Installer), nsis-web (Web-Installer), portable (portable App ohne Installation), AppX (Windows Store), MSI, Squirrel.Windows

 

Ein weiteres interessantes Feature ist der integrierte Auto Update Manager, der es ermöglicht, die eigene App über einen Fileserver oder auch über GitHub-Releases zu aktualisieren. Ebenfalls kann hier automatisiert eine Codesignatur erfolgen.

Die offizielle Dokumentation von electron builder bietet eine Vielzahl von Konfigurationsmöglichkeiten. In der electron.manifest.json-Datei haben wir hierfür den Build-Bereich hinterlegt. Die dort enthaltenen Konfigurationen werden eins zu eins an den electron builder überreicht. Listing 9 zeigt die Standardkonfiguration, womit im bin\Desktop-Verzeichnis die fertige Desktopanwendung erzeugt wird. Darin wird per Standard die App ungepackt erzeugt, und für einen Windows-Build ein passender nsis Installer.

{
  "executable": "MyElectronNETApp",
  "splashscreen": {
    "imageFile": ""
  },
  "name": "MyElectronNETApp",
  "author": "",
  "singleInstance": false,
  "environment": "Production",
  "build": {
    "appId": "com.MyElectronNETApp.app",
    "productName": "MyElectronNETApp",
    "copyright": "Copyright © 2020",
    "buildVersion": "1.0.0",
    "compression": "maximum",
    "directories": {
      "output": "../../../bin/Desktop"
    },
    "extraResources": [
      {
        "from": "./bin",
        "to": "bin",
        "filter": [ "**/*" ]
      }
    ],
    "files": [
      {
        "from": "./ElectronHostHook/node_modules",
        "to": "ElectronHostHook/node_modules",
        "filter": [ "**/*" ]
      },
      "**/*"
    ]
  }
}

 

 

 

Fazit

Dieser Artikel hat nur einige der neuen Features vorgestellt und kleine Tipps gegeben, die häufig nachgefragt wurden. Im Detail hat die wachsende Community für zahlreiche API-Implementationen gesorgt, sodass noch mehr vom nativen Electron innerhalb unserer .NET-Welt zur Verfügung steht. Wir arbeiten weiterhin intensiv an einer noch schlankeren und performanteren Lösung. Dazu wird sich das Backend technisch noch so weit verändern, dass eine API-Kommunikation über WebSockets hinfällig wird. Ein großes Ziel meinerseits ist es, dass diese Optimierungen ohne Breaking Changes auskommen werden.

Das Open-Source-Projekt sucht natürlich weiterhin nach Verstärkung. Wenn Interesse besteht, an Electron.NET etwas zu verändern, zeigt unser YouTube-Video „Electron.NET – Contributing Getting Started“, wie einfach für Electron.NET entwickelt werden kann. Bei weiteren Fragen steht die Community via Chat auf Gitter rund um die Uhr zur Verfügung. Natürlich darf man auch den Autor jederzeit per E-Mail kontaktieren.

The post Electron.NET von A bis Z: Cross-Plattform-Apps mit .NET und Electron entwickeln appeared first on BASTA!.

]]>
C# 9 and beyond: Die Zukunft der Programmiersprache und ihre neuen Möglichkeiten https://basta.net/blog/c-9-beyond-wohin-geht-die-reise/ Wed, 07 Oct 2020 07:22:12 +0000 https://basta.net/?p=80612 Vor 20 Jahren gab es die erste Version von C#. Seitdem hat sich viel getan! Wie wurde die populäre Programmiersprache vor 20 Jahren verwendet, welche Innovationen gibt es heute?

The post C# 9 and beyond: Die Zukunft der Programmiersprache und ihre neuen Möglichkeiten appeared first on BASTA!.

]]>
In seiner Keynote von der BASTA! 2020 zeichnet Softwarearchitekt und Microsoft MVP Christian Nagel (CN innovation) den gewundenen Entwicklungsweg von C# nach – von einer Windows-spezifischen Plattform mit Windows- und Web-Applikationen hin zum Multi-Platform-Paradigma, Open Source, mobilen Devices und Cloud-Umgebungen. Wie hat sich die Syntax von C# geändert, welche neuen Anwendungsszenarien wurden erschlossen, was steht uns mit C# 9 bevor und wie geht es danach weiter?

Weitere interessante Beiträge


● Go, der C#-Killer?

● NET Core 3.1 ist reif

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

The post C# 9 and beyond: Die Zukunft der Programmiersprache und ihre neuen Möglichkeiten appeared first on BASTA!.

]]>
Visualisierung von Machine Learning-Daten in VS Code https://basta.net/blog/visualisieren-von-machine-learning-daten-in-vs-code/ Tue, 01 Sep 2020 09:42:13 +0000 https://basta.net/?p=80448 Visualisierungen im Code haben in den letzten Jahren erfolgversprechende Innovationen hervorgebracht. Hierzu zählen die durchgehende Integration einiger Spezialtechnologien des Machine-Learning-Mappings wie die Einbindung des Jupyter-Notebook-Formats in VS Code, MS Power BI sowie das Aufrufen von Tensorboard zur Darstellung und Protokollierung der Trainingsergebnisse. Dieser Artikel verdeutlicht, wie weit die Codevisualisierung vorangeschritten ist und wie eigene Projekte davon profitieren.

The post Visualisierung von Machine Learning-Daten in VS Code appeared first on BASTA!.

]]>
Ein unmittelbarer Nutzen von Codevisualisierungen ist bereits heute klar: Bereiche wie die Robotik, Expertensysteme, mathematische Optimierung, Anomaliedetektion, Merkmalsreduktion oder modellbasierte Regelung wären besser erklärbar, wenn das Modell die gefundenen Merkmale zur Entscheidung direkt durch eine entsprechende Grafik aufzeigen könnte. Dazu dienen die in diesem Artikel beschriebenen Grundlagen.

Das Ziel ist es, so genau wie möglich zu verstehen, warum und wie eine KI bestimmte Entscheidungen trifft. Bei Bilderkennungsalgorithmen zeigt beispielsweise eine farbige Heatmap die Stellen eines Bildes, die besonders relevant für dessen Klassifizierung sind. Wir beginnen mit einem einfachen Datenset eines Klassifikationssystems und visualisieren die Entscheidung der Klassifikation mit einer Konfusionsmatrix und zugehöriger Heatmap. Als IDE nutzen wir Visual Studio Code mit den beiden Konfigurationsfiles tasks.json und dem projektspezifischen settings.json inklusive Test-Units und Pfadangaben (Listing 1 und 2).

{
  "python.pythonPath": 
"C:\\Users\\Max\\AppData\\Local\\Programs\\Python\\Python37\\python.exe",
  "python.testing.pytestArgs": [
    "freshonion"
  ],
  "python.testing.unittestEnabled": false,
  "python.testing.nosetestsEnabled": false,
  "python.testing.pytestEnabled": false,
  "python.testing.unittestArgs": [
    "-v",
    "-s",
    "./freshonion",
    "-p",
    "*test.py"
  ],
  "python.testing.promptToConfigure": false
}

 

{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  // build from older win8.1. to win10.2 by max
  "version": "2.0.0",
  "tasks": [
    {
      "label": "buildpython",
      "type": "shell",
      "command": "C:\\Users\\Max\\AppData\\Local\\Programs\\Python\\Python37\\python.exe",
      "args": ["${file}"],
      "showOutput":"always",
      "problemMatcher": [],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ]
}
Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Als Einstieg in VS Code mit Python empfiehlt sich das Tutorial, das Microsoft mit der aktuellen Version aus dem März 2020 (Version 1.44) publiziert hat: „Tutorials for creating Python containers and building Data Science models“. Wir starten mit den importierten Modulen in Listing 3 und nennen unser Skript logregclassifier2.py.

// get the modules we need
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix

 

Das Datenset

Die Daten unseres Beispiels sind bewusst neutral und einfach gehalten, um eine optimale Verständlichkeit zu gewährleisten. Als Trainingsdaten dient eine Datenreihe von 0 bis 9 (Samples), um ein Target mit 0 und 1 zu klassifizieren. Die Reihe könnte etwa zehn Patienten repräsentieren, die einen medizinischen Test absolvieren. Bei im Voraus bekannten Labels (Target) spricht man auch von Supervised Learning. Wir wollen nun das System soweit trainieren, dass die tiefen Zahlen wahrscheinlich mit 0 und die hohen Zahlen wahrscheinlich mit 1 zu klassifizieren sind (Listing 4).

X=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
y=[0, 0, 0, 0, 1, 1, 1, 1, 1, 1]

 

// arrays for the input (X) and output (y) values:
X = np.arange(10).reshape(-1, 1)
y = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])

Wir können mit dem Befehl np.arange(10) ein Array erstellen, das alle ganzen Zahlen von 0 bis 9 enthält. Als Konvention betrachten wir x als zweidimensionales Array (Matrix) und y als eindimensionales Target (Vektor). Reshape (-1,1) bedeutet, dass wir nur ein Feature als Kolonne haben. Features sind die Merkmalsträger, die dem Modell helfen, unbekannte Muster zu finden. Anschließend definieren wir das Modell, das bereits mit der Fit-Methode die Daten trainiert, um eine Beziehung zwischen den Einflussvariablen (Determinanten) und dem Target herzustellen (Listing 5).

// Once you have input and output prepared, define your classification model.
model= LogisticRegression(solver='liblinear', random_state=0)
model.fit(X, y)
print(model)

Nachdem das Modell aufgebaut ist, können wir mit predict() eine erste Klassifikation mit einem Score versuchen und gleich die Konfusionsmatrix erstellen (Listing 6).

 

print(model.predict(X))
print(model.score(X, y))
// One false positive prediction: The fourth observation is a zero that was wrongly predicted as one.
print(confusion_matrix(y, model.predict(X)))

 

Real:    [0 0 0 0 1 1 1 1 1 1]
Predict: [0 0 0 1 1 1 1 1 1 1]
Score: 0.9
Confusion Matrix:
0: [[3 1]  :4
1:  [0 6]] :6

 

Und siehe da: Ein False Positive (Fehlalarm) hat sich eingeschlichen. Das Modell hat eine 0 irrtümlich als 1 klassifiziert, vergleichbar mit einem System, das fälschlicherweise eine ruhige Situation als Brandalarm bewertet. Die Konfusionsmatrix zeigt das als Fehlalarm an. Idealerweise zeigt die Matrix bei einem 100 Prozent Score folgendes Bild:

0: [[4 0]  :4
1:  [0 6]] :6

Das Datenset wird zum Bild

Im nächsten Schritt erfolgt die visuelle Aufbereitung der Matrix, um eine optische Beziehung zwischen den realen und den vorausgesagten Daten herzustellen (Listing 7).

 

plt.rcParams.update({'font.size': 16})
fig, ax = plt.subplots(figsize=(4, 4))
ax.imshow(cm)
ax.grid(False)
ax.xaxis.set(ticks=(0, 1), ticklabels=('Predicted 0s', 'Predicted 1s'))
ax.yaxis.set(ticks=(0, 1), ticklabels=('Actual 0s', 'Actual 1s'))
ax.set_ylim(1.5, -0.5)
for i in range(2):
  for j in range(2):
    ax.text(j, i, cm[i, j], ha='center', va='center', color='red')
plt.show()

 

Abb. 1: Konfusionsmatrix mit Pyplot

 

Die Grafik in Abbildung 1 lässt sich auch einfacher und moderner mit einer zusätzlichen Bibliothek erzeugen. Wir benötigen dafür die Python Library seaborn (Abb. 2), die sich am besten über die integrierte Command Line Shell direkt in VS Code mit Pip Install installieren lässt (Listing 8).

 

import seaborn as sns
// get the instance of confusion_matrix:
cm = confusion_matrix(y, model.predict(X))
sns.heatmap(cm,  annot=True)
plt.title('heatmap confusion matrix')
plt.show()

 

Abb. 2: Konfusionsmatrix mit Seaborn

 

Die Klasse 0 hat also drei richtige Fälle (True Negatives), die Klasse 1 sechs richtige Fälle (True Positives) erkannt. Die Nutzergenauigkeit zeigt ebenfalls ein einziges falsches positives Ergebnis. Die Nutzergenauigkeit (Consumer Risk versus Producer Risk) wird auch als Überlassungsfehler oder Fehler vom Typ 1 bezeichnet. Fehler vom Typ 2 sind dann False Negatives.

Mit der Funktion .heatmap() aus der Bibliothek seaborn wird der Diagrammtyp definiert. Die folgenden Argumente parametrisieren das Aussehen des Diagramms. Werfen wir einen Blick in die Analyse des Fehlers, der durch den voreingestellten Schwellenwert der Wahrscheinlichkeit bei 0.5 definiert ist. Die Diskriminierung zwischen 0 und 1 hat zu früh stattgefunden, sodass unser Modell eine 0 zu früh als 1 klassifiziert hat. Natürlich lassen sich diese sogenannten Hyperparameter optimieren, um eine gerechtere Verteilung der Klassifikation zu finden. Dazu muss man sagen, dass die Wirkung auf diskrete, dichotome Variablen [0,1] sich nicht mit dem Verfahren der klassischen linearen Regressionsanalyse erklären und verifizieren lässt.

Hyperparameter

Die momentane Verteilung mit der zugehörigen Klassifikation sieht wie in Abb. 3 gezeigt aus.

 

Abb. 3: Die ersten drei Samples werden als 0, die restlichen als 1 gewertet

 

sns.set(style = 'whitegrid')
sns.regplot(X, model.predict_proba(X)[:,1], logistic=True,
                     scatter_kws={"color": "red"}, line_kws={"color": "blue"}) #label=model.predict(X))
plt.title('Logistic Probability Plot')
plt.show()

 

In Listing 9 ist in der Funktion regplot die geschätzte Wahrscheinlichkeit als Target enthalten. Nicht jeder Klassifizierer bietet die internen Wahrscheinlichkeiten an. Auch der Naive-Bayes-Klassifikator ist probabilistisch (Kasten: „Naive-Bayes-Klassifikator“). Die entsprechende Entscheidungsgrenze (Decision Boundary) ist für die Analyse optisch erkennbar und hilft, das Ergebnis zu interpretieren oder einen besseren Solver zu finden (Abb. 4).

 

Naive-Bayes-Klassifikator

Der Naive-Bayes-Klassifikator ist nach dem englischen Mathematiker Thomas Bayes benannt und leitet sich aus dem Bayes-Theorem ab. Die grundlegende Annahme besteht darin, von einer strengen Unabhängigkeit der verwendeten Merkmale auszugehen (deshalb „Naive“).

 

Abb. 4: Decision Boundary mit dem False Positive (blauer Punkt auf weißer Fläche)

 

     T precision    recall       f1-score    support      CM      
0 1.00 0.75 0.86 4 [[3 1
1 0.86 1.00 0.92 6 [0 6]]

Tabelle 1: Classification Report

Der Tabelle 1 können wir entnehmen, dass es einen Fall von Falsch-Positiv und keinen Fall von Falsch-Negativ gibt. Das bedeutet, dass nur in 86 Prozent aller Fälle ein positives Ergebnis auch einer Erkrankung entspricht. Die Precision errechnet sich dabei so:

Truepositiv / (Truepositiv + Falsepositiv) =
6 / (6+1) = 0.8571 = <strong>0.86</strong>

Es ist also entscheidend, in die Genauigkeit der Tests (Screening) auch die Falsch-Positiv-Fälle mit einzubinden. Ähnliche Beispiele zur bedingten Wahrscheinlichkeit befinden sich übrigens auf der Webseite „Lügen mit Statistik“. Auch hier habe ich einmal einen Fall nachgerechnet und visualisiert (aus dem Gebiet der Mammografie). Wie in Abbildung 5 gezeigt, sehen die Falsch-Positiv-Werte schon komplexer aus.

 

Abb. 5: Nichtlineare Analyse von Falsch-Positiv in einem Hyperplane (Support Vector Machine)

 

Optimieren der Optik

Nun wollen wir die erwähnten Hyperparameter ins Spiel bringen, von denen es einige gibt und die einen Teil der Modellevaluation ausmachen.

  • C ist eine positive Gleitkommazahl (standardmäßig 1,0), die die relative Stärke der Regularisierung definiert. Kleinere Werte zeigen eine stärkere Regularisierung an.
  • Solver ist eine Zeichenfolge (standardmäßig liblinear), die entscheidet, welchen Solver ich zum Anpassen des Modells verwende, und die Teil eines Kernels sein kann. Andere Optionen sind newton-cg, lbfgs, sag und saga.
  • max_iter ist eine Ganzzahl (standardmäßig 100), die die maximale Anzahl von Iterationen durch den Solver während der Modellanpassung definiert.

 

In Listing 10 erkennen wir die voreingestellten Modellparameter, die sich natürlich verändern lassen. Es ist allerdings nicht möglich, den besten Wert für einen Modellhyperparameter in Bezug auf ein bestimmtes Problem direkt zu ermitteln. Stattdessen kann man Erfahrungswerte nutzen, Werte kopieren, die bei anderen Problemen verwendet wurden, oder durch Ausprobieren nach dem besten Wert suchen. Ich benutze vor allem den Wert C (Regulator), um den Kernel oder den Solver zu optimieren.

model = LogisticRegression(solver='liblinear', C=1, random_state=0).fit(X, y) // show more model details
print(model)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
  intercept_scaling=1, max_iter=100, multi_class='warn',
  n_jobs=None, penalty='l2', random_state=0, solver='liblinear',
  tol=0.0001, verbose=0, warm_start=False)

Die eigentliche Anpassung besteht darin, schlicht und ergreifend einen anderen Solver einzusetzen:

model = LogisticRegression(solver='<strong>lbfgs</strong>', C=1, random_state=0).fit(X, y)
  print(classification_report(y, model.predict(X)))

Die Ergebnisse sind nun optimal in Bezug auf die Güte des Algorithmus unserer Zahlenreihe und kann auch einem optischen Vergleich mit einem Decision Tree standhalten (Abb. 6).

 

Abb. 6: Optimale Entscheidung der Klassifikation

 

Das Entscheidungsbaumverfahren (Decision Tree) ist eine verbreitete Möglichkeit der Regression oder Klassifikation über ein multivariates Datenset. Das Verfahren kann beispielsweise verwendet werden, um die Zahlungsfähigkeit von Kunden zu klassifizieren oder eine Funktion zur Vorhersage von Falschmeldungen zu bilden. In der Praxis stellt das Verfahren den Data Scientist aber vor große Herausforderungen bezüglich Interpretation und Overfitting (Auswendiglernen der trainierten Beispiele), obwohl der Baum selbst eine transparente und lesbare Grafik bietet. Dazu nutzen wir in VS Code den installierten Graphviz 2.38 sowie eine zusätzliche Zeile im Code, die uns direkt die Pfadangaben im OS-Pfad setzt (Listing 11). Somit können wir direkt im Code Anpassungen an eine andere Version vornehmen oder die Plattform konfigurieren (Abb. 7).

<strong>from sklearn.tree import DecisionTreeClassifier</strong>
from converter import app, request
import unittest
import os
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/'
os.environ["PATH"] += os.pathsep + 'C:/Program Files/Pandoc/'

 

Abb. 7: Die Konfusionsmatrix hat keine Falschen mehr

 

Wichtig: Die Dimensionen der Konfusionsmatrix sind leider nicht normiert. Im Beispiel steht die Wahrheit „Real Actual“ in den Zeilen und die Schätzung „Predict“ in den Spalten. Je nach verwendeter Software können die Dimensionen aber vertauscht sein. Wichtig erscheint mir, die Matrix bei 0 zu beginnen, also True Negatives oben links zu normieren (Abb. 8). Für ein N-Klassen-Problem besteht die Konfusionsmatrix dann aus einer NxN-Matrix, ist also nicht auf eine binäre Klassifikation beschränkt.

 

Abb. 8: Eine normierte Konfusionsmatrix

 

Jupyter Notebook in VS Code

Zum Abschluss noch ein Ausblick in die Integration von Jupyter. Jupyter (ehemals IPython) Notebooks ist ein Open-Source-Projekt, mit dem sich interaktiver Markdown-Text und ausführbarer Python-Quellcode einfach auf einer Leinwand (Canvas) kombinieren lassen, die man als Notebook bezeichnet. Visual Studio Code unterstützt die native Arbeit mit Jupyter Notebooks sowie über Python-Codedateien. Auch meine Erfahrungen bezüglich Debugging oder Codemetriken sind gut. Um mit Jupyter Notebooks arbeiten zu können, ist eine Anaconda-Umgebung in VS Code oder eine andere Python-Umgebung erforderlich, jedoch ist vorgängig ein Jupyter-Paket zu installieren. Somit erhalten wir in VS Code direkt die Möglichkeit, Grafiken einzubinden, zu dokumentieren oder interaktiven Code auszuführen (Abb. 9, 10).

 

Abb. 9: Arbeiten mit Jupyter

 

Abb. 10: Mit dem Terminal lassen sich auch Images interaktiv im Code steuern

The post Visualisierung von Machine Learning-Daten in VS Code appeared first on BASTA!.

]]>
Blazor WebAssembly ist endlich erschienen: Eine neue Ära der Webentwicklung mit .NET https://basta.net/blog/blazor-webassembly-ist-endlich-erschienen/ Tue, 11 Aug 2020 09:01:58 +0000 https://basta.net/?p=80430 Blazor WebAssembly ist am 19. Mai 2020 erstmals in einer stabilen Version erschienen. In diesem Beitrag stelle ich kurz zehn Gemeinsamkeiten und ausführlich zehn Unterschiede zu dem bereits im September 2019 erschienenen Blazor Server vor.

The post Blazor WebAssembly ist endlich erschienen: Eine neue Ära der Webentwicklung mit .NET appeared first on BASTA!.

]]>
Blazor WebAssembly und Blazor Server sind sich grundsätzlich sehr ähnlich:

  1. Beide Architekturen basieren auf .NET. Der Webentwickler schreibt C#, HTML, CSS und in einigen Fällen etwas JavaScript.
  2. Nicht auf alle Funktionen eines Webbrowsers kann man in Blazor direkt per C#-Code zugreifen. Derzeit müssen Entwickler selbst für vermeintlich einfache Funktionen auf die Interoperabilität mit JavaScript zurückgreifen, z. B. für das Lesen und Schreiben von Cookies und anderen lokalen Speichertypen des Webbrowsers. Die Interoperabilität funktioniert jeweils in beide Richtungen: C#-Code kann JavaScript und JavaScript wieder C#-Code aufrufen.
  3. Seiten und Seitenteile definiert man in Razor Components. Diese bestehen wahlweise nur aus einem Razor Template (.razor mit C#-Einschüben nach dem @-Zeichen), aus einem Razor Template plus Code-Behind-Datei in C# oder nur aus einer C#-Klasse. Razor Components sind jedoch keine Web Components gemäß W3C-Standard, sondern nur Komponenten innerhalb von Blazor.
  4. Razor Components sind zustandsbehaftet, d. h., die Werte in Klassenmitgliedern (Fields und Properties) bleiben erhalten, solange die Komponente aktiv (sichtbar) ist.
  5. Razor Components feuern Lebenszyklusereignisse (OnInitialized(), OnAfterRender() usw.) und Benutzerereignisse (@onclick, @onkeyup usw.), auf die der Entwickler im C#-Code reagiert.
  6. Die Razor Components manipulieren ein Virtual Document Object Model (DOM), Blazor synchronisiert es mit dem echtem DOM des Browsers.
  7. Die .NET Core Dependency Injection kommt zum Einsatz (z. B. für den NavigationManager zum Abruf und zur Steuerung des Browser-URL).
  8. Eine Kapselung von Razor Components, C#-Code, JavaScript-Code und statischen Inhalten ist in Razor Class Libraries (DLLs) möglich. Es gibt bereits zahlreiche hilfreiche Razor-Class-Library-Pakete aus der Community. Ein Verzeichnis findet man auf GitHub unter „Awesome Blazor“.
  9. Es gibt in beiden Blazor-Varianten noch kein Konzept für zur Laufzeit nachladbare Module (Lazy Loading), was bedeutet, dass alle Razor Components einer Anwendung bereits beim Anwendungsstart geladen werden müssen.
  10. Die Authentifizierung erfolgt über eine Ableitung der Klasse Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider. Es gibt eine optionale Integration mit ASP.NET Core Identity.

Das waren in Kürze zehn Gemeinsamkeiten, die dazu beitragen, dass man sehr leicht Programmcode zwischen Blazor WebAssembly und Blazor Server austauschen bzw. gemeinsamen Code und gemeinsame Oberflächen in beiden Architekturen verwenden kann.

Ein interessanter Ansatz ist, eine Webanwendung so in eine Razor Class Library zu kapseln, dass sie sowohl in Blazor WebAssembly als auch Blazor Server verwendet werden kann. Man besitzt dann zwei Kopf-/Start-Projekte und gemeinsame DLLs mit der Oberfläche, dem Programmcode und Ressourcen, wie man es heute schon von Xamarin.Forms kennt.

Im Folgenden wird dieser Beitrag zehn Unterschiede genauer erläutern, die in der Praxis zu beachten sind, wenn man sich für eine der beiden Architekturen entscheiden oder gemeinsam nutzbare Razor Class Libraries schaffen will.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

1. Versionsnummer und Support

Die aktuelle Veröffentlichung von Blazor Server trägt die Versionsnummer 3.1.5. Bei Blazor WebAssembly ist die erste Version direkt unter der Versionsnummer 3.2 erschienen; Versionen 1.0 bis 3.1 hat es nie gegeben. Der Unterschied zwischen den Versionsnummern 3.1 bei Blazor Server zu 3.2 bei Blazor WebAssembly bedeutet aber keineswegs, dass Blazor WebAssembly der Nachfolger von Blazor Server ist. Die Versionsnummer 3.2 bedeutet nur, dass die erste Version von Blazor WebAssembly außerhalb des Veröffentlichungszyklus von .NET Core erschienen ist. Die nächste Version wird 5.0 sein. Diese Versionsnummer wird sowohl für Blazor Server als auch Blazor WebAssembly gelten und am 10. November 2020 im Rahmen der .NET Conf 2020 als Teil von .NET 5.0 erscheinen. Auf GitHub findet man eine Roadmap für geplante Verbesserungen und weitere Funktionen in ASP.NET Core Blazor 5.0, die aber wahrscheinlich nicht alle schon in .NET 5.0 erscheinen werden.

Blazor in ASP.NET Core 3.1 ist eine Long-Term-Support-Version (LTS), die Microsoft drei Jahre nach dem Erscheinen mit Bugfixes und Sicherheitsupdates sowie mit kommerziellem Support unterstützt. Die Version 3.1 ist am 3. Dezember 2019 erschienen und wird also bis zum 3. Dezember 2022 unterstützt. Blazor WebAssembly 3.2 hat allerdings lediglich den Status Current Version, erhält also nicht den Long-Term-Support der .NET-Core-Version 3.1 aus dem Dezember 2019. Das bedeutet, dass Anwender nach dem Erscheinen der nächsten Version am 10. November 2020 nur drei Monate Zeit haben werden, die Blazor-WebAssembly-Version zu wechseln. Die erste Long-Term-Support-Version von Blazor WebAssembly wird nach aktuellem Wissen erst die Version 6.0 in .NET 6.0 im November 2021 sein.

2. Architektur

Bei Blazor Server (früher: Server-side Blazor) läuft der .NET-Programmcode auf dem Webserver. Die HTML-Oberfläche wird auf dem Webserver in einem sogenannten Virtual DOM gerendert und mit einem kompakten Synchronisierungsverfahren in einzelnen Datenpaketen über eine kontinuierliche WebSocket-Verbindung zum Client übertragen. Eine JavaScript-Bibliothek im Browser aktualisiert mit diesen Datenpaketen die Oberfläche im Sinne eines Remote Renderings.

Bei Blazor WebAssembly (früher: Client-side Blazor) laufen der .NET-Programmcode und das HTML-Rendering tatsächlich im Webbrowser in dessen WebAssembly-Laufzeitumgebung. Eine JavaScript-Bibliothek kommt dennoch auch hier zum Einsatz, um zwischen der WebAssembly Virtual Machine des Browsers und dem DOM zu synchronisieren, denn die WebAssembly VM des Browsers hat laut W3C-Standard keinen Zugriff auf dessen DOM.

 

Abb. 1: Blazor Server vs. Blazor WebAssembly

 

Der Benutzer der Webanwendung erlebt in beiden Fällen eine Single Page Application (SPA). Allerdings kann eine Blazor-Server-basierte Webanwendung niemals offlinefähig werden (daher in Abbildung 1 „SPA light“ genannt), und es gibt auch Probleme bei nicht konstanten Netzwerkverbindungen: Wenn der Webserver nicht mehr erreichbar ist, wird die aktuelle Ansicht im Browser ausgegraut und der Benutzer sieht oben die Warnmeldung „Attempting to reconnect to the server …“. Sollte das nicht gelingen, erfolgt dann die Meldung „Could not reconnect to the server. Reload the page to restore functionality.“ Wenn der Benutzer dann nicht wieder am Anfang der Anwendung landen soll, sondern an seinem letzten Standort, müssen Zustände im Browser gehalten werden.

Blazor Server hat zudem Herausforderungen bei der Skalierbarkeit, denn das Virtual DOM und der Zustand aller Razor Components liegt für jeden aktuell angeschlossenen Browser im RAM des Webservers. Microsoft hat dokumentiert, dass für jeden angeschlossenen Browser schon bei einer Hello-World-Anwendung 250 KB RAM im Server gebraucht werden. Für echte Anwendungen ist der RAM-Bedarf jeweils selbst zu messen, weil auch Microsoft keine Formel besitzt, um ihn vorherzusagen. Dass Microsoft das nicht für eine beliebige Anwendung vorhersagen kann, ist nachvollziehbar, denn der RAM-Bedarf ist von vielen Faktoren abhängig, insbesondere natürlich von der Komplexität der Benutzeroberflächen und der Menge der im Zustand der Komponente gehaltenen Daten. Im Rahmen der .NET Conf am 14.01.2020 hat Microsoft eigene Messergebnisse veröffentlicht. So kann auf einer virtuellen Maschine in der Azure-Cloud mit 4 virtuellen CPUs und 14 GB RAM bei 20 000 gleichzeitigen Benutzern immer noch eine Latenz (Verzögerung bei der Darstellung von Änderungen) von unter 200 Millisekunden erreicht werden.

3. Hosting

Während eine Blazor-Server-Anwendung naturgemäß immer auf einem ASP.NET-Core-fähigen Webserver laufen muss, gibt es bei Blazor WebAssembly zwei alternative Hosting-Optionen:

  • Hosting auf einem ASP.NET-Core-fähigen Webserver
  • Hosting auf einem beliebigen anderen Webserver, der nur statische Inhalte ausliefern können muss

Bei der Auswahl der Projektvorlage Blazor WebAssembly in Visual Studio bzw. bei dotnet new blazorwasm gibt es daher noch die Zusatzoption ASP.NET Core hosted (auf der Kommandozeile: –hosted). Ohne diese Option wird eine rein lokal im Browser laufende Blazor-WebAssembly-Anwendung erstellt. Für die Installation einer Blazor-WebAssembly-Anwendung muss das IIS-Zusatzmodul URL Rewrite auf dem Webserversystem installiert werden. Nur wenn die Blazor-WebAssembly-Anwendung auch noch ein ASP.NET-Core-basiertes WebAPI-Projekt umfasst, muss auch das ASP.NET Core Runtime Hosting Bundle installiert sein.

Ein Hosting in Azure App Services ist sowohl für Blazor-Server- als auch Blazor-WebAssembly-Anwendungen möglich. Eine Blazor-WebAssembly-Anwendung kann allerdings derzeit nur in einem Windows-basierten Azure-App-Service verwendet werden („A Linux server image to host the app isn’t available at this time.“). Blazor Server kann auch in Linux-basierten App Services laufen.

Während Blazor Server komplett auf .NET Core basiert und auch Blazor-WebAssembly-Projekte mit dem .NET Core SDK übersetzt werden, wirken bei Blazor WebAssembly zur Laufzeit eine Mono-basierte Laufzeitumgebung (aktuell: Mono 6.13.0) und das Projekt Emscripten. Ein weiterer Unterschied zwischen Blazor Server und Blazor WebAssembly ist, dass bei Ersterem beim Deployment eine .exe-Datei aus dem Projekt entsteht, beim zweiten eine .dll-Datei. Bei Blazor Server ist also ein Selfhosting außerhalb eines Webservers möglich, wie bei ASP.NET Core üblich, indem man die entstandene .exe-Datei einfach startet. Hier startet der in ASP.NET integrierte Webserver Kestrel.

 

 

 

4. Projektaufbau

Der Projektaufbau bei Blazor Server und Blazor WebAssembly ist ähnlich (Verzeichnisse /wwwroot, /Pages und /Shared, Dateien App.razor und launchsettings.json), aber im Detail gibt es Unterschiede. Für Blazor WebAssembly gibt es zwei Projektvorlagen: für den eigenständigen Fall und für den ASP.NET-Core-gehosteten (Abb. 2). Bei Blazor Server und der eigenständigen Blazor-WebAssembly-Anwendung entsteht jeweils nur ein Projekt. Mit der Option ASP.NET Core hosted entstehen gleich drei Projekte:

  • Name.Client ist die Blazor-WebAssembly-basierte Frontend-Webanwendung für den Webbrowser.
  • Name.Server ist ein ASP.NET-Core-basiertes Backend, das einerseits das Blazor-WebAssembly-basierte Frontend lädt, andererseits ein Web API bereitstellt, das Daten an das Frontend liefert.
  • Name.Shared ist eine .NET-Standard-Bibliothek, die sowohl in Client als auch Server eingebunden wird. Sie stellt gemeinsame Geschäftsobjekte (WeatherForecast.cs) bereit.

Der Startcode einer Blazor-WebAssembly-Anwendung besteht seit Version 3.2 Preview 1 vom 28.01.2020 nur noch aus einer Klasse: Program.cs. Hier wird auch die .NET Core Dependency Injection (Microsoft.Extensions.DependencyInjection) konfiguriert. Die aus ASP.NET-Core-Projekten bekannte und auch in Blazor Server verwendete Zweiteilung in Program.cs und Startup.cs ist entfallen. Die Rahmenseite mit dem – und -Tag ist bei Blazor WebAssembly die statische Datei wwwroot/index.html, bei Blazor Server ist es eine Razor Page: pages/_host.cshtml.

 

Abb. 2: Projektaufbau bei Blazor Server vs. Blazor WebAssembly

 

5. Debugging

Debugging ist in Blazor WebAssembly trotz der komplexen Softwarearchitektur zwar inzwischen grundsätzlich möglich, aber nicht alle aus Visual Studio bekannten .NET-Debugging-Funktionen sind implementiert. Möglich beim Debugging von Blazor-WebAssembly-Anwendungen ist Folgendes:

  • Haltepunkte in Razor Components (im Template und im Code-Behind) sowie davon aufgerufenem Code
  • Variablenwerte können durch die Tooltips sowie die Fenster Locals und Watch betrachtet werden
  • Debuggersteuerung mit F10 (Step Over), F11 (Step Into) sowie F5 (Fortsetzen)
  • Ansicht des Call-Stacks

Das Folgende ist leider bisher nicht bzw. nur eingeschränkt möglich:

  • Debugging mit Browsern, die nicht auf Chromium basieren, d. h. Debugging ist derzeit nur mit Google Chrome und dem neuen Microsoft Edge (ab Version 79) möglich
  • Haltepunkte in der Startphase der Anwendung (also in Program.cs)
  • Aufruf des Debuggers bei unbehandelten Ausnahmen (Exceptions)
  • Aufruf des Debuggers mit System.Diagnostics.Debugger.Break()
  • Anzeige der Inhalte von einigen Objektmengen (z. B. Dictionaries)
  • Nutzung des Auto-Fensters
  • Das Call Stack-Fenster zeigt als Sprache auch für C#-Code immer „JavaScript“ an
  • Im Immediate Window funktionieren viele Zugriffe nicht, z. B. auf Listeninhalte

Bei Blazor Server sind hingegen alle Debugging-Funktionen von Visual Studio uneingeschränkt verfügbar.

Blazor WebAssembly kann neben dem Debugging in Visual Studio auch ein Debugging direkt im Chromium-basierten Browser vollziehen. Dazu drückt man bei einer lokal gestarteten Blazor-WebAssembly-Anwendung die Tastenkombination SHIFT + ALT + D und folgt dann der Anweisung, um den Browser im Debug-Modus zu starten.

6. Anwendungsgröße und Startzeiten

Blazor WebAssembly lädt beim Start eine Vielzahl von Dateien, der Anwendungsstart ist entsprechend langsam. Selbst eine minimale Blazor-WebAssembly-Anwendung mit einer einzigen statischen Razor Component benötigt beim Start 35 HTTP-Anfragen mit insgesamt 5,5 MB Netzwerkdatenverkehr (Abb. 3) nach einem Deployment im Releasemodus unter Einsatz des Mono-IL-Linkers und Brotli-Komprimierung. Wenn man in den Entwicklerwerkzeugen im Chrome-Browser eine „Fast-3G“-Verbindung simulieren lässt, sieht der Benutzer rund 32 Sekunden lang „Loading …“. Das ist inakzeptabel für viele Internetanwendungen, man kann es allenfalls bei Intra- und Extranetanwendungen vertreten. Bei aktiviertem Cache dauert das zweite Laden rund 6,5 Sekunden.

Immerhin wächst die Anwendungsgröße nicht linear. Eine Blazor-WebAssembly-Anwendung aus der Praxis mit rund 30 Komponenten umfasst 7,5 MB (Abb. 4; geladen von einem Azure-App-Service in den USA), mit 200 Komponenten 8,7 MB (Abb. 5; geladen im lokalen Netzwerk). Die Anzahl der HTTP-Anfragen ändert sich nicht bei steigender Komponentenanzahl, da alle Razor Components eines Projekts in eine einzige DLL hineinkompiliert werden. In Abbildung 4 sieht man aber bei der Praxisanwendung mehr HTTP-Anfragen, da hier viele weitere Ressourcen (Style Sheets, Grafiken, JavaScript-Dateien etc.) verwendet werden.

 

Abb. 3: Das „Hello-World“-Kompilat in Blazor WebAssembly

 

Abb. 4: Eine Blazor-WebAssembly-Anwendung mit 30 Komponenten

 

Abb. 5: Eine größere Blazor-WebAssembly-Anwendung mit 200 Komponenten

 

SIE LIEBEN .NET?

Entdecken Sie die BASTA! Tracks

 

7. Laufzeitverhalten

Bei Blazor Server findet die in .NET übliche Übersetzung von Intermediate Language (MSIL) in Maschinencode per Just-in-Time-(JIT-)Compiler statt. Bei Blazor WebAssembly wird ebenfalls MSIL in den Browser geladen, dort aber von einem MSIL-Interpreter innerhalb der dortigen .NET-Laufzeitumgebung interpretiert, die eine WebAssembly-basierte Variante von Mono ist. Diese Interpreterarchitektur macht schon deutlich, dass die Codeausführung in Blazor WebAssembly deutlich langsamer als bei Blazor Server ist. Daniel Roth, Program Manager im ASP.NET-Team bei Microsoft, bestätigt das in der Antwort auf eine Frage in seinem Blogeintrag: „Blazor WebAssembly runs on a .NET IL interpreter based runtime – there’s no JIT compilation to WebAssembly happening, so execution performance is much slower than normal .NET code execution.“ In seiner Antwort auf die Performancefrage des Kunden geht er auch offen damit um, dass Blazor WebAssembly derzeit hinsichtlich der Ausführungsgeschwindigkeit nicht konkurrenzfähig zu aktuellen JavaScript Frameworks ist: „Blazor WebAssembly isn’t going to win in any performance comparisons with JavaScript based frameworks like Angular or React.“

Wie aufwendig die Verarbeitung bei Blazor WebAssembly ist, zeigt sich bei Leistungsmessungen: Eine Reihe von 100 000 Fibonacci-Berechnungen, die in Blazor Server inklusive Übertragung von Parametern und Ergebnis zwischen den Prozessen 373 Millisekunden braucht, dauert bei der Ausführung in Blazor WebAssembly in Chrome stattliche 10 Sekunden (Abb. 6). Damit man nicht Äpfel mit Birnen vergleicht, ist hier der Webserver der gleiche PC, auf dem auch der Browser läuft. Bei diesem gewaltigen Unterschied (Faktor 38) darf aber man aber nicht vergessen, dass sich in der Praxis bei Blazor Server alle aktiven Benutzer die Rechenleistung des Webservers teilen, während Blazor WebAssembly die CPU jedes Clients ausnutzt.

Schon bei der Erstankündigung von Blazor im März 2018 brachte Microsoft auch eine Ahead-of-Time-Kompilierung (AOT) von MSIL in WebAssembly-Bytecode ins Spiel, die die Ausführung beschleunigen könnte. AOT ist aber nicht in der jetzt erschienenen Version 3.2 enthalten. Microsoft tut sich seit Jahren schwer mit AOT für .NET. Man hat schon diverse Ansätze versucht und wieder beerdigt.

Die Leistungsdaten in Abbildung 6 wurden jeweils mit den Webbrowsern Chrome und Firefox auf drei Servertypen erhoben:

  1. auf einem lokalen System (Webbrowser und Webserver auf dem gleichen Rechner, ein aktueller Entwickler-PC mit AMD Ryzen 9 3950X mit 16 Kernen und 128 GB RAM)
  2. in der Microsoft-Azure-Cloud als Web-App-Service-gehostete Webanwendungen (Tarif S1, ca. 65 Euro/Monat)
  3. in der Microsoft-Azure-Cloud als Web-App-Service-gehostete Webanwendungen (Tarif P3V2, ca. 500 Euro/Monat)

Abbildung 6 liefert zwei weitere Erkenntnisse:

  • Blazor WebAssembly stürzt bei sehr vielen Rechenoperationen mit dem Laufzeitfehler „System.OutOfMemoryException: Out of memory“ ab, weil die Berechnung die Ergebnisse im RAM speichert und der von der WebAssembly VM nutzbare Speicher begrenzt ist. Während die Dokumentation sich dazu ausschweigt, ergaben meine Tests, dass Blazor WebAssembly dem Entwickler etwas weniger als 1 GB RAM zur freien Verfügung stellt.
  • Bei Blazor Server kann man – wie zu erwarten – durch einen besser ausgestatteten Webserver eine höhere Rechenleistung erreichen.

 

Abb. 6: Leistungsvergleich Blazor Server zu Blazor WebAssembly

 

8. JavaScript-Interoperabilität

Die Interoperabilität zwischen dem C#-Programmcode und JavaScript ist in beiden Blazor-Varianten vorhanden, es gibt jedoch vier Unterschiede:

  • Zentrale Anlaufstelle für die Interoperabilität von .NET mit JavaScript ist die .NET-Schnittstelle Microsoft.JSInterop.IJSRuntime mit den Methoden InvokeAsync() und InvokeVoidAsync() in jeweils drei Überladungen. Der Entwickler muss sich eine Implementierung zu dieser Schnittstelle per Dependency Injection liefern lassen. Unter Blazor Server erhält er eine Instanz der Klasse Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime, unter Blazor WebAssembly eine Instanz der Klasse Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime. Die verschiedenen Implementierungen ergeben Sinn, denn bei Blazor WebAssembly muss lediglich zwischen der WebAssembly VM und der JavaScript-Laufzeitumgebung innerhalb des Webbrowsers vermittelt werden. Bei Blazor Server liegt das Netzwerk dazwischen: Die Aufrufe mit Parametern und die Rückgabewerte werden über ASP.NET SignalR serialisiert.
  • In Blazor Server ist JavaScript-Interoperabilität allerdings nicht im Rahmen der sogenannten Pre-Rendering-Phase einer Razor Component möglich, d. h. nicht in den Lebenszyklusereignisbehandlungsroutinen OnInitialized() und OnInitializedAsync(), sondern erst in OnAfterRenderAsync(). Wenn man das nicht beachtet, bricht die Blazor-Server-Anwendung zur Laufzeit mit diesem Fehler ab: „JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.“
  • Ein Zugriff auf den Titel des Browserfensters oder andere Daten im -Element der Webseite ist in Blazor WebAssembly nur über JavaScript möglich. Unter Blazor Server kann der Entwickler über die Razor Page _host.cshtml, die im gleichen Prozess läuft wie die Razor Component von Blazor Server, darauf zugreifen.
  • Unter Blazor WebAssembly erzeugt der Aufruf der Methode Console.WriteLine() eine Ausgabe in der Entwicklerkonsole des Webbrowsers. Unter Blazor Server landet Console.WriteLine() auf dem Webserver. Für die Ausgabe auf der Entwicklerkonsole des Webbrowsers muss man per JavaScript-Interoperabilität die JavaScript-Funktion console.log() aufrufen.

API-Zugriffe

Blazor Server kann alle in .NET Core erreichbaren Programmierschnittstellen verwenden, also sowohl .NET Assemblies und COM-Komponenten als auch direkt die Windows-Betriebssystem-APIs Windows 32 (Win32) und Windows Runtime (WinRT). So sind aus einer Blazor-Server-Anwendung heraus zum Beispiel direkte Zugriffe auf Datenbankmanagementsysteme und Verzeichnisdienste sowie an den Server angeschlossene oder im Netzwerk erreichbare Hardware möglich.

Die WebAssembly VM, auf der Blazor WebAssembly aufsetzt, läuft hingegen genau wie JavaScript in der Sandbox des Webbrowsers. Daher kann der Entwickler nicht alle Programmierschnittstellen nutzen. So ist ein direkter Datenbankzugriff in Blazor WebAssembly nicht möglich. Der Entwickler kann zwar einen Datenbankprovider wie Microsoft.Data.SqlClient via NuGet-Paket in den Browser laden, ein Datenbankverbindungsaufbau scheitert dann aber mit dem Laufzeitfehler „Microsoft.Data.SqlClient is not supported on this platform“. Auch andere Netzwerkprotokolle wie LDAP werden damit unterbunden. Der Entwickler muss alle Daten über HTTP-basierte Web Services beziehen und senden. Auch Zugriffe auf viele lokale Ressourcen werden abgeblockt; so kann Blazor WebAssembly nicht via System.Diagnostics.Process.GetCurrentProcess() Informationen über den eigenen Prozess beziehen. Das führt zum Laufzeitfehler „Process has exited or is inaccessible, so the requested information is not available.“ Die Einschränkungen durch die Sandbox sind bei WebAssembly die gleichen wie bei JavaScript.

Bei Blazor WebAssembly ist hingegen der Zugriff auf Server über HTTP-/HTTPS-basierte Web Services (z. B. Web APIs) zu kapseln. Für den Zugriff auf REST-Dienste verwendet man in Blazor die in .NET und .NET Core etablierte Bibliothek System.Net.Http.HttpClient. Sie gehört zum Standardumfang des .NET Core SDK und muss daher in Blazor-Projekten nicht explizit als NuGet-Paket referenziert werden. Optional kann man aber als neue Hilfsbibliothek System.Net.Http.Json verwenden, deren Erweiterungsmethoden Microsofts neuen JSON Serializer System.Text.Json in Verbindung mit HttpClient verwendet. Das Paket System.Net.Http.Json ist in den Blazor-WebAssembly-Projektvorlagen im Standard referenziert, in Blazor Server aber nicht. Die Standardprojektvorlage für Blazor Server enthält kein Beispiel für den Einsatz von HttpClient, da hierbei ja ein Web API nicht zwingend notwendig ist. Wenn man bei der Blazor-WebAssembly-Vorlage die Option ASP.NET Core hosted auswählt, bekommt man ein Serverprojekt mit einem Web-API-Dienst (/Controllers/WeatherForecastController.cs) und im Clientprojekt einen Web-API-Client (/Pages/FetchData.razor). Auch die Nutzung von Diensten, die auf Google RPC (gRPC) basieren, ist in beiden Blazor-Varianten möglich. Ebenso kann ASP.NET Core SignalR eingesetzt werden.

 

 

 

10. PWA-Unterstützung

Die Option in den Projektvorlagen, eine Webanwendung als installierbare Progressive Web App (PWA) zu betreiben (Abb. 7), gibt es nur bei Blazor WebAssembly. Bereits beim Anlegen eines Blazor-WebAssembly-Projekts kann man ein Häkchen setzen (Abb. 8), das alle notwendigen Voraussetzungen schafft. Bei Blazor Server muss man selbst Hand anlegen.

 

Abb. 7: Diese Blazor-WebAssembly-Anwendung mit PWA-Unterstützung ist installierbar

 

Abb. 8: Anlegen einer Blazor-WebAssembly-Anwendung als PWA

 

Es entstehen dadurch, zusätzlich zu den Elementen, die man in Abbildung 2 in Spalte 2 und 3 sieht, weitere Elemente:

  1. Datei /wwwroot/manifest.json (Manifestdatei)
  2. Datei /wwwroot/service-worker.js
  3. Datei /wwwroot/service-worker.published.js
  4. Datei /wwwroot/icon-512.png
  5. Zeile <link href=”manifest.json” rel=”manifest” /> in index.html
  6. Zeile <link rel=”apple-touch-icon” sizes=”512×512″ href=”icon-512.png” /> in index.html
  7. Zeile <script>navigator.serviceWorker.register(‘service-worker.js’);</script> in index.html

Alternativ kann man diese Voraussetzungen auch nachträglich in einem bestehenden Projekt leicht erfüllen.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Fazit

Nach all den genannten Gemeinsamkeiten und Unterschieden kann man festhalten:

  • Programmcode lässt sich (mit ein paar Änderungen) leicht zwischen Blazor-Server- und Blazor-WebAssembly-Projekten austauschen.
  • Programmcode lässt sich (mit ein paar Fallunterscheidungen) zur gemeinsamen Verwendung in Blazor Server und Blazor WebAssembly leicht in einer Razor Class Library kapseln.

Es stellt sich für die Praxis die Frage, ob man Blazor WebAssembly und/oder Blazor Server einsetzen sollte. Grundsätzlich ist Blazor ein Angebot für .NET-Entwickler, die ihr Know-how und/oder eine bestehende .NET-Programmcodebasis ins Web bringen wollen.

Blazor WebAssembly hat aufgrund der vielen zu ladenden Dateien und deren Größe sowie der fehlenden Lazy-Loading-Funktion den Nachteil, dass Nutzer (insbesondere bei schlechten Netzwerkverbindungen) beim ersten Aufruf einer Website ohne Inhalte im Cache sehr lange warten müssen, bis sie die Anwendung überhaupt nutzen können. Für öffentliche Internetangebote ist die Ladezeit deutlich zu lang, denn die Benutzer haben in der Zeit längst eine alternative Seite aufgerufen. Im Intra- oder Extranet könnte man die Anwender entsprechend instruieren, die Wartezeit zu ertragen.

Blazor-Server-Anwendungen starten hingegen sehr schnell, können aber bei dauerhaft instabilen Netzwerkverbindungen die Nerven der Benutzer belasten.

Die Entscheidung zwischen Blazor Server und Blazor WebAssembly ist folglich nicht leicht und abhängig vom Projekt, der Anzahl der Nutzer und deren Verhalten. Ich tendiere derzeit dazu, unsere bestehenden Projekte, die Blazor Server nutzen, vorerst dort zu belassen und auf Optimierungen in Blazor WebAssembly in der Zukunft zu hoffen.

Die Hoffnung auf die Zukunft hängt daran, ob Microsoft das AOT-Thema für Blazor WebAssembly möglichst schnell in den Griff bekommt und sich damit die Startzeiten verringern und die Ausführungsgeschwindigkeit erhöhen werden. Eine weitere Hoffnung ist, dass das Thema Internet Explorer sich irgendwann ganz erledigt haben wird und alle Internetnutzer wirklich WebAssembly-fähige Browser einsetzen.

The post Blazor WebAssembly ist endlich erschienen: Eine neue Ära der Webentwicklung mit .NET appeared first on BASTA!.

]]>
Web Components auf Steroiden: Maximieren Sie die Leistung und Wiederverwendbarkeit Ihrer Webanwendungen https://basta.net/blog/web-components-auf-steroiden/ Thu, 11 Jun 2020 08:00:11 +0000 https://basta.net/?p=77537 Saubere Architektur und eine gute Dokumentation machen Web Components erst so richtig gut wiederverwendbar. Beides lässt sich ohne eigenes Framework in der Regel kaum sinnvoll für größere Projekte abbilden. Hier kommen Frameworks speziell für Web Components ins Spiel. In diesem Fall Stencil, ein Tool zur Entwicklung, aber auch Dokumentation von Web Components. Eine Kampfansage gegen große Single Page Application Frameworks?

The post Web Components auf Steroiden: Maximieren Sie die Leistung und Wiederverwendbarkeit Ihrer Webanwendungen appeared first on BASTA!.

]]>
Web Components – ein Begriff, den man seit einiger Zeit immer öfter hört und immer öfter hören wird. Dabei handelt es sich um ein natives Komponentenmodell im Browser. Wir können damit Komponenten entwickeln, ohne auf ein Framework angewiesen zu sein. Weniger Abhängigkeiten, weniger Fehlermöglichkeiten. Wer schon einmal selbst Web Components ausprobiert hat oder meinen Artikel im Windows Developer 4.20 gelesen hat, wird sich etwas vorkommen wie bei „Zurück in die Zukunft“. Moderne Anwendungsentwicklung und moderne Konzepte, aber mit HTML, JavaScript und CSS, wie wir es vor vielen Jahren schon geschrieben haben.

Wie der Begriff Components vermuten lässt, bauen wir mit Web Components keine vollständigen Anwendungen, sondern vielmehr unsere Basiskomponenten, unser Designsystem, das wir dann in unseren Teams und deren Anwendungen nutzen, um ein einheitliches Look and Feel zu erhalten. Diese Komponenten erhalten ihre Daten in der Regel von außen und stellen sie wie gewünscht dar. Das bedeutet allerdings, dass der Entwickler wissen muss, welche Eigenschaften eine Komponente hat, sprich eine gute Dokumentation über die Komponente ist wichtig.

Genau hier setzt Stencil an. Stencil ist ein vom Ionic-Framework-Team entwickeltes Tool und wird auch für das aktuelle Ionic Framework 5 genutzt. Stencil versteht sich als Compiler für Web Components und PWAs und bringt uns damit wirklich zurück in die Zukunft. Es bringt gewohnte Features moderner SPA Frameworks mit und liefert als Ausgabe Web Components, die wir überall einsetzen können. Es ist wichtig zu verstehen, dass Stencil kein UI Framework ist, also kein Aussehen von Komponenten definiert; das obliegt uns Entwicklern. Es hilft uns aber, genau diese Komponenten und ihr Aussehen zu definieren, die dann von anderen Teams und Anwendungen genutzt werden können.

Als Features erhalten wir mit Stencil eine robuste Build Pipeline mit TypeScript, CSS-Präprozessoren und Dokumentationsgenerierung. Zusätzlich mischt Stencil gewisse Vorzüge von Angular und React. Es nutzt zur Komponentendefinition Dekoratoren und JSX für die Templatedefinition. Damit haben wir die Möglichkeit, reaktive Komponenten zu entwickeln, deren DOM-Updates sich durch das von Stencil implementierte Virtual DOM auf ein Minimum beschränken.

Stencil hat sich als Ziel gesetzt, Komponenten zu erzeugen, die den Webstandards entsprechen und auf Webstandards basieren. Durch TypeScript kann der Stencil-Compiler automatisch Optimierungen vornehmen, um so eine hohe Performance zur Laufzeit gewährleisten zu können. Stencil möchte zudem nur ein kleines, aber sehr robustes API anbieten, dessen Grundlage immer die Webstandards sind. Dadurch soll eine zukunftssichere Bibliothek ermöglicht werden, die auf den nativen Web Components aufsetzt und durch das eigene API dem Entwickler die typische Boilerplate abnimmt.

In diesem Artikel wollen wir gemeinsam zwei kleine Web Components mit Hilfe von Stencil entwickeln. Die erste Komponente wird von dem öffentlichen PokéAPI eine Liste von Pokémon abrufen und darstellen. Eine zweite Komponente berechnet die Pagination, visualisiert sie und stellt ein Event bereit, das beim Durchblättern aufgerufen wird. Die Listenkomponente empfängt dieses Event und wird die entsprechende Seite vom API anfordern. Auf GitHub ist das fertige Beispiel zum direkten Ausprobieren hinterlegt, Abbildung 1 zeigt das User Interface. Los gehts!

 

Abb. 1: Das fertige Beispiel Pokémon List

 

Projekterzeugung

Stencil benötigt eine aktuelle Node-Version, die mindestens npm in Version 6 mitbringt. Out of the box ist das mindestens Node 10.3.0. Nach erfolgter Installation von Node kann ein Stencil-Projekt mit folgendem Befehl auf der Kommandozeile erstellt werden:

npm init stencil

Hierdurch lädt npm die nötigen Quellen für das Stencil CLI herunter und startet es automatisch. Im CLI selbst können wir zwischen drei Optionen wählen: ionic-pwa, app und component (Abb. 2). Die Option ionic-pwa legt ein neues Projekt zur Erstellung einer Progressive Web App an, die auf dem Ionic Framework basiert (und damit auch ein UI festlegt). Die Option app legt ein neues Projekt zur Erstellung einer Single Page Application mit Hilfe von Stencil an, hier haben wir also auch Features wie Routing. Die dritte Option components erstellt eine Web Components Bibliothek. Und genau hierfür werden wir uns entscheiden, da wir eine Web-Components-Bibliothek und keine App entwickeln wollen. Nach der Bestätigung müssen wir noch ein Projektnamen festlegen, bspw. windows-developer-stencil-components. Nach einer weiteren Bestätigung erzeugt das CLI einen Ordner mit dem Projektnamen und einer Beispielkomponente. Abbildung 3 zeigt den Aufbau des erzeugten Projekts.

 

Abb. 2: Auswahlmöglichkeiten des Stencil CLI

 

 

Abb. 3: Struktur eines neuen Stencil Projekts

 

Die wichtigsten Dateien und Ordner sind: package.json, stencil.config.ts, src/index.html, src/components und src/components.d.ts. Schauen wir uns mal genauer an, was wir dort finden.
In der package.json finden wir einige vordefinierte Kommandos, um mit dem Projekt zu arbeiten:

  • build erzeugt alle unsere Komponenten, sodass wir sie in weiteren Projekten einbinden können.
  • start startet die Entwicklungsumgebung von Stencil. Diesen Befehl benötigen wir, wann immer wir unsere Komponenten entwickeln.
  • test führt einmalig Unit-Tests aus; geeignet für ein CI-System.
  • test.watch führt automatisch beim Ändern des Source Codes die Tests aus; praktisch für Test-driven Development.
  • generate erzeugt eine neue Web Component mit dem Standardtemplate von Stencil.

 

Die Datei stencil.config.ts enthält Konfigurationen für unser Projekt, beispielsweise welche Ausgabe wir erzeugen wollen und welche Plug-ins wir nutzen möchten. In der Dokumentation finden wir alle Möglichkeiten. Per Standard verarbeitet Stencil in den Komponenten nur CSS. Für unser Projekt wollen wir allerdings SCSS nutzen. Dazu müssen wir in einer Kommandozeile den folgenden Befehl ausführen:

npm install -D @stencil/sass

Mit dem Befehl wird das Plugin für SCSS installiert. Damit der Stencil-Compiler es benutzt, müssen wir an der Datei stencil.config.ts eine Ergänzung vornehmen. In der Datei wird ein Objekt config exportiert, diesem fügen wir folgenden Eintrag hinzu:

plugins: [sass()]

Natürlich dürfen wir den Import nicht vergessen, den wir ganz an den Anfang der Datei schreiben:

import { sass } from '@stencil/sass'

Nach dem Abspeichern können wir die Datei schließen. Weiter geht es mit src/index.html und src/components. Die index.html ist die Datei, die nur zur Entwicklungszeit in den Browser geladen wird, um unseren Komponenten anzuzeigen. Das heißt, wir können diese Datei einfach als eine Art Komponentenkatalog ansehen, in der alle unseren Komponenten aufgelistet werden. Die eigentliche Entwicklung der Komponenten findet dann im Ordner src/components statt.

Zuletzt bleibt uns noch die Datei src/components.d.ts. Hier finden wir eine TypeScript-Definitionsdatei über unsere Komponenten. Es ist die für eine IDE verständliche Dokumentation, um uns IntelliSense besser anzuzeigen. Aktuell existiert für Web Components generell noch keine standardisierte Art der Dokumentation. Das W3C diskutiert noch über die verschiedenen Möglichkeiten, Stencil hat mit der components.d.ts eine erste implementiert. Es kann daher sein, dass diese Datei in künftigen Versionen nicht mehr existiert, andere Inhalte besitzt oder durch eine andere Möglichkeit ersetzt wird.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Unsere erste Komponente: Pokémon List

Nachdem wir uns mit dem initialen Setup beschäftigt haben, ist es Zeit, unsere erste Komponente zu erstellen. Dazu benötigen wir folgenden Befehl auf einer Kommandozeile:

npm run generate

Dieser Befehlt startet das Stencil CLI zur Erzeugung einer neuen Komponente. Zuerst muss der Name der Komponente eingegeben werden. Wichtig: Eine Web Component muss ein Dash-/Minus-Zeichen im Namen haben. In unserem Fall nutzen wir hier pokemon-list. Nach Bestätigung des Namens werden wir gefragt, welche Dateien erzeugt werden sollen. Hier wählen wir Stylesheets an und Spec Test und E2E Test ab. Für diesen Artikel sind die Testdateien nicht relevant. Wer dennoch an den Tests interessiert ist, kann ein Blick in src/components/my-component werfen. Diese Komponente wurde standardmäßig bei der Initialisierung des neuen Projekts angelegt und beinhaltet beide Testarten. Achtung, auch wenn wir als Plug-in zuvor SCSS angegeben haben, legt das CLI standardmäßig eine reine CSS-Datei an, die wir dann später manuell umbenennen müssen.

Als Nächstes werden wir den Inhalt der erzeugten Datei src/components/pokemon-list/pokemon-list.tsx austauschen. Den neuen Inhalt finden wir in Listing 1.

import { Component, ComponentDidLoad, ComponentInterface, h, Host, Prop, State } from '@stencil/core';
import { PokeApiService, Pokemon } from './poke-api.service';

@Component({
  tag: 'pokemon-list',
  styleUrl: 'pokemon-list.scss',
  shadow: true,
})
export class PokemonList implements ComponentInterface, ComponentDidLoad {
  private itemsPerPage = 10;
  private offset = 0;
  private pokeApiService = new PokeApiService();

  @State() private pokemons: Pokemon[];
  @State() private pokemonCount: number;

  /** The title of this Pokémon List. */
  @Prop() listTitle = 'Pokémon List';

  componentDidLoad(): void {
    this.loadPage();
  }

  private loadPage(): void {
    this.pokeApiService.loadPage(this.offset, this.itemsPerPage)
      .then(response => {
        this.pokemons = response.results;
        this.pokemonCount = response.count;
      });
  }

  render() {
    return (
      <Host>
      </Host>
    );
  }
}

PokemonList erstellt, die die Interfaces ComponentInterface und ComponentDidLoad implementiert. Das Interface ComponentInterface muss von jeder Stencil-Komponente implementiert werden und gibt damit vor, dass jede eine render-Methode haben muss. Zusätzlich muss jede Komponente mit dem Dekorator @Component versehen werden. Als Option kann hier bspw. der Tag der Komponente angeben werden, konkret, wie das HTML-Tag lautet, um die Komponente später als Web Component anzusprechen. In unserem Fall ist das pokemon-list. Über styleUrl kann ein Stylesheet referenziert werden, in unserem Fall pokemon-list.scss. Die Eigenschaft shadow gibt an, dass diese Komponente ein Shadow DOM nutzen soll. Ein solches sollte immer eingesetzt werden, um die Vorteile von Web Components zu nutzen. Eine weitere Eigenschaft, die wir aktuell nicht nutzen, ist assetsDirs. Sie erlaubt es, Ordner mit Asset-Dateien anzugeben, also bspw. Bildern oder Schriftarten, die von Stencil beim Erstellen der Komponente mit abgearbeitet werden sollen.

Als nächstes definiert die Komponente drei Felder: itemsPerPage, offset und pokeApiService. Die ersten beiden Eigenschaften sind für die Pagination gedacht und der Service wird sich um das Abrufen der Daten vom API kümmern. Diesen haben wir noch nicht entwickelt und werden wir im Listing 3 kennenlernen. Wer möchte, kann natürlich auch echte ECMAScript Private Fields statt dem TypeScript-Sichtbarkeitsmodifikator private nutzen.

Darauf folgt die Definition zweier weiterer Felder, pokemons und pokemonCount. Das erste Feld wird alle Pokémon beinhalten, die die Liste aktuell darstellt und das zweite Feld kennt die Gesamtzahl aller Pokémon, die das API kennt. Erstmalig entdecken wir hier auch den Dekorator @State. Er dient zum Management des internen State der Komponente. Wird dieser verändert, also dem Feld ein anderer Wert zugewiesen, wird automatisch die render-Methode unserer Komponente aufgerufen. Wichtig hierbei ist jedoch, dass Stencil nur einen Referenzvergleich macht. Beim Arbeiten mit Objekten oder Arrays ist es daher wichtig, immer eine Kopie zu erzeugen.

Das nächste definierte Feld ist listTitle, ein einfacher String, den wir später zum Erzeugen einer Überschrift nutzen. Hier sehen wir zum ersten Mal auch den @Prop-Dekorator. Im Gegensatz zu @State erlaubt @Prop, das dazugehörige Feld von außen auch als HTML-Attribut zu nutzen. Auch hier gilt: Ändert sich die Referenz dieses Felds, wird die render-Methode aufgerufen. Mit @Prop können wir daher ein API für unsere Komponente definieren. Wichtig ist, dass Stencil nur für primitive Datentypen automatisch ein HTML-Attribut generiert. Für Objekte und Arrays kann man dieses Verhalten explizit einschalten, in dem man dem Dekorator ein zusätzliches Optionenobjekt übergibt. Außerdem ist zu beachten, dass per Konvention die Feldnamen als Dash Case im HTML abgebildet werden. So wird aus listTitle im JavaScript ein list-title als HTML-Attribut.

Nach den Felddefinitionen sehen wir die Implementierung des Interface ComponentDidLoad mit seiner Methode componentDidLoad. Hierbei handelt es sich um eine Lifecycle-Methode einer Stencil-Komponente, die einmalig aufgerufen wird, nachdem die Komponente vollständig geladen und zum ersten Mal gerendert wurde. Natürlich können wir auch Web-Component-Lifecycle-Methoden wie connectedCallback oder disconnectedCallback oder weitere von Stencil definierte Lifecycle-Methoden implementieren.

In der Methode selbst rufen wir unsere private Methode loadPage auf, die über den pokeApiService die gewünschte Seite lädt und das Ergebnis in unsere Felder schreibt. Durch den @State-Dekorator wird die Komponente automatisch nach Erhalt der Daten neugerendert.

Was genau gerendert werden soll, bestimmt die Methode render in Form von JSX. Aktuell sehen wir hier nur ein Host-Element. Hierbei handelt es sich um ein virtuelles Element von Stencil, bspw. eine CSS-Klasse oder ein Event auf den Shadow-Host.

Damit wir auch eine visuelle Ausgabe unserer Komponente erhalten, fügen wir Listing 2 in unser Host-Element ein.

<Host>
  <header>
    <h2>{this.listTitle}</h2>
  </header>

  {this.pokemons && this.pokemons.length
    ? <div>
      <p>Es existieren {this.pokemonCount} in der Datenbank.</p>
      <p>Folgend sind die nächsten {this.pokemons.length}.</p>

      <table>
        <thead>
          <tr>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          {this.pokemons.map(pokemon =>
            <tr>
              <td>{pokemon.name}</td>
            </tr>,
          )}
        </tbody>
      </table>
    </div>
    : <div>PokeApi wird befragt...</div>
  }
</Host>

JSX erlaubt es uns, HTML direkt in unseren TypeScript-Code einzubetten. Um einen Wert auszugeben, nutzen wir geschwungene Klammern. So geben wir in einem h2-Element den Titel unserer Liste aus.

Darauf folgt eine alternierende Ausgabe mit Hilfe des Ternary-Operators. Wir prüfen erst, ob pokemons truthy ist und eine Länge hat. Ist das der Fall, geben wir eine Tabelle mit den geladenen Pokémon-Namen aus. Hier ist (JSX-bedingt) zu beachten, dass wir das Array nicht mit forEach iterieren, sondern mit map, dessen Rückgabe wieder JSX ist.

Ist pokemons falsy oder hat keine Länge geben wir den Text „PokeApi wird befragt…“ aus. Wir haben hier eine sehr einfache textuelle Ladeanzeige implementiert. Stattdessen könnte man hier auch eine weitere Web Component implementieren, die einen typischen Loading Spinner darstellt und diesen anstelle des Texts nutzen.

Eigentlich müssten wir folgerichtig noch das CSS hinterlegen, damit die Komponente ihr gewünschtes Aussehen erhält. Da im CSS keine Besonderheiten zu finden sind und um den Rahmen des Artikels nicht zu sprengen, ist das CSS ausschließlich im zugehörigen GitHub-Repository zu finden.

 

Laden der Daten via Service

Bisher kann die Komponente noch keine Daten darstellen, da wir schlicht den dazugehören Service noch nicht implementiert haben. Das wollen wir ändern, indem wir eine neue Datei poke-api.service.ts anlegen, im gleichen Ordner wie unsere Komponente. Den Inhalt finden wir in Listing 3.

export interface PokeApiListResult<T> {
  count: number;
  next: string;
  previous: string;
  results: T[];
}

export interface Pokemon {
  name: string;
  url: string;
}

export class PokeApiService {
  private readonly pokeBaseUrl = 'https://pokeapi.co/api/v2/';

  loadPage(offset: number, size: number): Promise<PokeApiListResult<Pokemon>> {
    return fetch(`${this.pokeBaseUrl}pokemon?offset=${offset}&limit=${size}`)
      .then(response => response.json());
  }
}

Der PokeApiService ist sehr einfach gestrickt und bietet genau eine Methode an, um eine Seite des PokéAPI zu laden. Hierzu wird die Methode loadPage definiert, die ein offset und size benötigt, um eine konkrete Seite via fetch zu laden. Das Ergebnis wird in JSON gewandelt und zurückgegeben. Da es sich um einen asynchronen Aufruf handelt, ist der Rückgabewert der Methode natürlich ein Promise.

Weiter definiert die Datei noch zwei Interfaces mit den Daten, die wir vom API erwarten. Das sollte für einen ersten Demo-Service genügen und müsste natürlich entsprechend erweitert werden, wenn man dem PokéAPI mehr Daten entlocken möchte.

 

Anpassung der index.html

Im nächsten Schritt müssen wir noch die Datei src/index.html anpassen, sodass auf unserer Demoseite auch wirklich unsere Pokémon-Liste geladen wird. Dazu fügen wir im body-Tag einfach ein neues HTML-Tag ein:

<pokemon-list></pokemon-list>

Wer möchte, kann auch über das Attribute list-title eine andere Überschrift setzen. In Abbildung 4 sehen wir den aktuellen Fortschritt.

 

Abb. 4: Aktueller Fortschritt unserer Pokémon-Liste

 

Erstellung der Pagination

In diesem Schritt wollen wir noch die Pagination erstellen, sodass wir noch eine Interaktion zwischen Komponenten abbilden können. Dazu legen wir eine neue Komponente via Stencil CLI mit den gleichen Einstellungen wie zuvor an. Nur als Name nutzen wir jetzt list-pagination. Den Inhalt der erzeugten Datei list-pagination.tsx ersetzen wir vollständig mit dem Code aus Listing 4.

// import { ... } from '@stencil/core'

@Component({
  tag: 'list-pagination',
  styleUrl: 'list-pagination.scss',
  shadow: true,
})
export class ListPagination implements ComponentInterface, ComponentWillLoad {
  @State() private totalPages: number;
  @State() private currentPage: number;
  @State() private previousPage: number | undefined;
  @State() private nextPage: number | undefined;

  /** The count of all items in the list. */
  @Prop() count: number;

  /** How much items per page shall be shown in the list? */
  @Prop() itemsPerPage: number;

  /** The current offset of the list.*/
  @Prop() offset: number;

  /** Emits, when a paging is triggered. */
  @Event() paging: EventEmitter<{ offset: number }>;

  private handleClick(offset: number): void {
    this.offset = offset;
    this.calculate();
    this.paging.emit({ offset });
  }

  private calculate(): void {
    this.totalPages = Math.ceil(this.count / this.itemsPerPage);
    this.currentPage = Math.floor(this.offset / this.itemsPerPage) + 1;
    this.previousPage = this.currentPage - 1 <= 0 ? undefined : this.currentPage - 1;
    this.nextPage = this.currentPage + 1 >= this.totalPages ? undefined : this.currentPage + 1;
  }

  componentWillLoad(): void {
    this.calculate();
  }

  render() {
    return (
      <Host>
      </Host>
    );
  }
}

Zusätzlich benötigen wir noch den Code aus Listing 5, den wir in die render-Methode einfügen.

<Host>
  <ul>
    <li onClick={() => this.handleClick(0)}>&laquo;</li>

    {
      this.previousPage &&
      <li onClick={() => this.handleClick(this.offset - this.itemsPerPage)}>{this.previousPage}</li>
    }

    <li class="current">{this.currentPage}</li>

    {
      this.nextPage &&
      <li onClick={() => this.handleClick(this.offset + this.itemsPerPage)}>{this.nextPage}</li>
    }

    <li onClick={() => this.handleClick(this.count - this.itemsPerPage)}>&raquo;</li>
  </ul>
</Host>

Schauen wir uns zunächst den Code aus Listing 4 genauer an. Hier fallen zwei Dinge auf, die wir noch nicht kennen: das Interface ComponentWillLoad und den @Event-Dekorator.

ComponentWillLoad, implementiert durch die Methode componentWillLoad, wird aufgerufen, nach dem die Komponente vollständig initialisiert wurde, aber bevor das erste Mal die Methode render aufgerufen wird. In diesem Fall wird die Methode calculate aufgerufen, die auf Basis der definierten Properties @Prop die Pagination berechnet.

Der @Event-Dekorator kann genutzt werden, um ein eigenes CustomEvent zu erstellen. In unserem Fall generieren wir ein Event, sobald eine Seite angeklickt wurde. Dazu setzen wir einen onClick-Handler auf ein HTML-Element (Listing 5), der bei Ausführung die Methode handleClick (Listing 4) ausführt und das dazugehörige Offset übermittelt. Generell müssen alle Events vom Typ EventEmitter sein.

In Listing 5 sehen wir den Code der render-Methode. Er enthält JSX-typische Event Handler und partielle Templates, die nur ausgegeben werden, wenn das dazugehörige Feld gesetzt ist. Hierbei nutzt man die Tatsache, dass in JavaScript das Ergebnis einer booleschen Operation der Inhalt ist, der zuletzt evaluiert wurde. Das ist der Ausdruck, der am weitesten rechts in der Ausdruckskette steht. In unserem Beispiel wird daher überprüft, ob previousPage truthy ist, ist das der Fall, wird geschaut, ob das HTML-Element li truthy ist. Das ist es per Definition und da es der Ausdruck ist, der zuletzt evaluiert wurde, ist das li das Ergebnis unseres impliziten If und wird daher gerendert. Ist previousPage falsy, wird nichts gerendert.

 

Nutzung der Pagination

Nach der Implementierung unserer Pagination-Komponente wollen wir sie in unserer Pokémon-Liste verwenden. Dazu sind zwei Anpassungen an der Datei pokemon-list.tsx nötig, die wir in Listing 6 finden.

// Zusätzliche Methode implementieren
private handlePaging(paging: { offset: number }): void {
  this.offset = paging.offset;
  this.loadPage();
}

// Direkt nach </table> einfügen
<list-pagination 
  count={this.pokemonCount} 
  offset={this.offset} 
  itemsPerPage={this.itemsPerPage}
  onPaging={event => this.handlePaging(event.detail)} 
/>

Hier fügen wir eine Methode handlePaging hinzu, die als Event Handler für das paging-Event unserer list-pagination Komponente dient. Zudem fügt Listing 6 die list-pagination-Komponente ein, sodass wir diese entsprechend nutzen können. Hier sehen wir, dass aus unserem Event paging ein onPaging wurde. Es ist eine Konvention von JSX, dass alle Events mit dem Prefix on versehen werden.

Nach diesen Änderungen ist unsere Entwicklung fertig und wir können unsere Komponenten im Browser ansehen.

 

Dokumentationsgenerierung

Bis hierhin hätten wir auch ein anderes Framework nutzen können, um unsere Web Components zu entwickeln. Die Implementierung wäre gewiss etwas anders, aber das Endergebnis erstmal identisch. Jetzt wollen wir noch eine weitere Stärke von Stencil ausspielen, nämlich die der Dokumentationsgenerierung. Dazu rufen wir auf der Kommandozeile folgenden Befehl auf:

npm run build

Mit diesem Befehl erzeugt Stencil eine ganze Reihe an Ausgaben. Wir erhalten zum einen pro Komponente eine TypeScript-Definitionsdatei zur Autovervollständigung für unsere IDEs. Zum anderen erhalten wir pro Komponente eine Markdown-Datei mit einer Dokumentation über unsere Komponente. Die Markdown-Datei selbst ist zweigeteilt. In einem Teil dürfen wir selbst manuelle Dokumentation hinzufügen. Der zweite Teil ist automatisch aus unserem Code generiert. Properties (@Prop) und Events (@Event) werden korrekt mit Typ und Kommentar dokumentiert, sofern vorhanden. Zudem erhalten wir einen Dependency-Graph zwischen unseren Komponenten, damit sehen wir auf einen Blick, was welche Komponenten benutzt. Sowohl Parent-Child- als auch Child-Parent-Beziehungen werden visualisiert. Die Visualisierung erfolgt auf Basis von in Markdown eingebettetem Mermaid-Graphen, die auch ohne Rendering textlich gut lesbar sind. Ein gerendertes Beispiel der Dokumentation unserer list-pagination Komponente sehen wir in Abbildung 5.

Zu guter Letzt generiert der Befehl auch die entsprechenden Releasepakete unserer Web Components, sodass wir sie überall nutzen können. Es werden bspw. die Formate für CommonJS oder ES Module Import generiert. Es bleiben so kaum Wünsche offen, wenn man die Komponenten wiederverwenden möchte.

 

Abb. 5: Beispieldokumentation von list-pagination

 

Fazit

Auch wenn wir mit diesem Artikel erst an der Oberfläche von Stencil gekratzt haben, haben wir die wichtigsten Punkte gesehen: das Erzeugen von Web Components und die dazugehörige Dokumentation. Beides kommt bei Stencil aus einer Hand. Genau das ist es, was Stencil sehr charmant macht. Es ist ein Allround-Paket und lässt kaum Wünsche offen. Wer noch tiefer eintauchen möchte, kann sich über Dinge wie reaktive Daten oder funktionale Komponenten freuen, all das gepaart mit weiteren Features von Web Components, nur eben auf Steroiden.

Noch viel mehr zu Stencil und Web Components erfahren Sie in den BASTA!-Sessions von Manuel Rauber.

 

The post Web Components auf Steroiden: Maximieren Sie die Leistung und Wiederverwendbarkeit Ihrer Webanwendungen appeared first on BASTA!.

]]>
Die Microfrontend-Revolution https://basta.net/blog/realisierung-von-microfrontends/ Mon, 18 May 2020 14:41:06 +0000 https://basta.net/?p=31986 Die in webpack 5 integrierte Module Federation erlaubt das Laden separat kompilierter Programmteile und das Teilen von Bibliotheken. Damit bietet es endlich eine offizielle Lösung für die Realisierung von Microfrontends.

The post Die Microfrontend-Revolution appeared first on BASTA!.

]]>
Bei der Umsetzung von Microfrontends musste man bis dato ein wenig in die Trickkiste greifen. Ein Grund dafür war bisher, dass aktuelle Build-Tools und Frameworks dieses Konzept nicht kennen. Das sich derzeit in der Betaphase befindliche webpack 5 wird hier jedoch einen Kurswechsel einleiten.

Es erlaubt einen Ansatz namens Module Federation zum Referenzieren von Programmteilen, die zum Kompilierungszeitpunkt noch nicht bekannt sind. Dabei kann es sich auch um eigenständig kompilierte Microfrontends handeln. Außerdem können die einzelnen Programmteile untereinander Bibliotheken teilen, sodass die einzelnen Bundles keine Duplikate beinhalten.

In diesem Artikel zeige ich anhand eines einfachen Beispiels, wie sich Module Federation [2] nutzen lässt. Der Quellcode befindet sich hier.

Beispiel

Das hier verwendete Beispiel besteht aus einer Shell, die in der Lage ist, einzelne separat bereitgestellte Microfrontends bei Bedarf zu laden (Abb. 1).

 

Abb. 1: Shell mit Microfrontend

 

Die Shell wird hier durch die schwarze Navigationsleiste repräsentiert. Das Microfrontend durch den darunter dargestellten eingerahmten Bereich. Außerdem lässt sich das Microfrontend auch ohne Shell starten (Abb. 2).

 

Abb. 2: Microfrontends im Standalone-Modus

 

Das ist notwendig, um ein separates Entwickeln und Testen zu ermöglichen. Außerdem kann es für schwächere Clients wie mobile Endgeräte von Vorteil sein, nur den benötigten Programmteil laden zu müssen.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Funktionsweise

In der Vergangenheit war die Umsetzung von Szenarien wie das hier gezeigte schwierig, zumal Werkzeuge wie webpack davon ausgehen, dass der gesamte Programmcode beim Kompilieren vorliegt. Lazy Loading ist zwar möglich, aber nur von Bereichen, die beim Kompilieren abgespaltet wurden.

Gerade bei Microfrontend-Architekturen möchte man die einzelnen Programmteile jedoch separat kompilieren und bereitstellen. Daneben ist ein gegenseitiges Referenzieren über den jeweiligen URL notwendig. Dazu wären Konstrukte wie dieses hier wünschenswert:

import('http://other-microfrontend');

Da das aus den genannten Gründen nicht möglich ist, musste man auf Ansätze, wie Externals [4] und manuellem Skript-Loading ausweichen. Mit der Module Federation in webpack 5 wird sich das zum Glück ändern.

Die Idee dahinter ist einfach: Ein sogenannter Host referenziert einen Remote über einen konfigurierten Namen (Abb. 3). Auf was dieser Name verweist, ist zum Kompilierungszeitpunkt nicht bekannt.

 

Abb. 3: Der Host greift über einen konfigurierten Namen auf den Remote zu

&nsbp;

Dieser Verweis wird erst zur Laufzeit aufgelöst, indem ein sogenannter Remote Entrypoint geladen wird. Dabei handelt es sich um ein minimales Skript, das den tatsächlichen externen URL für solch einen konfigurierten Namen bereitstellt.

Implementierung des Hosts

Beim Host handelt es sich um eine JavaScript-Anwendung, die einen Remote bei Bedarf lädt. Dazu kommt ein dynamischer Import zum Einsatz. Der Host in Listing 1 lädt auf diese Weise die Komponente hinter mfe1/component.

const rxjs = await import('rxjs');

const container = document.getElementById('container');
const flightsLink = document.getElementById('flights');

rxjs.fromEvent(flightsLink, 'click').subscribe(async _ => {
  const module = await import('mfe1/component');
  const elm = document.createElement(module.elementName);
  [...]
  container.appendChild(elm);
});

Normalerweise würde webpack diesen Verweis beim Kompilieren berücksichtigen und ein eigenes Bundle dafür abspalten. Um das zu verhindern, kommt das ModuleFederationPlugin zum Einsatz (Listing 2).

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

[...]

plugins: [
  new ModuleFederationPlugin({
    name: "shell",
    library: { type: "var", name: "shell" },
    remotes: {
      mfe1: "mfe1"
    },
    shared: ["rxjs"]
  })
]

Mit seiner Hilfe wird der Remote mfe1 (Microfrontend 1) definiert. Dazu stellt die gezeigte Konfiguration ein Mapping zur Verfügung, das den anwendungsinternen Namen mfe1 auf denselben offiziellen Namen abbildet. Jeder Import, der sich nun auf mfe1 bezieht, wird von webpack nicht in die zur Compile Time generierten Bundles aufgenommen.

Bibliotheken, die sich der Host mit den Remotes teilen soll, sind unter shared einzutragen. Im gezeigten Fall handelt es sich dabei um rxjs. Das bedeutet, dass die gesamte Anwendung diese Bibliothek nur ein einziges Mal laden muss. Ohne diese Angabe würde rxjs sowohl in den Bundles des Hosts als auch in jenen aller Remotes landen.

Damit das problemlos funktioniert, müssen sich Host und Remote auf eine gemeinsame Version einigen. Außerdem muss der Host solche Bibliotheken, wie in Listing 1 gezeigt, über einen dynamischen Import laden. Statische Importe wie import * as rxjs from ‘rxjs’; unterstützte das Plug-in nicht, als der vorliegende Text verfasst wurde.

Implementierung des Remotes

Der Remote ist ebenfalls eine eigenständige Anwendung. Im hier betrachteten Fall basiert er auf Web Components (Listing 3).

class Microfrontend1 extends HTMLElement {

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  async connectedCallback() {
    this.shadowRoot.innerHTML = `[…]`;
  }
}

const elementName = 'microfrontend-one';
customElements.define(elementName, Microfrontend1);

export { elementName };

Anstatt Web Components können aber auch beliebige JavaScript-Konstrukte oder Komponenten, die auf Frameworks basieren, zum Einsatz kommen. Die Frameworks lassen sich in diesem Fall auf die gezeigte Weise zwischen den Remotes und dem Host teilen.

Die webpack-Konfiguration des Remotes, die ebenfalls das ModuleFederationPlugin nutzt, exportiert diese Komponente mit der Eigenschaft exposes unter dem Namen component (Listing 4).

output: {
  publicPath: "http://localhost:3000/",
  [...]
},
[...]
plugins: [
  new ModuleFederationPlugin({
    name: "mfe1",
    library: { type: "var", name: "mfe1" },
    filename: "remoteEntry.js",
    exposes: {
      component: "./mfe1/component"
    },
    shared: ["rxjs"]
  })
]

Dazu verweist der Name component auf die entsprechende Datei. Außerdem legt diese Konfiguration für den vorliegenden Remote den Namen mfe1 fest. Zum Zugriff auf den Remote nutzt der Host einen Pfad, der sich aus den beiden konfigurierten Namen, mfe1 und component, zusammensetzt. Somit ergibt sich die oben gezeigte Anweisung import(‘mfe1/component’).

Allerdings muss der Host dazu wissen, unter welchem URL er mfe1 findet. Der nächste Abschnitt gibt darüber Aufschluss.

Host mit Remote verbinden

Um dem Host die Möglichkeit zu geben, den Namen mfe1 aufzulösen, muss dieser einen Remote Entrypoint laden. Dabei handelt es sich um ein Skript, dass das ModuleFederationPlugin beim Kompilieren des Remote generiert.

Der Name dieses Skripts findet sich in der Eigenschaft filename (Listing 4). Der URL des Microfrontend wird aus dem unter output festgelegten publicPath entnommen. Das bedeutet, dass der URL des Remote bei dessen Kompilierung bereits bekannt sein muss. Das ist zwar nicht schön – in vielen Fällen kann man damit aber leben.

Nun ist dieses Skript nur noch in den Host einzubinden:

<script src="http://localhost:3000/remoteEntry.js"></script>

Zur Laufzeit lässt sich nun beobachten, dass die Anweisung import(‘mfe1/component’); im Host dazu führt, dass er den Remote von seiner eigenen URL (hier localhost:3000) lädt (Abb. 4).

 

Abb. 4: Laden des Remote von anderer URL

 

Fazit und Ausblick

Die in webpack 5 integrierte Module Federation füllt eine große Lücke für Microfrentends. Endlich lassen sich separat kompilierte und bereitgestellte Programmteile nachladen und bereits geladene Bibliotheken wiederverwenden.

Die an der Entwicklung solcher Anwendungen beteiligten Teams müssen jedoch manuell sicherstellen, dass die einzelnen Teile auch zusammenspielen. Das bedeutet auch, dass man Verträge zwischen den einzelnen Microfrontends definieren und einhalten, aber auch, dass man sich bei den geteilten Bibliotheken auf jeweils eine Version einigen muss.

Die Notwendigkeit für dynamische Imports führt zu ungewohnten Codestrecken, zumal das Framework der Wahl in der Regel statisch importiert wird. Bei Angular, das den Programmcode mit einem Compiler transformiert, erweist sich dieser Umstand als Showstopper. Bis zur finalen Version ist jedoch noch ein wenig Zeit, um das zu ändern.

Als der vorliegende Artikel geschrieben wurde, war webpack 5 noch in der Betaversion und somit nicht für den Produktionseinsatz reif. Das bedeutet, dass man vorerst noch auf die eingangs erwähnten Tricks setzen muss. Mittel- bis langfristig dürfte die Module Federation jedoch die Standardlösung für Microfrontends im JavaScript-Umfeld werden.

The post Die Microfrontend-Revolution appeared first on BASTA!.

]]>
Einstieg in die fabelhafte Welt der Web Components https://basta.net/blog/einstieg-in-die-fabelhafte-welt-der-web-components/ Tue, 21 Apr 2020 10:19:40 +0000 https://basta.net/?p=31866 Aktuell helfen uns viele Frameworks, im Web Komponenten zu entwickeln. Dabei hat jedes Framework seine individuelle Ausprägung, wie der Code zu strukturieren ist, welche Features oder Lifecycle-Methoden die Komponente hat. Wechseln wir von Framework A zu Framework B, müssen wir mitunter einiges Neues lernen und uns auf die Beschaffenheit des neuen Frameworks einlassen. Mit Web Components zieht ein natives Komponentenmodell in den Browser ein. Ist das die Abhilfe? Und damit der Untergang der Frameworks?

The post Einstieg in die fabelhafte Welt der Web Components appeared first on BASTA!.

]]>

Komponenten – ein Wort, das wir Windows-Entwickler seit vielen Jahren kennen und zu schätzen wissen. Auch im Web ist eine Komponente nichts Neues. Damals, zu jQuery-Zeiten, half uns jQuery, UI-Komponenten zu entwickeln. Heute nutzen wir hier Frameworks wie Angular, React oder Vue. Doch was leistet so eine Komponente eigentlich?

Eine Komponente kapselt Funktion in Form von Code, UI-Struktur und UI-Design. Heruntergebrochen auf das Web bedeutet das z. B. Code in Form von JavaScript oder TypeScript, UI-Struktur mit HTML und UI-Design mit CSS. Mit Hilfe dieser Programmier- und Auszeichnungssprachen erhalten wir wiederverwendbare Komponenten, aus denen wir unsere finale Anwendung komponieren. Oftmals besteht eine Anwendung aus vielen kleinen Komponenten. Jede mit einer eigenen, ganz bestimmten Funktion und oft auch einer Schnittstelle nach außen, sodass der Entwickler Daten in die Komponente geben kann, aber auch Daten aus ihr erhält. Wirft man einen Blick auf die drei großen Single Page Application (SPA) Frameworks, Angular, React und Vue, geben alle drei dem Entwickler ein Komponentenmodell an die Hand. Sei es bei Angular via @Component-Dekorator, bei React via Ableitung von React.Component und bei Vue die Funktion Vue.component(). All das hilft uns, im jeweiligen Framework unsere Business Use Cases in Komponenten zu gießen.

Auch wenn alle drei genannten Frameworks mit dem Komponentenmodell eine Gemeinsamkeit haben, ist die Entwicklung danach grundverschieden. Jedes Framework hat ein eigenes Konzept, wie Daten an das UI übermittelt werden oder auf Benutzereingaben in Form von Events reagiert werden können. Auch die Lifecycle-Methoden unterscheiden sich etwas. Alle Frameworks bieten Methoden an, die in der jeweiligen Komponente aufgerufen werden, wenn diese z. B. zur Anzeige gebracht oder vom Framework wieder zerstört wird. Daneben existieren allerdings weitere Framework-spezifische Lifecycle-Methoden. Für uns Entwickler bedeutet das, dass wir einen Teil unseres Basiswissens, dem Verständnis über das Web, Framework-übergreifend verwenden können. Alles andere müssen wir für jedes Framework immer wieder neu lernen, was gerne auch mal mit dem einen oder anderen Fallstrick verbunden ist.

Web Components – ein natives Komponentenmodell

Bricht man alle Frameworks auf ihre Basisidee herunter, entstanden sie alle aus einem Grund: Dem Nachrüsten eines Komponentenmodells im Browser, da dieser keines zur Verfügung stellt. Das manche Frameworks dabei etwas größer wurden und Mehrwertdienste anbieten, wie z. B. eine Dependency Injection, ist eine individuelle Entscheidung der Framework-Entwickler, um dem Entwickler mehr Funktion an die Hand zu geben. Seit geraumer Zeit entwickeln und etablieren sich drei Standards im Web, die es ermöglichen, dass uns der Browser ein natives Komponentenmodell zur Verfügung stellt: Custom Elements, Shadow DOM und HTML Templates. Vor einiger Zeit hätte man noch einen vierten Standard, HTML Imports, hinzugezählt. Dieser wird allerdings zugunsten von ES Module Imports nicht mehr benötigt. Sehen wir uns diese drei Standards einmal genauer an.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Custom Elements

<div class=”datepicker”></div> – kommt Ihnen das bekannt vor? In früheren Zeiten der Webentwicklung hat diese Angabe, nebst der Einbindung einer JavaScript-Bibliothek und CSS-Dateien, gereicht, um aus einem
<div>-Element einen Datepicker zu machen. Zur Laufzeit der Anwendung wurde dieses
<div> dann um weitere Elemente und Funktionen erweitert. Viel schöner wäre es doch, wenn wir stattdessen <my-datepicker></my-datepicker> schreiben könnten. Und genau das ermöglicht uns das Custom Elements API. Es erlaubt uns, dem Browser neue HTML-Tags beizubringen, die dann mit einem von uns definierten Inhalt gerendert werden. Damit das klappt, brauchen wir die nächsten zwei Standards.

Shadow DOM

Wir alle kennen das Problem im Web: Man entwickelt eine schöne Komponente, bindet sie in eine Drittanbieterwebseite ein und in der Regel passieren zwei Dinge. Erstens sieht unsere Komponente meist nicht mehr so aus, wie wir es wollten. Zweitens sehen plötzlich Dinge auf der Website nicht mehr so aus, wie sie eigentlich sein sollten. Das liegt oft daran, dass zu generische CSS-Selektoren genutzt wurden, die der Browser natürlich auf alle gefundenen Elemente der kompletten Website anwendet. Genau hier setzt Shadow DOM an und bietet Abhilfe. Shadow DOM ist ein Set von JavaScript APIs, die es uns ermöglichen, einen sogenannten Shadow DOM Tree einem HTML-Element anzuhängen, der vom Browser separat und außerhalb des Main Document DOM gerendert wird. Sind im Shadow DOM Tree CSS-Angaben enthalten, werden diese auch nur auf die Elemente innerhalb des Shadow DOM Trees angewendet. CSS-Angaben außerhalb, also sprich im Main Document DOM, haben keine Auswirkungen auf die Elemente innerhalb des Shadow DOM. Dadurch können Komponenten genauso gestylt werden, wie wir es gerne hätten, und wir brauchen keine Sorge haben, dass jemand unsere CSS-Stile überschreibt. Jetzt bleibt nur noch die Frage offen, wie wir eigentlich definieren, welche HTML-Elemente im Shadow DOM angezeigt werden? Dazu benötigen wir den letzten Standard.

HTML Templates

Im Wesentlichen besteht dieser Standard aus zwei HTML-Tags, nämlich <template> und <slot>. Das <template&gt gibt an, welche HTML-Elemente gerendert werden sollen. Den <slot> kann man quasi als Platzhalter im Template sehen, dazu später mehr. Der Unterschied ist allerdings, dass der Browser alles innerhalb des <template>-Tags nicht rendert. Er parst den Inhalt und baut das passende DOM dazu auf, bringt es aber nicht zur Anzeige. Erst mit der eigentlichen, und auch wiederverwendbaren Nutzung des Templates wird es vom Browser gerendert.

Der Zähler – ein Beispiel einer Web Component

Mit Hilfe der drei genannten Technologien sind wir in der Lage, eine eigene wiederverwendbare Web Component zu entwickeln. Und genau das wollen wir in den nächsten Abschnitten machen, um uns so Schritt für Schritt den wichtigsten APIs der Standards zu nähern. Das fertige Beispiel finden Sie auf GitHub. Zur Entwicklung benötigen Sie einen Codeeditor, Node.js und Google Chrome. Auch wenn die Evergreen-Browser mittlerweile beinahe alle APIs unterstützen, empfiehlt es sich für diesen Artikel, Google Chrome zu verwenden. Gemeinsam werden wir eine kleine Zählerkomponente entwickeln, mit einer Anzeige des aktuellen Wertes sowie einem Plus- und Minus-Button. Die Abbildung 1 zeigt den universellen Einsatz unserer Implementierung.

 

Abb. 1: Demo der fertigen Zählerkomponente

Zum Start erstellen wir uns eine Datei mit dem Namen package.json. Den Inhalt entnehmen Sie Listing 1.

{
  "name": "windows-developer-web-components",
  "version": "1.0.0",
  "scripts": {
    "start": "browser-sync start --server src --files src --no-open"
  },
  "dependencies": {},
  "devDependencies": {
    "browser-sync": "^2.26.7"
  }
}

Die wichtigste Angabe hier ist der Startbefehl. Dieser startet die Anwendung browser-sync. Diese Anwendung bietet uns zwei Dinge: Zum einen erhalten wir einen Webserver, der später unsere Web Component ausliefern wird, sodass sie im Browser angezeigt werden kann. Zum anderen bemerkt die Anwendung, wenn innerhalb des Entwicklungsordners eine Änderung stattfindet, und lädt automatisch den Browser neu. So können wir sehr schnell unsere Änderung ansehen, nämlich in dem Moment, in dem wir im Codeeditor auf Speichern drücken.

Damit es wie gewünscht funktioniert, erstellen wir zunächst noch den Ordner src. Ist das geschehen, öffnen wir eine Kommandozeile im Ordner, in dem auch die package.json-Datei liegt. In der Kommandozeile führen wir das Kommando npm install aus, um browser-sync herunterzuladen. Danach führen wir npm start aus, um die Anwendung zu starten. Zu sehen ist natürlich noch nichts. Das wollen wir mit dem nächsten Schritt ändern. Dazu legen wir im Ordner src die Datei index.html an. Den Inhalt der Datei finden Sie in Listing 2.

&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Windows Developer Web Components Demo&lt;/title&gt;
    &lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22counter.js%22%20type%3D%22module%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;lt;script&amp;gt;" title="&amp;lt;script&amp;gt;" /&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;/body&gt;
&lt;/html&gt;

Neben den HTML-Dokument-typischen Angaben sehen wir auch die Angabe eines <script>-Tags, was auf eine Datei counter.js verweist. Hinweis: Diese Datei haben wir noch nicht erstellt. Interessant ist die Angabe von type=“module”. Sie teilt dem Browser mit, dass er die Datei als JavaScript-Modul laden soll. Auch hierbei handelt es sich um eine mittlerweile native Funktion des Browsers. Früher hätte man sein JavaScript als CommonJS- oder AMD-basierte Module bereitgestellt. Heute reicht die Angabe im Browser aus, um jede JavaScript-Datei als Modul zu laden. Module ermöglichen es uns, unsere JavaScript-Programme in kleinere Teile zu zerstückeln und nur bei Bedarf zu laden.

Gut! Wenn wir jetzt im Google Chrome den URL http://localhost:3000 aufrufen, sollten wir zumindest eine weiße Seite mit dem Titel „Windows Developer Web Components Demo“ sehen. Ist das nicht der Fall, prüfen Sie zuerst, ob in Ihrer Kommandozeile beim Ausführen von npm start ein Fehler angezeigt wird. Falls nicht, prüfen Sie, ob auf Ihrer Maschine browser-sync ggf. auf einem anderen Port gestartet wurde. Auch das ist in der Logausgabe der Kommandozeile ersichtlich.

 

 

 

Counter.js ؘ– das Herzstück unserer Komponente

Mit den zwei Dateien zuvor haben wir das Grundgerüst unserer Anwendung geschaffen. Zum einen eine kleine Entwicklungsumgebung, die sich automatisch bei Änderung an Dateien diese neu lädt. Als auch eine kleine Demo-Applikation mit der index.html. Denn diese benötigen wir später für unsere eigentliche Komponente nicht. Sie dient nur dazu, dass wir unsere eigene Komponente sehen, um sie besser entwickeln zu können. Als Nächstes legen wir im Ordner src die Datei counter.js an. In dieser Datei werden wir unsere Web Component entwickeln. In Listing 3 finden wir das Grundgerüst einer jeden Web Component.

class MyCounter extends HTMLElement {
  constructor() {
    super();
  }
}

window.customElements.define('my-counter', MyCounter);

Die Datei startet mit der Definition der Klasse MyCounter. Sie leitet von einer Klasse HTMLElement ab. Die Klasse HTMLElement wird vom Browser zur Verfügung gestellt. Sie dient als Basisklasse für jedes im Browser befindliche HTML-Element, daher auch für unsere. Übrigens, wollten wir z. B. ein spezielles Eingabefeld entwickeln, könnten wir auch von der Klasse HTMLInputElement ableiten. Im MDN findet sich eine ganze Reihe von Interfaces, von denen wir unsere eigene Komponente ableiten können. Damit wir die Vererbungskette ordnungsgemäß einhalten, nutzen wir einen super()-Aufruf in unserem Konstruktor, ähnlich wie base() bei C#.

Am Ende von Listing 3 findet sich das erste API unseres Standards Custom Elements wieder. Es handelt sich hier um die Definition eines neuen HTML-Elements für den Browser. Daher ist dieses API auf dem Objekt window zu finden. Die Methode define() definiert das neue Element. Der erste Parameter gibt den Namen des Elements an. Der zweite Parameter ist eine Konstruktor-Funktion, in dem Fall unsere Klasse MyCounter.

Der Typ von customElements ist eine CustomElementRegistry. Diese ist global für das aktuelle Browsertab. Neben der Definition von neuen Elementen, könnte man prüfen, ob ein bestimmtes HTML-Element registriert wurde, in dem man bspw. customElements.get(‘name-des-elements’) aufruft. Entweder man erhält die Konstruktor-Funktion oder null. Alternativ kann mit customElements.whenDefined(‘name-des-elements’) gewartet werden, bis ein HTML-Element mit dem gewünschten Namen zur Verfügung steht. Die Methode gibt ein Promise zurück, das erfüllt wird, sobald das HTML-Element registriert wurde. Um das Element zu nutzen, können wir im <body>-Bereich in der index.html unser Element via <my-counter></my-counter> aufrufen. Es ist zu beachten, dass Web Components generell ein schließendes Tag benötigen.

HTML Template zur Visualisierung

Durch das Speichern im Codeeditor hat sich unser Browser automatisch neu geladen. In den DevTools von Chrome sehen wir zwar unser HTML-Element, allerdings ist visuell auf der Seite immer noch nichts sichtbar. Es fehlen schließlich noch unser HTML Template und der Shadow DOM. In Listing 4 finden wir ein erstes kleines Template. Dieses wird noch vor der Definition unserer Klasse abgelegt.

const template = document.createElement('template');

template.innerHTML = `
  &lt;/wp-p&gt;
&lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%20%20%20%20.counter-container%20%7B%0A%20%20%20%20%20%20--default-height%3A%20var(--height%2C%2050px)%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20width%3A%20calc(var(--default-height)%20*%203)%3B%0A%20%20%20%20%20%20height%3A%20var(--default-height)%3B%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.counter-container%20%3E%20div%20%7B%0A%20%20%20%20%20%20color%3A%20black%3B%0A%20%20%20%20%20%20font-size%3A%202.2em%3B%0A%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20justify-content%3A%20center%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20.button%20%7B%0A%20%20%20%20%20%20padding%3A%201rem%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20black%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20.value%20%7B%0A%20%20%20%20%20%20margin%3A%200%201rem%3B%0A%20%20%20%20%7D%0A%20%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;lt;style&amp;gt;" title="&amp;lt;style&amp;gt;" /&gt;
&lt;wp-p&gt;

  &lt;slot name=&amp;quot;header&amp;quot;&gt;
    &lt;/wp-p&gt;
&lt;h1&gt;My Counter&lt;/h1&gt;
&lt;wp-p&gt;
  &lt;/slot&gt;

  &lt;/wp-p&gt;
&lt;div class=&amp;quot;counter-container&amp;quot;&gt;&lt;wp-p&gt;
    &lt;/wp-p&gt;
&lt;div class=&amp;quot;button decrement&amp;quot;&gt;-&lt;/div&gt;
&lt;wp-p&gt;

    &lt;/wp-p&gt;
&lt;div class=&amp;quot;value&amp;quot;&gt;
      &lt;slot name=&amp;quot;value-prefix&amp;quot;&gt;&lt;/slot&gt;
      &lt;span class=&amp;quot;value-display&amp;quot;&gt;0&lt;/span&gt;
      &lt;slot name=&amp;quot;value-postfix&amp;quot;&gt;&lt;/slot&gt;
    &lt;/div&gt;
&lt;wp-p&gt;

    &lt;/wp-p&gt;
&lt;div class=&amp;quot;button increment&amp;quot;&gt;+&lt;/div&gt;
&lt;wp-p&gt;
  &lt;/div&gt;
&lt;wp-p&gt;`;

// class MyCounter extends HTMLElement ...

Bitte beachten Sie, dass zur besseren Darstellung im Artikel das Styling der Komponente nicht dem der Abbildung 1 entspricht, sondern abgeändert wurde. Das originale Styling finden Sie im GitHub Repository. Es ist daher auch zu empfehlen, das Styling aus dem Repository zu übernehmen.

Schauen wir uns Listing 4 genauer an. In der ersten Zeile wird via document.createElement() ein HTML Template erstellt. Über den Zugriff auf innerHTML können wir unser Template definieren. Hier können wir neben dem eigentlichen Markup eben auch CSS-Stile hinterlegen, um unsere Komponente zu stylen. Im Kasten „Struktureller Aufbau von Web Components“ finden sich weitere Informationen über den strukturellen Aufbau von Web Components. Im CSS selbst wird zunächst die Variable –default-height definiert. Sie prüft, ob eine Variable –height gesetzt wurde. Falls nicht, wird 50px als Standardwert übernommen. Auf Basis dieser Variable wird die Größe unserer Komponente bestimmt. Im Originalstyling hat die Variable Auswirkung auf weitere Elemente innerhalb der Web Component.

Spannend wird das HTML Markup, da wir hier ein Element sehen, das wir nur innerhalb eines HTML Templates verwenden können. Es handelt sich hierbei um das Element <slot>. Es dient als Platzhalter für Inhalt, den wir später von außen, also in unserer index.html bestimmen können. Wird der Inhalt nicht von außen überschrieben, wird der Inhalt vom <slot>-Element selbst gerendert, in diesem Fall ein

<h1>-Tag. Der name erlaubt uns, gezielt einen bestimmten Slot anzusprechen, den wir überschreiben. Dieses Konzept ist in anderen Frameworks oft unter den Begriffen Transclusion, Content Projection oder Higher-Order Component zu finden. Das weitere Markup ist gewohntes HTML.

Struktureller Aufbau von Web Components

Je nach Framework sind Webentwickler es gewohnt, Code von Template und Styling zu trennen, in zwei oder mehrere Dateien. Im einfachsten Fall von Web Components, wie in diesem Artikel dargestellt, befindet sich alles innerhalb einer Datei. Bei größeren oder komplex gestylten Web Components kann diese Datei daher recht groß werden. Auch der Einsatz von CSS-Prozessoren ist meist schwierig, wenn sich das CSS innerhalb von JavaScript-Dateien wiederfindet. Möchte man bereits erste produktive Web Components entwickeln, empfiehlt sich der Einsatz von Frameworks, die sich mittlerweile auf Web Components spezialisieren. Herausstechend ist hier Stencil.js. Stencil.js lagert das Styling in eine separate Datei aus und hat von Haus aus Unterstützung von verschiedenen CSS-Prozessoren, sodass wir hier auf gewohntem Weg entwickeln können. Des Weiteren bietet Stencil.js einigen syntaktischen Zucker, um wiederkehrende Aufgaben in Web Components einfacher zu gestalten, bspw. das Binden von Daten in die UI oder das Reagieren auf Events wie einen Button-Klick.

SIE WOLLEN MEHR INPUT ZUM THEMA WEB DEVELOPMENT?

Entdecken Sie den BASTA! Web Development Track

 

Shadow DOM

Falls wir zwischenzeitlich unseren Codeeditor gespeichert haben, hat der Browser zwar ein Reload ausgeführt, dennoch ist nichts zu sehen. Das hat auch einen guten Grund: Wir haben zwar ein Template definiert, aber wir nutzen es noch nicht. Das wollen wir ändern. Dazu benötigen wir den Inhalt aus Listing 5. Dieser wird unmittelbar nach dem super() im Konstruktor unserer Klasse eingefügt.

constructor() {
  super();

  this.shadow = this.attachShadow({ mode: 'open' });
  this.shadow.appendChild(template.content.cloneNode(true));
}

Der wohl wichtigste Aufruf ist attachShadow(). Hierdurch wird unserer Komponente ein Shadow DOM angehängt, in das wir etwas platzieren dürfen. Die Angabe des ersten Parameters ist obligatorisch und gibt den Modus des Shadow DOM an. Hier im Beispiel nutzen wir den Modus open. Alternativ gäbe es auch eine Variante closed.

Der Modus bestimmt, ob wir via JavaScript von außen auf das Shadow DOM zugreifen dürfen. Jede Web Component hat ein JavaScript Property shadowRoot. Der Modus bestimmt, ob wir beim Zugriff auf dieses Property den tatsächlichen Shadow DOM Tree erhalten (open) oder ob wir beim Zugriff null erhalten (closed). Jetzt stellt sich die Frage, warum wir uns von außen Zugriff erlauben wollten. Gerade im Hinblick auf die Entwicklung größerer Komponentenbibliotheken kann es durchaus sinnvoll sein, Zugriff auf das Shadow DOM kleinerer interner Komponenten zu erlauben. Allerdings gilt es dann, den Zugriff auf das Shadow DOM zu sperren bei den Komponenten, die später von den Entwicklern eingesetzt werden. Übrigens, JavaScript wäre nicht JavaScript, wenn man nicht über kleine Tricks auch beim closed-Modus an den Shadow DOM Tree kommen könnte. Eine Suchmaschine Ihrer Wahl hilft beim Auffinden solcher kleinen Gemeinheiten.

Nachdem wir nun einen Shadow DOM Tree erzeugt haben, fügen wir mit der letzten Zeile im Listing 5 unser HTML Template ein, in dem wir es klonen. Der Parameter gibt an, ob ein deepClone stattfinden soll, sprich der komplette Baum kopiert wird. Ansonsten erhalten wir nur den Wurzelknoten. Wenn wir jetzt unseren Codeeditor speichern, werden wir zum ersten Mal ein Rendering unserer Komponente sehen. Zugegebenermaßen nicht ganz so hübsch mit dem CSS direkt aus dem Artikel, allerdings umso hübscher mit dem CSS aus dem Repository.

Zurück in die Zukunft: Plain old JavaScript

Auch wenn unsere Komponente gerendert wird, können wir noch nicht mit ihr interagieren. Um unserer Komponente Leben einzuhauchen, benötigen wir den Code aus Listing 6.

constructor() {
  // super &amp; shadow

  this.decrementButton = this.shadow.querySelector('.decrement');
  this.incrementButton = this.shadow.querySelector('.increment');
  this.valueDisplay = this.shadow.querySelector('.value-display');

  this.decrementButton.addEventListener('click', () =&gt; this.decrement());
  this.incrementButton.addEventListener('click', () =&gt; this.increment());
}

connectedCallback() {
  this.render();
}

get value() {
  return +this.getAttribute('value') || 0;
}

set value(v) {
  this.setAttribute('value', v);
  this.render();
}

increment() {
  this.value++;
  this.dispatchEvent(new CustomEvent('valueChange', { detail: this.value }));
}

decrement() {
  this.value--;
  this.dispatchEvent(new CustomEvent('valueChange', { detail: this.value }));
}

render() {
  this.valueDisplay.textContent = this.value;
}

Zunächst holen wir, fast schon etwas altertümlich in Hinblick auf moderne SPA-Frameworks, über den querySelector() alle Elemente, die wir benötigen. Das wären beide Buttons sowie das Element, das den aktuellen Wert der Komponente angeben soll. Danach registrieren wir zwei Event Handler via addEventListener(). Beim Klick auf die jeweiligen Buttons soll eine Funktion ausgeführt werden, nämlich decrement() und increment(). Danach folgt die Implementierung der Lifecycle-Methode connectedCallback(). Sie wird immer dann aufgerufen, wenn der Browser die Komponente im DOM platziert. Sie kann daher für erste Initialisierungen genutzt werden. In unserem Fall ein Aufruf der Methode render(), die wiederum den aktuellen Wert this.value in unser HTML-Element valueDisplay schreibt. Neben dem connectedCallback() existiert auch ein disconnectedCallback(), sprich, wenn die Komponente wieder aus dem DOM entfernt wird, bspw. bei einer konditionalen Anzeige.

Im nächsten Schritt wird ein Getter/Setter value definiert. Dieser schreibt und liest den aktuellen Wert des HTML-Attributs value aus. Beim Lesen des Wertes wenden wir einen kleinen JavaScript-Trick an. HTML-Attribute sind generell als Strings abgebildet. Da wir aber einen numerischen Wert benötigen, nutzen wir das Plus-Zeichen vor dem Aufruf von getAttribute() zur Konvertierung eines Strings zu einer Nummer. Alternativ kann hier auch die Methode parseInt genutzt werden. Beim Setzen des Wertes wird zusätzlich die Methode render() aufgerufen, um den gesetzten Wert zur Anzeige zu bringen. Im Kasten „JavaScript Properties vs. HTML-Attribute“ sind nützliche Informationen zu JavaScript Properties und HTML-Attributen enthalten.

Zu guter Letzt werden unsere increment()– und decrement()-Methoden definiert. Sie erhöhen bzw. verringern den Wert von value. Danach schicken sie ein Event nach außen, das meldet, dass sich der Wert geändert hat. So kann der Nutzer dieser Komponente darauf reagieren, wenn sich der Wert der Komponente ändert. Dazu wird ein CustomEvent erzeugt. Der erste Parameter ist der Name des Events, der zweite dient zur Übermittlung von weiteren Daten. Wichtig hierbei ist, dass es sich um ein Objekt handeln muss, auf dem die Eigenschaft detail definiert ist. Hier – und nur hier – können wir einen einzelnen Wert oder ein Objekt übergeben.

JavaScript Properties vs. HTML-Attribute

Bei Web Components unterscheiden wir zwei verschiedene States. Den JavaScript State in Form von Properties und den HTML State in Form von HTML-Attributen. Mit dem in Listing 6 und Listing 7 gezeigten Pattern synchronisieren wir automatisch beide States. Das bedeutet, wenn wir im HTML das Attribut value setzen, ändern wir auch das Property value in JavaScript. Andersrum, wenn wir im JavaScript this.value setzen, ändern wir auch das HTML-Attribute value. Wir sind nicht gezwungen, den State zu synchronisieren. Standardelemente wie z. B. ein <input>-Element synchronisiert diesen State auch nicht.

Bei einfachen Werten können wir uns eine Synchronisierung erlauben, bei komplexen Werten eher nicht. Denn JavaScript Properties können natürlich nicht nur einfache Werte repräsentieren, sondern auch Arrays und Objekte. HTML-Attribute sind allerdings immer Strings. Würde man daher bspw. die Datenquelle einer tabellarischen Web Component als HTML-Attribut ausgeben, würden wir unter Umständen große Arrays als String darstellen und dem Browser übermitteln. Das zehrt an der Performance.

Das Pattern der Synchronisierung nennt sich Reflecting properties to attributes oder auch Reflected DOM Attributes.

 

Attribute beobachten

Ein kleines Detail fehlt uns noch. Wir können zwar auf unsere Buttons klicken und der angezeigte Wert unserer Komponente wird sich ändern. Ändern wir aber den Wert unserer Komponente im HTML via DevTools, wird nichts passieren. Warum das so ist, entnehmen Sie der Info in Kasten „JavaScript Properties vs. HTML-Attribute“. Um das Problem zu lösen, müssen wir noch eine dritte Lifecycle-Methode anbinden. Diese finden wir in Listing 7 und sie kann bspw. direkt nach dem (aber nicht im) Konstruktor implementiert werden.

static get observedAttributes() {
  return [ 'value' ];
}

attributeChangedCallback(name, oldVal, newVal) {
  if (oldVal === newVal) {
    return;
  }

  if (name === 'value') {
    this.value = newVal;
  }
}

Es handelt sich hier um die Lifecycle-Methode attributeChangedCallback(). Sie hat drei Parameter. Den Namen des geänderten Attributs als String, sowie den alten und den neuen Wert, ebenfalls als Strings, da HTML-Attribute generell als String repräsentiert werden. Zusätzlich benötigen wir den statischen Getter observedAttributes. Dieser liefert ein String-Array mit allen Attributen, die der Browser überwachen soll, um bei einer Änderung die Methode attributeChangedCallback() aufzurufen.

Wie man dem Code der Lifecycle-Methode entnehmen kann, führt jeder Zugriff auf das Attribut zu einer Änderung. So kann es durchaus sein, dass der alte und der neue Wert übereinstimmen. Daher prüfen wir das zunächst und machen nichts, falls sich der Wert nicht ändert. Ändert sich allerdings der Wert und der Name des geänderten Attributs ist value, übernehmen wir diesen Wert.

Achtung: Wie der Kasten „JavaScript Properties vs. HTML-Attribute“ erläutert, nutzen wir hier das Pattern Reflected DOM Attributes. Durch das Setzen des Property value setzen wir auch das HTML-Attribut value. Das hat zur Folge, dass der Browser, da sich das Attribut ändert, wieder attributeChangedCallback() aufruft. Ohne die Prüfung, ob sich der Wert geändert hat, würden wir hier in eine Endlosschleife geraten. Daher müssen wir bei der Anwendung des Patterns sehr vorsichtig sein, um keine Endlosschleife zu erzeugen. Damit wäre die Entwicklung unserer Komponente abgeschlossen.

Anpassung von index.html

Um die Entwicklung abzurunden, nehmen wir noch eine kleine Änderung an der index.html vor. In Listing 8 finden wir den kompletten Inhalt der Datei, den wir auch genau so in unseren Editor übernehmen.

&lt;!DOCTYPE html&gt;
&lt;html lang=&amp;quot;en&amp;quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&amp;quot;UTF-8&amp;quot;&gt;
    &lt;title&gt;Windows Developer Web Components Demo&lt;/title&gt;

    &lt;/wp-p&gt;
&lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%20%20%20%20%20%20my-counter.special%20%7B%0A%20%20%20%20%20%20%20%20--height%3A%20150px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20my-counter.special%20h1%20%7B%0A%20%20%20%20%20%20%09color%3A%20red%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;lt;style&amp;gt;" title="&amp;lt;style&amp;gt;" /&gt;
&lt;wp-p&gt;

    &lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%E2%80%9Ccounter.js%26quot%3B%20type%3D%26quot%3Bmodule%26quot%3B%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;lt;script&amp;gt;" title="&amp;lt;script&amp;gt;" /&gt;


&lt;/em&gt;&lt;/wp-p&gt;
&amp;nbsp;
&lt;h1&gt;Value pre- &amp; postfix&lt;/h1&gt;
&gt;
€

&lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%3E%0A%20%20%20%20%20%20var%20counter%20%3D%20document.querySelector('my-counter')%3B%0A%0A%20%20%20%20%20%20counter.addEventListener('valueChange'%2C%20(%7B%20detail%20%7D)%20%3D%3E%20console.log(detail))%3B%0A%20%20%20%20%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;lt;script&amp;gt;" title="&amp;lt;script&amp;gt;" /&gt;


Wir sehen drei Änderungen: Es wurde etwas CSS definiert, unser Zähler ist zweimal definiert, einmal recht einfach und einmal mit Inhalt. Und zuletzt ein kleines Skript, das auf Werteänderungen des ersten Zählers reagiert. Um diese zu sehen, müssen wir die DevTools öffnen und die Konsole anschauen. Beim Klicken auf den ersten Zähler wird der aktuelle Wert auf der Konsole ausgegeben. Im CSS-Bereich sehen wir, dass wir eine Klasse special erstellt haben. Sie setzt die CSS-Variable height auf 150 Pixel. Daher sollte unser zweiter Zähler im Browser deutlich größer erscheinen als der erste. Wir sind also in der Lage, über CSS-Variablen das Aussehen unserer Komponente von außen zu beeinflussen. Wollten wir das eigentlich nicht vermeiden? Ja – und nein. Wir wollen vermeiden, dass uns jemand ungewollt CSS-Stile überschreibt, weil sie zufällig den gleichen Namen haben. Mit den CSS-Variablen bietet unsere Komponente ein API an, das wir bewusst nach außen geben, um das Aussehen zu ändern. Wir als Komponentenentwickler haben es also damit in der Hand, welche Aspekte unserer Komponente geändert werden dürfen.

Die zweite CSS-Angabe ändert die Textfarbe des <h1>-Tags der zweiten Zählerkomponente. Auch hier können wir uns die Frage stellen, warum wir Zugriff darauf haben, da doch die Überschrift im Shadow DOM enthalten ist. Das ist korrekt, solange die Elemente nicht über einen Slot überschrieben werden, was auch nur möglich ist, wenn wir es als Komponentenentwickler genau so definieren. Um einen Slot zu benutzen, müssen wir das Element oder die Elemente in das Tag unserer Web Component schreiben und das Attribut slot auf einen Namen setzen, den wir in unserer Web Component definiert haben. Ein Slot ändert die Zusammensetzung unseres Shadow DOM. Die geslotteten Elemente werden nämlich nicht in das Shadow DOM kopiert, sondern das Shadow DOM verweist auf die Elemente im Main Document DOM. Das bedeutet, dass die, in diesem Beispiel drei, geslottete Elemente gar nicht zum Shadow DOM gehören, sondern zum Main Document DOM, und damit haben wir per CSS ganz normal Zugriff darauf (mit allen Vor- und Nachteilen).

In Abbildung 2 sind die Chrome DevTools zu sehen. Dort sieht man zwei Dinge: Die überschriebenen Slots liegen außerhalb des Shadow DOM und im Shadow DOM ist ein Verweis auf ein <h1>-Tag zu sehen. Klickt man den Verweis an, landet man bei dem Element, das in den Slot gesetzt wurde. Versuchen Sie einmal via CSS die Buttonfarbe zu ändern. Auch mit !important werden Sie es nicht schaffen, da genau diese Elemente im Shadow DOM liegen und wir kein API dafür definiert haben.

 

Abb. 2: Verweis von Slots aus dem Shadow DOM in das Main Document DOM

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

Fazit

Web Components sind eine tolle Sache, die uns in Zukunft immer mehr begleiten wird. Kleinere Komponenten lassen sich so gänzlich ohne Framework entwickeln. Wir haben aber auch gesehen, dass wir viel händisch machen müssen, was wir von SPA Frameworks als bereits vorhanden gewohnt sind, bspw. Datenbindung oder das Reagieren auf Events. Auch müssen wir uns Gedanken machen, wann wir welchen Teil der UI aktualisieren. In größeren Projekten dürfte das kaum praktikabel sein, da man sehr viel Boilerplate benötigt. Hier lohnt es sich, einmal einen Blick in Stencil.js zu werfen, ein Framework speziell für Web Components mit sinnvollen Features wie CSS-Prozessoren, Dokumentationserzeugung oder Datenbindung, aber ohne weiteren Schnickschnack.

Wir haben auch gesehen, dass wir mit Web Components in der Lage sind, sehr bewusst zu definieren, was wir von außen zugänglich machen und was nicht, wir können hier ein richtiges API für den Entwickler definieren. Das bedeutet aber auch, dass wir unsere Komponenten gut dokumentieren müssen, da aktuell sehr viele Dinge stringbasiert sind bspw. Namen von Slots oder Attributen.

Alles in allem helfen uns die Standards rund um Web Components, native Komponenten zu entwickeln, die in jedem Browser genutzt werden können. Es gibt noch viel mehr zu entdecken, wie bspw. CSS Shadow Parts oder weitere Funktionen der HTML Slots und deren Styling via ::slotted(). Viel Spaß beim Erkunden der Welt von Web Components!

The post Einstieg in die fabelhafte Welt der Web Components appeared first on BASTA!.

]]>
Keynote | Oberflächendimensionen: Alles nur UI? Mitnichten! https://basta.net/blog/oberflaechendimensionen-alles-nur-ui-mitnichten/ Mon, 09 Mar 2020 15:45:38 +0000 https://basta.net/?p=31592 Wer nur auf die Oberfläche schaut, sieht weniger als die Spitze des Eisbergs. Das einfache UI ist nur ein Aspekt der UX (User Experience), die heute für den Erfolg einer Software bei den Nutzern wichtig ist – so weit, so gut. Aber wie sieht es dabei mit der DX (Developer Experience) aus?

The post Keynote | Oberflächendimensionen: Alles nur UI? Mitnichten! appeared first on BASTA!.

]]>
Aber was sind denn die Oberflächendimensionen, die heute für Entwickler wichtig sind? Diese und andere Frage diskutierten Thomas Claudius Huber, Manfred Steyer und Mirko Schrempp zur Eröffnung der BASTA! Spring 2020. Sie spannten dazu den Bogen von technologischen und architekturellen Dimensionen aus Sicht von .NET-Entwicklern, die sich immer mehr Dimensionen öffnen müssen – von den UIs über verschiedene Architekturansätze bis in die Clouds.

Aufgezeigt wurde dabei nicht nur eine Veränderung und Weiterentwicklung innerhalb der Technologien, sondern auch, dass Entwickler umdenken müssen. Heute sollen Anwendungen nicht mehr nur auf dem Desktop oder im Browser laufen, sondern für verschiedene Plattformen verwendbar sein – aber sie müssen es nicht unbedingt. Der Desktop ist nicht tot, WPF und Windows Forms sind wieder da, UWP ist aber auch noch ein wichtiges Thema. .NET Core 3.1 bringt aktuell schon Blazor als C#-basierte Webtechnologie und Ende des Jahres kommt schon .NET 5 als neues, umfassendes Framework für Multiplattform-Projekte. Wie geht man damit um? Nach einer einführenden Diskussionsrunde stellte Thomas Huber zunächst seine Sicht auf die vielen direkten UI-Optionen vor und Manfred Steyer ergänzt in seinem Beitrag diese Dimension mit Überlegungen zu grundlegenden Strategien für das Backend und die Architektur. Damit eröffneten sie den Teilnehmern den Raum, um mit Fragen den konkreten Bedarf im eigenen Unternehmen zu erkunden und auf der BASTA! die passenden Sessions auszuwählen.

 

Weitere interessante Beiträge zu Frontend


● Vom Core zum Frontend

● XAML Islands: WPF und Windows Forms

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

The post Keynote | Oberflächendimensionen: Alles nur UI? Mitnichten! appeared first on BASTA!.

]]>
.NET Core 3.1 ist reif: Vorteile und Neuerungen für Entwickler:innen https://basta.net/blog/net-core-3-1-ist-reif/ Fri, 28 Feb 2020 16:57:30 +0000 https://basta.net/?p=31561 Ungewöhnlich an .NET Core 3.1 ist: Es gibt nur sehr wenige neue Funktionen, sondern vor allem Fehlerbehebungen und sogar inkompatible Änderungen, die es bei einer Version mit Änderung der Versionsnummer an der zweiten Stelle gar nicht geben dürfte. Das gute ist, es ist damit reif für den Einsatz.

The post .NET Core 3.1 ist reif: Vorteile und Neuerungen für Entwickler:innen appeared first on BASTA!.

]]>
.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:

 

  1. Der Microsoft-Entwickler musste zugeben, dass es nicht ausreichend Unit-Tests für die System.Data.SqlClient gibt, also nicht gut getestet wurde.
  2. 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.
  3. 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).

 

Abb. 1: Die .NET-Familie Stand Ende 2019 nach dem Erscheinen von .NET Core 3.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
&nbsp;
<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.

 

Abb. 2: Neue Fehlerleiste in Blazor-Anwendungen in ASP.NET Core

 

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

Tabelle 1: Entfallene und neue Steuerelemente und Klassen
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).

 

Abb. 3: Aktivierung des Windows Forms-Designers für .NET Core in Visual Studio 2019 v16.5 Preview 1

 

Abb. 4: Probleme der Preview des Windows Forms-Designers

 

Abb. 5: Kompletter Absturz der Preview des Windows Forms-Designers

 

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

 

The post .NET Core 3.1 ist reif: Vorteile und Neuerungen für Entwickler:innen appeared first on BASTA!.

]]>
Go, der C#-Killer? https://basta.net/blog/go-der-csharp-killer/ Tue, 07 Jan 2020 15:23:40 +0000 https://basta.net/?p=31326 Es hat sich herumgesprochen, dass wichtige Plattformen wie Docker und Kubernetes mit der Programmiersprache Go geschrieben sind. Kein Wunder, dass viele Entwicklerinnen und Entwickler neugierig geworden sind. In der Stack Overflow Survey 2019 landete Go auf der Liste der populärsten Entwicklungstechnologien auf Platz 13 und damit vor namhaften Sprachen wie Swift, Kotlin oder Dart. In der Rangliste der beliebtesten Sprachen erreicht Go Platz 9 und liegt damit sogar knapp vor C#.

The post Go, der C#-Killer? appeared first on BASTA!.

]]>
Ist es also an der Zeit, C# an den Nagel zu hängen und sich neu zu orientieren? Nein, Go ist kein C#-Killer. Go ist aber auf jeden Fall eine interessante Alternative zu C# auf dem Server. In diesem Artikel werfe ich einen Blick auf Go aus meiner Sicht als Entwickler, der seit Jahren C# nutzt und gern hat.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Wie lernt man Go?

Eine komplette Einführung in Go ist in einem Magazinartikel unmöglich, das soll erst gar nicht versucht werden. Dieser Artikel soll grundlegende Unterschiede und Gemeinsamkeiten zwischen C# und Go aufzeigen. Außerdem soll er durch ein paar Codebeispiele einen Eindruck davon vermitteln, wie sich Go anfühlt. Wer neugierig geworden ist und mehr wissen möchte, sollte einen Blick auf Go by Example werfen. Dort wird Go anhand vieler kleiner Beispiele erklärt. Wer noch tiefer einsteigen möchte, dem empfehle ich die Go-Dokumentation, zum erfolgreichen Starten insbesondere den Artikel Effective Go und die FAQ-Liste.

Natürlich stellt sich für Personen, die in Go einsteigen, auch die Frage nach der Entwicklungsumgebung. Wenn man von C# kommt, liegt Visual Studio Code mit der Go Extension auf der Hand. Viele .NET-Entwicklungsteams schätzen auch die Tools aus dem Hause JetBrains. Mit GoLand hat die Firma eine IDE für Go im Angebot, die im Gegensatz zu Visual Studio Code kostenpflichtig ist. Für Open-Source-Projekte, Schüler, Lehrer, User Groups etc. ist GoLand aber kostenlos.

Grundphilosophie: Code einfach halten

Entwicklungsteams mit C#-Hintergrund wird beim ersten Blick auf Go auffallen, wie reduziert die Sprache und auch das Tooling im Vergleich zu C# sind. So stehen beispielsweise den in C# über hundert Schlüsselwörtern in Go gerade einmal 25 gegenüber. Dieser erste Eindruck täuscht nicht. Die Sprache und den damit erstellten Code einfach und einheitlich zu halten, ist ein wichtiges Designziel von Go. Die Dokumentation des Speichermodells von Go beginnt beispielsweise mit nur drei Zeilen und bevor im Anschluss alle Details erklärt werden, folgt der Hinweis: „If you must read the rest of this document to understand the behavior of your program, you are being too clever. Don’t be clever.“ Go kommt mit einem Tool zum Codeformatieren, das auch Visual Studio Code verwendet. Es hat genau keine Settings. Die Community erwartet, dass jeder Go-Code gleich aussieht, egal woher er kommt. „The output of the current gofmt is what your code should look like, no ifs or buts“. Willkommen in der Denkweise von Go.

Man kann einwenden, dass die Einfachheit daher kommt, dass Go jünger als C# ist. Go kam Ende 2009 heraus, die erste Version von C# sieben Jahr davor. Das ist aber nicht der alleinige Grund. Die relativ gemächliche Weiterentwicklung (nach zehn Jahren ist die Version 2 der Sprache gerade erst in der Designphase), der Prozess des ausführlichen Abwägens von Vorteilen gegenüber hinzugefügter Komplexität bei Spracherweiterungen, die Kultur der offenen Diskussion bei neuen Sprachfunktionen, all das unterstreicht, dass Stabilität und Einfachheit Designprinzipien der Sprache Go sind. Der Vorteil, den Entwicklungsteams davon haben, ist, dass Go, entsprechende generelle Informatikkenntnisse vorausgesetzt, leicht zu erlernen, und Go-Code relativ leicht zu lesen und zu warten ist. In Zeiten von Open Source und Microservices sind das wichtige Eigenschaften einer Sprache, da häufig viele verschiedene Personen im Lauf der Zeit mit dem Code einer Softwarelösung zu tun haben.

Limits

Die Kehrseite der Medaille ist, dass Go im Vergleich zu C# viele Funktionen fehlen. Keine Klassen und Vererbung? Keine Generics? Fehlerbehandlung ohne try-catch-Blöcke? Das lässt einen erst einmal den Kopf schütteln, wenn man von C# kommt. Manche dieser Dinge (z. B. Klassen, Vererbung) gibt es in Go bewusst nicht. Die Sprache hat andere Lösungsansätze für die zugrunde liegenden Herausforderungen. Listing 1 stellt einige dieser Ansätze an einem kleinen kommentierten Codebeispiel vor. Andere Dinge (z. B. Generics) sind in Go schlicht und einfach noch nicht fertig. Das sieht man aber nicht als Katastrophe, im Gegenteil. Es gibt Designs und experimentelle Implementierungen, die über Jahre diskutiert, ausprobiert und oft auch wieder verworfen werden. Lieber ein paar Jahre ein paar zusätzliche Zeilen Code schreiben als eine schlechte Lösung übers Knie brechen.

Listing 1: Struct und Interface

// Play with this code snippet online at https://play.golang.org/p/37kbcROPsZz

package main

import (
  "fmt"
  "math"
)

// Let us define two simple structs. In contrast to C#, there are no classes.
// Everything is a struct. Note that this does not say anything about allocation
// on stack or heap. The compiler will decide for you whether to allocate an
// instance of a struct on the stack or heap based on *escape analysis*
// See also https://en.wikipedia.org/wiki/Escape_analysis.

// ... and yes, dear C# developer, you can believe your eyes:
// No semicolons at the end of lines in Go ;-)

type Point struct {
  X, Y float64
}

type Rect struct {
  LeftUpper, RightLower Point
}

type Circle struct {
  Center Point
  Radius float64
}

// Now let us add some functions to our structs

func (r Rect) Width() float64 {
  return r.RightLower.X - r.LeftUpper.X
}

func (r Rect) Height() float64 {
  return r.RightLower.Y - r.LeftUpper.Y
}

func (r Rect) Area() float64 {
  return float64(r.Width() * r.Height())
}

func (r *Rect) Enlarge(factor float64) {
  // Note that this function has a *pointer receiver type*.
  // That means that it can manipulate the content of r and
  // the caller will see the changed values. The other methods
  // of rect have a *value receiver type*, i.e. the struct
  // is copied and changes to its values are not visible
  // to the caller.

  r.RightLower.X = r.LeftUpper.X + r.Width()*factor
  r.RightLower.Y = r.LeftUpper.Y + r.Height()*factor
}

func (c Circle) Area() float64 {
  return math.Pi * c.Radius * c.Radius
}

// Next, we define an interface. Note that the structs do not need to
// explicitly implement the interface. rect and circle fulfill the
// requirements of the interface (i.e. they have an area() method),
// therefore the implement the interface.

type Shape interface {
  Area() float64
}

const (
  WHITE int = 0xFFFFFF
  RED   int = 0xFF0000
  GREEN int = 0x00FF00
  BLUE  int = 0x0000FF
  BLACK int = 0x000000
)

// Go does not support inheritance of structs/classes. However, it
// supports embedding types. The following struct embeds Circle. Because of
// *member set promotion*, all members of Circle become available on
// ColoredCircle, too.

type ColoredCircle struct {
  Circle
  Color int
}

func (c ColoredCircle) GetColor() int {
  return c.Color
}

type Colored interface {
  GetColor() int
}

func main() {
  // Note the declare-and-assign syntax in the next line. The compiler
  // automatically determines the type of r, no need to specify it explicitly.
  r := Rect{LeftUpper: Point{X: 0, Y: 0}, RightLower: Point{X: 10, Y: 10}}
  c := Circle{Center: Point{X: 5, Y: 5}, Radius: 5}

  // Note that we can access the Radius of the ColoredCircle although Radius
  // is a member of the embedded type Circle.
  cc := ColoredCircle{c, RED}
  fmt.Printf("Colored circle has radius %f\n", cc.Radius)

  // Next, we create an array of shapes. As you can see, rect
  // and circle are compatible with the shape interface
  shapes := []Shape{r, c, cc}

  // Note the use of range in the for loop. In contrast to C#, the for-range
  // loop provides the value and an index.
  for ix, shape := range shapes {
    fmt.Printf("Area of shape %d (%T) is %f\n", ix, shape, shape.Area())

    // Note the syntax of the if statement in Go. You can write
    // declare-and-assign and boolean expression in a single line.
    // Very convenient once you got used to it.
    
    // Additionally note how we check whether shape is compatible
    // with the Colored interface. You get back a variable with the
    // correct type and a bool indicator indicating if the cast was ok.
    if colCirc, ok := shape.(Colored); ok {
      fmt.Printf("\thas color %x\n", colCirc.GetColor())
    }
  }

  r.Enlarge(2)
  fmt.Printf("Rectangle's area after enlarging it is %f\n", r.Area())
}

Einfaches Deployment

Einfachheit ist also eine positive Eigenschaft. Das allein kann die Faszination von Go aber nicht ausmachen. Als C#-Entwickler haben mich Go-Programme von Anfang an durch das fast schon triviale Deployment fasziniert. Wenn man ein Go-Programm kompiliert, erhält man als Ergebnis eine einzelne ausführbare Datei. Die Datei ist in Maschinensprache, es gibt keine Intermediate Language wie in .NET. Die ausführbare Datei hat keinerlei Abhängigkeiten, erfordert am Zielsystem also keine Installation einer Go Runtime. Sie könnte in einem Linux-Container sogar From scratch, also direkt am Linux-Kernel, ausgeführt werden. Man hat es nicht mit hunderten DLLs zu tun, wie man es heutzutage von .NET Core bei Self-contained Deployments kennt. Es gibt kein Herumschlagen mit einem riesigen node_modules-Ordner wie bei Node.js. Man kompiliert mit Go bei Bedarf über Plattformgrenzen hinweg, gerne auch ein Linux Executable auf dem Windows-Entwicklungsrechner.

Ein einfacher Lösungsansatz kommt auch beim Verteilen wiederverwendbarer Packages zum Einsatz. Das go-get-Tool lädt Packages mit ihrem Sourcecode aus einem lokalen Ordner oder direkt aus der Quellcodeverwaltung (z. B. Git, Mercurial). Packages werden immer aus dem Quellcode gebaut. Binary-only Packages wie bei NuGet Packages mit DLLs ohne Sourcecode gab es bei Go früher, sie werden aber nicht mehr unterstützt.

Der Name des Package enthält die Quelle (z. B. github.com/gorilla/mux verweist auf GitHub). Größere Firmen können Module Proxies (z. B. Athens Project) betreiben, über die man steuern kann, welche Pakete verwendet werden dürfen, und die außerdem Package Cache sind. Für kleine Teams wird Google eigene Module Mirrors betreiben.
Die Organisation von Sourcecode am lokalen Entwicklungsrechner hat sich seit Go 1.11 schrittweise verändert. Früher gab es einen einzelnen Ordner (referenziert durch die GOPATH-Umgebungsvariable), in dem alle Pakete sowie der eigene Quellcode in einer definierten Verzeichnisstruktur abgelegt werden mussten. In Go 1.11 und 1.12 wurde diese Einschränkung aufgehoben und Go erhielt ein eigenes Modulsystem, durch das wiederholbare Builds viel einfacher wurden. Ab Go 1.13 (erscheint in Kürze) ist das Modulsystem der Standard.

 

 

 

Pointer

Wenn man aus C# kommt, sind Pointer in Go am Anfang etwas ungewohnt. Man erinnert sich vielleicht mit Schrecken an Fehler aus C-Zeiten, mit denen in C# Schluss war. Keine Angst, Pointer in Go können nicht mit Pointern in C verglichen werden. Es gibt keine Pointer-Arithmetik und Pointer haben klar definierte Typen. Einen Eindruck, wie Pointer in Go funktionieren, bekommt man am besten, wenn man einen Blick auf ein Beispiel wirft. Listing 2 enthält einige Zeilen Go-Code, der Pointer verwendet. Achten Sie auf die Erklärungen in den Codekommentaren.

Listing 2: Pointer

// Play with this code snippet online at https://play.golang.org/p/1CtnUde0Kps

package main

import "fmt"

type person struct {
	firstName string
	lastName string
}

func main() {
  // Let us create an int value and get its address
  x := 42
  px := &amp;amp;x
  // Note the *dereferencing* with the * operator in the next line
  fmt.Printf("x is at address %v and it's value is %v\n", px, *px)

  // Let us double the value in x. Again, we use dereferencing
  *px *= 2
  fmt.Printf("x is at address %v and it's value is %v\n", px, *px)
  
  // We can allocate memory and retrieve a point to it using *new*.
  // The allocated memory is automatically set to zero.
  px = new(int)
  fmt.Printf("x is at address %v and it's value is %v\n", px, *px)

  // Go only knows call-by-value. In the following case, the value
  // is a pointer and the method can dereference it to write something
  // into the memory the pointer points to.
  func(val *int) {
    *val = 42
  }(px)
  fmt.Printf("x is at address %v and it's value is %v\n", px, *px)
  
  // We can also create pointers to structs. Note that although we
  // have a pointer, we can still access the struct's members using
  // a dot.
  pp := &amp;amp;person{"Foo", "Bar"}
  fmt.Printf("%s, %s\n", pp.lastName, pp.firstName)

  // We can pass a pointer to a struct to a method. In this case,
  // the method can change the struct's content.
  func(somebody *person) {
    somebody.firstName, somebody.lastName = somebody.lastName, somebody.firstName
  }(pp)
  fmt.Printf("%s, %s\n", pp.lastName, pp.firstName)
}

Fehlerbehandlung

Über die Fehlerbehandlung in Go-Programmen könnte man lange schreiben. Sie ist auch innerhalb der Go-Community ein oft diskutiertes Thema, und für Go 2 liegen einige Designvorschläge vor, was man daran verbessern könnte. Für jemanden, der von C# kommt, wirkt die Fehlerbehandlung von Go auf den ersten Blick rückschrittlich. Man ist schließlich seit Jahren try/catch gewöhnt. Warum darauf verzichten?

Die Grundidee der Fehlerbehandlung in Go geht von der Überlegung aus, dass Fehlerbehandlung im Code möglichst direkt am Ort der Fehlerentstehung zu finden sein soll. Außerdem muss im Code klar sein, welche Methoden Fehler zurückgeben. Bei try/catch ist das oft nicht der Fall. Die Fehlerbehandlung kann an einer ganz anderen Stelle geschehen als die, an der der Fehler auftrat. Außerdem ist beim Ansehen eines Methodenaufrufs nicht klar, ob die Methode einen Fehlerzustand in Form einer Ausnahme zurückgeben kann oder nicht.

 

SIE LIEBEN C#?

Entdecken Sie die BASTA! Tracks

 

In Go geben Methoden einen Fehlerzustand per Konvention als letzten Rückgabewert zurück. Dazu muss man wissen, dass Go ohne Weiteres mehrere Rückgabewerte bei Methoden unterstützt. Listing 3 zeigt einen kurzen Codeausschnitt, der demonstriert, wie einfache Fehlerbehandlung in Go abläuft. Bitte beachten Sie, dass dieses kurze Beispiel nur an der Oberfläche des Themas kratzt. Wer mehr wissen möchte, dem empfehle ich den Blogartikel „Error handling and Go“.

Listing 3: Fehlerbehandlung

// Play with this code snippet online at https://play.golang.org/p/eGlvSiQJgcF

package main

import (
  "errors"
  "fmt"
)

// The following method returns the result AND an error. The error is nil if
// everything is ok.

func div(x int, y int) (int, error) {
  if y == 0 {
    return -1, errors.New("Sorry, division by zero is not supported")
  }

  return x / y, nil
}

func main() {
  // Here we declare-and-assign the result and the error variable.
  result, err := div(42, 0)
  if err != nil {
    fmt.Printf("Ups, something bad happened: %s\n", err)
    return
  }

  fmt.Printf("The result is %d\n", result)
}

Nebenläufigkeit

Go zeichnet sich durch besonders gute Unterstützung von Concurrency, also Nebenläufigkeit aus. Statt C# Tasks kommen in Go Goroutines zum Einsatz. Eine Goroutine ist eine ganz normale Go-Funktion. Erst der Aufruf mit dem go-Schlüsselwort macht aus der Funktion eine Goroutine. Wie Tasks laufen Goroutines auf einem Pool von Betriebssystem-Threads. Ebenfalls wie Tasks sind Goroutines wesentlich leichtgewichtiger als Threads, insofern kann es viel mehr davon geben.

Spätestens wenn es um das Scheduling geht, enden die Gemeinsamkeiten. In C# werden Tasks typischerweise in Verbindung mit async/await genutzt. Der C#-Compiler erzeugt daraus im Hintergrund eine State Machine. Eine Goroutine kann man sich im Gegensatz dazu als leichtgewichtigen, von der Go-Laufzeitumgebung (nicht vom Betriebssystem!) verwalteten Thread mit eigenem Stack vorstellen. Der Stack einer Goroutine ist aber klein (2kb) und kann je nach Bedarf vergrößert und verkleinert werden. Dadurch geht bei einer größeren Anzahl an Goroutines nicht gleich der Speicher aus.

Go verwendet beim Scheduling von Goroutines kein Zeitscheibenverfahren (Preemtive Scheduling), sondern kooperatives Multitasking (Non-preemtive oder Cooperative Scheduling). Eine Goroutine gibt also zu genau definierten Zeitpunkten die Kontrolle ab (z. B. wenn sie auf das Ergebnis einer blockierenden Operation wartet), wird aber ansonsten nicht unterbrochen.

Channels

Das volle Potenzial von Goroutines erschließt sich erst, wenn man sich mit Go-Channels beschäftigt. Channels sind ein Mittel, wie Goroutines ohne Shared Memory und Locking (Sync Package) miteinander kommunizieren können. Durch sie kann eine Goroutine einer anderen Nachrichten schicken. Man kann auf das Eintreffen einer Nachricht warten (Blocking) oder auf das Vorhandensein einer Nachricht prüfen, ohne zu blockieren (Non-blocking). Channels können Nachrichten auch puffern. Listing 4 zeigt exemplarisch einige Anwendungsfälle von Channels. Bitte beachten Sie beim Lesen die eingefügten Kommentare. Erfahrenen C#-Entwicklerinnen und – Entwicklern empfehle ich, beim Durchsehen des Codes zu überlegen, wie man die jeweilige Aufgabe in C# mit Tasks und async/await lösen würde.

.NET hat im System.Threading.Channels ebenfalls Channels im Angebot. Sie werden im Rahmen der .NET Platform Extensions als eigene NuGet Packages ausgeliefert und kommen zu .NET Core 3 dazu. Im Gegensatz zu Go sind diese Klassen aber nicht speziell in die Sprache C# integriert. In Go sind Channels eine Besonderheit, durch die sich die Sprache von anderen Programmiersprachen abgrenzt, und dementsprechend gut ist die Integration.

Listing 4: Channels

// Play with this code snippet online at https://play.golang.org/p/lHTHywrgcuA

package main

import (
  "fmt"
  "time"
)

func sayHello(source string) {
  fmt.Printf("Hello World from %s!\n", source)
}

// Note that the following method receives a channel through which it can
// communicate its result once it becomes available.

func getValueAsync(result chan int) {
  // Simulate long-running operation (e.g. read something from disk)
  time.Sleep(10 * time.Millisecond)

  // Send result to calling goroutine through channel
  result &amp;lt;- 42
}

// Note that the following method does not return a value. The channel is
// just used to indicate the completion of the asynchronous work.

func doSomethingComplex(done chan bool) {
  // Simulate long-running operation
  time.Sleep(10 * time.Millisecond)

  done &amp;lt;- true
}

func main() {
  // Call sayHello directly and using the *go* keyword on a separate goroutine.
  sayHello("direct call")
  go sayHello("goroutine")

  // Call method on a different goroutine and give it a channel through which it can send back the result.
  result := make(chan int)
  go getValueAsync(result)
  // Wait until result is available and print it.
  fmt.Println(&amp;lt;-result)

  // Do something asynchronously and wait for it to finish
  done := make(chan bool)
  go doSomethingComplex(done)
  // Wait until a message is available in the channel
  &amp;lt;-done
  fmt.Println("Complex operation is done")

  // Note the select statement here. You can use it to wait on
  // multiple channels. In this case, we use a channel from a
  // timer to implement a timeout functionality.
  go getValueAsync(result)
  select {
  case m := &amp;lt;-result:
    fmt.Println(m)
  case &amp;lt;-time.After(5 * time.Millisecond):
    fmt.Println("timed out")
  }

  // Let us print a status message for a certain amount of time.
  ticker := time.NewTicker(100 * time.Millisecond)
  // Note the anonymous function here.
  go func() {
    // Note that Go's range operator supports looping over
    // values received through a channel.
    for range ticker.C {
      fmt.Println("Tick")
    }
  }()
  // Wait for some time and then stop timer.
  &amp;lt;-time.After(500 * time.Millisecond)
  ticker.Stop()
}


Go wofür?

Man sieht, dass Go gegenüber C# teilweise andere Schwerpunkte hat und eine etwas andere Philosophie verfolgt. Einen wichtigen Unterschied habe ich bisher noch nicht ausdrücklich erwähnt: Go spielt zum überwiegenden Teil nur auf dem Server beziehungsweise in der Konsole eine Rolle. Laut der jährlichen Go-Umfrage arbeiten 65 Prozent der Go-Nutzer im Bereich Webentwicklung. Danach folgen die Aufgabengebiete DevOps (41 Prozent) und Systemprogrammierung (39 Prozent). Go wird nur in Ausnahmefällen für GUI-Entwicklung verwendet. Es gibt Packages dafür, in der Regel greift man aber auf webbasierende Benutzerschnittstellen zurück.

 

 

 

Fazit

Joel Spolsky, Mitgründer von Stack Overflow, meinte kürzlich in einem Interview: „Produktivität verbessert man am besten, indem man Komplexität verringert.“ In Zeiten rasend schneller Veränderung und Erweiterung von Plattformen und Programmiersprachen ist der Ansatz von Go erfrischend. Der Fokus liegt auf einer einfachen Sprache, leicht verständlichem Code mit einheitlichem Aufbau, einfachem Deployment und guter Performance. Gleichzeitig bietet die Sprache eine Menge interessanter Features für nebenläufige Programmierung.

In der Praxis ist für eine Plattform nicht nur die Programmiersprache, sondern auch die Verfügbarkeit von Bibliotheken entscheidend. Hier glänzt Go speziell in den Bereichen Webentwicklung (Web-Apps und Web-APIs) sowie System- und Netzwerkprogrammierung. Natürlich ist die Verwendung von Go in Verbindung mit Containern besonders angenehm, da schließlich die Docker-Plattform auf Go aufbaut.

Alles in allem halte ich Go für eine interessante Alternative beziehungsweise Ergänzung zu C#, wenn es um Microservices geht. Die gewohnten Tools wie Visual Studio Code, Azure DevOps und Azure muss man deshalb nicht über Bord werfen, ganz im Gegenteil. Sie alle bringen sehr gute Go-Unterstützung mit.

The post Go, der C#-Killer? appeared first on BASTA!.

]]>
XAML Islands: Integration von WPF und Windows Forms für moderne Anwendungen https://basta.net/blog/xaml-islands-wpf-und-windows-forms/ Tue, 10 Dec 2019 13:47:59 +0000 https://basta.net/?p=30834 Als XAML Islands wird eine mit Windows 10 Version 1903 eingeführte Technologie bezeichnet, mit der sich moderne UWP Controls mit Fluent Design in WPF- und Windows-Forms-Applikationen einbinden lassen. Damit ist es möglich, bestehende Anwendungen zu modernisieren, ohne dass diese gleich von Grund auf neu geschrieben werden müssen. In diesem Artikel lesen Sie, wie Sie XAML Islands einsetzen, und Sie erfahren auch, wie der zukünftige Weg der Universal Windows Platform aussieht.

The post XAML Islands: Integration von WPF und Windows Forms für moderne Anwendungen appeared first on BASTA!.

]]>
Viele Unternehmen haben zahlreiche .NET-Desktopanwendungen im Einsatz, die mit Windows Forms und WPF basierend auf dem .NET Framework entwickelt wurden. Mit .NET Core und XAML Islands lassen sich diese Anwendungen modernisieren. Bevor wir uns in diesem Artikel XAML Islands widmen, werfen wir einen kurzen Blick auf die Migration von .NET-Framework-Anwendungen nach .NET Core.

Migration nach .NET Core

Mit .NET Core 3.0 hat Microsoft sowohl Windows Forms als auch WPF auf .NET Core portiert und darüber hinaus auch den Quellcode auf GitHub veröffentlicht.

Um XAML Islands in einer WPF oder Windows Forms App zu verwenden, ist eine Portierung der eigenen App auf .NET Core nicht zwingend erforderlich, aber als Teil der Modernisierung empfohlen. .NET Core unterstützt mit XAML Islands mehr Szenarien und hat darüber hinaus noch weitere Vorteile. Beispielsweise lassen sich nur mit .NET Core die neuesten, mit C# 8.0 eingeführten Sprachfeatures verwenden. Im .NET Framework stehen diese Sprachfeatures nicht zur Verfügung.

Um eine einfache WPF-Anwendung nach .NET Core zu portieren, ist lediglich eine Anpassung der .csproj-Datei notwendig. Üblicherweise kann der bestehende Inhalt komplett gelöscht und durch den Inhalt aus Listing 1 ersetzt werden.

Listing 1: .NET-Core-Projektdatei

&lt;Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"&gt;
  &lt;PropertyGroup&gt;
    &lt;OutputType&gt;WinExe&lt;/OutputType&gt;
    &lt;TargetFramework&gt;netcoreapp3.0&lt;/TargetFramework&gt;
    &lt;UseWPF&gt;true&lt;/UseWPF&gt;
  &lt;/PropertyGroup&gt;
&lt;/Project&gt;

Listing 1 enthält das sogenannte SDK-Style-Projektformat, das für ein .NET-Core-Projekt zwingend notwendig ist. Das SDK-Style-Projektformat ist daran zu erkennen, dass auf dem Project-Element, das die Wurzel des Dokuments darstellt, das Sdk-Attribut gesetzt ist. In der Projektdatei in Listung 1 wird mit diesem Sdk-Attribut das Windows Desktop SDK von .NET Core verwendet. Zudem ist in der Projektdatei in Listing 1 .NET Core 3 als Target Framework angegeben. Mit dem UseWPF-Element werden in einem .NET-Core-Projekt die Referenzen für die WPF Assemblies hinzugefügt. Wäre es ein Windows-Forms-Projekt, wäre der einzige Unterschied in der Projektdatei, dass statt eines UseWPF-Elements eben ein UseWindowsForms-Element verwendet wird, um damit die Windows Forms Assemblies zu referenzieren.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Nach der Anpassung der .csproj-Datei auf das minimale, in Listing 1 dargestellte Format, müssen natürlich vorherige Projekt- und NuGet-Referenzen wieder hinzugefügt werden, was wie gewohnt über den Solution Explorer von Visual Studio gemacht werden kann. Damit ist die Migration von .NET Framework nach .NET Core abgeschlossen.

In diesem Artikel wird für XAML Islands eine .NET-Core-3.0-WPF-Anwendung verwendet. Bevor es losgeht, werfen wir einen Blick auf die XAML-Islands-Grundlagen.

XAML-Islands-Grundlagen

Mit Windows 10 Version 1903, dem sogenannten May 2019 Update, wurde die XAML Islands API eingeführt. Dabei handelt es sich um eine Windows-Schnittstelle. Mit ihr lässt sich das Aussehen und die Funktionalität einer WPF oder Windows Forms App mit Windows-10-UI-Features erweitern. Die Windows-10-UI-Features werden in Form von UWP Controls bereitgestellt. Es lassen sich beliebige UWP Controls in der bestehenden .NET-Anwendung hosten, wie beispielsweise das Windows 10 MapControl zum Anzeigen von Landkarten oder das InkCanvas Control, um die Windows-10-Stifteingabe optimal in der eigenen .NET-Desktop-Anwendung zu unterstützen.

Prinzipiell lässt sich jedes UWP Control in einer WPF- oder Windows-Forms-Anwendung hosten, wenn es von der UWP-Basisklasse Windows.UI.Xaml.UIElementableitet. Dabei werden folgende Szenarien unterstützt:

  1. das Hosten von 1st Party UWP Controls, die von Microsoft über das Windows SDK oder die sogenannte WinUI-Bibliothek bereitgestellt werden (mehr zur sogenannten WinUI-Bibliothek erfahren Sie später in diesem Artikel, wenn es um die Zukunft der UWP geht)
  2. das Hosten von benutzerdefinierten UWP Controls, wie beispielsweise ein klassisches User Control, das Sie selbst mit UWP entwickelt haben

An dieser Stelle kommt jetzt ein Vorteil von .NET Core ins Spiel. Sowohl .NET Framework als auch .NET Core unterstützen das erste Szenario, das Hosten eines 1st Party UWP Control aus dem Hause Microsoft. Das zweite Szenario, das Hosten eines benutzerdefinierten UWP Control, wird ebenfalls von .NET Framework und .NET Core unterstützt, falls das UWP Control in C++ geschrieben wurde. Wurde im zweiten Szenario das UWP Control jedoch in C# geschrieben, so lässt es sich nur in .NET-Core-basierten WPF/Windows Forms Apps einsetzen, nicht in .NET-Framework-Applikationen. Somit liegt der Unterschied zwischen .NET Framework und .NET Core bei 3rd-Party-Komponenten. Dies ist nochmals in Tabelle 1 übersichtlich dargestellt. Da .NET Core somit mehr Szenarien als .NET Framework unterstützt, empfiehlt sich als Teil einer Modernisierung von WPF- und Windows-Forms-Applikationen im ersten Schritt eine Portierung auf .NET Core.

Desktop-App-Host 3rd-Party-C#-Komponente 3rd-Party-C++-Komponente
.NET Framework Keine Unterstützung Wird unterstützt
.NET Core Wird unterstützt Wird unterstützt

Tabelle 1: Unterstützung von 3rd-Party-Komponenten

XAML Islands einsetzen

Zum Hosten eines UWP Control in einer WPF-Anwendung ist ein Zugriff auf die XAML Islands API notwendig. Der direkte Zugriff auf diese auch als UWP XAML Hosting API bezeichnete Windows-Schnittstelle ist für C#-Entwickler nicht ganz trivial, da diverse Windows-Runtime-Klassen und COM-Interfaces zum Einsatz kommen. Um das Ganze für WPF- und Windows-Forms-Entwickler einfacher zu gestalten, stellt Microsoft mit dem Windows Community Toolkit</a href> verschiedene .NET Controls zur Verfügung, die den Zugriff auf die XAML Islands API kapseln. Microsoft empfiehlt die Verwendung dieser .NET Controls für WPF und Windows Forms Apps, in denen XAML Islands zum Einsatz kommen sollen, da diese .NET Controls eine deutlich komfortablere Entwicklung ermöglichen als ein direkter Zugriff auf die XAML Islands API.

Die Controls aus dem Toolkit

Mit dem Windows Community Toolkit werden zur Unterstützung von XAML Islands in Form von NuGet-Packages zwei Arten von Controls bereitgestellt:

  • Bereits vordefinierte Controls, die UWP Controls kapseln und die sich sofort in der eigenen Anwendung wie ein natives Control verwenden lassen. Diese Controls werden auch als Wrapped Controls bezeichnet.
  • Host Controls, mit denen sich sowohl UWP Controls von Microsoft als auch eigenentwickelte UWP Controls sehr einfach einbinden lassen.

Falls ein sogenanntes Wrapped Control existiert, ist es zu bevorzugen, da es sich wie ein natives WPF oder Windows Forms Control verwenden lässt. Doch welche Controls werden als Wrapped Controls bereitgestellt?

 

 

+

 

Wrapped Controls

Microsoft stellt die in Tabelle 2 dargestellten UWP Controls als Wrapped Controls für WPF und Windows Forms zur Verfügung. Voraussetzung ist, dass die Anwendung unter Windows 10 Version 1903 läuft – das ist ja eine allgemeine Voraussetzung für XAML Islands.

Control Beschreibung
InkCanvas und InkToolbar Stellt das Canvas und die Toolbar bereit, um moderne Stifteingaben zu erlauben, die auf der Windows 10 Ink API basieren
MediaPlayerElement Streamt und rendert Medieninhalte
MapControl Stellt eine interaktive Karte dar

Tabelle 2: Die Wrapped Controls

Die in Tabelle 2 dargestellten Controls lassen sich in einer WPF-Anwendung einfach verwenden, indem das NuGet-Package Microsoft.Toolkit.Wpf.UI.Controls hinzugefügt wird. Das ist alles, was notwendig ist. Während in der Vergangenheit noch weitere Schritte nötig waren, um beispielsweise bestimmte Windows 10 APIs in Form von Windows-Metadata-Dateien (.winmd) zu referenzieren, erledigt Microsoft mittlerweile alles Notwendige beim Hinzufügen dieses NuGet-Packages. Dies kann man als sehr entwicklerfreundlich sehen.

Um die Wrapped Controls in einer Windows-Forms-Anwendung einzusetzen, wird das NuGet-Package Microsoft.Toolkit.Forms.UI.Controls hinzugefügt, anschließend ist das Vorgehen identisch zur WPF. Daher fokussieren sich die folgenden Abschnitte lediglich auf WPF-Anwendungen, die Konzepte gelten jedoch auch für Windows Forms.

Das MapControl in WPF einsetzen

Um das UWP MapControl in einer WPF-Anwendung einzusetzen, wird zunächst das NuGet-Package Microsoft.Toolkit.Wpf.UI.Controls hinzugefügt. Dieses NuGet-Package steht ab Version 6 nicht nur für .NET Framework, sondern auch für .NET Core zur Verfügung. Beim Druck dieses Artikels war Version 6 nur in einer Preview-Version vorhanden, somit muss zum Finden des NuGet-Packages in Visual Studio im NuGet Package Manager das Häkchen zum Suchen von Preview-Versionen gesetzt sein, damit Version 6 in der Suche auftaucht.

Nachdem das NuGet-Package installiert wurde, kann es mit den Wrapped Controls auch schon losgehen. Die Controls lassen sich wie gewöhnliche WPF Controls in der WPF-Anwendung einsetzen.

Listing 2 zeigt die MainWindow.xaml-Datei eines brandneuen WPF-Projekts namens WpfAppMapControl. Neben ein paar standardmäßigen Elementen wie Grid, StackPanel und Button ist darin auch das MapControl-Element zu sehen. Das ist jetzt das Wrapper Control für das UWP MapControl. Um es zu verwenden, wurde in Listing 2 das XAML-Namespace-Präfix controls verwendet. Auf dem Window-Wurzelelement wird dieses controls-Präfix dem CLR Namespace Microsoft.Toolkit.Wpf.UI.Controls zugeordnet.

 

SIE LIEBEN UI?

Entdecken Sie die BASTA! Tracks

 

Listing 2: „MainWindow.xaml“

&lt;Window x:Class="WpfAppMapControl.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;
                         assembly=Microsoft.Toolkit.Wpf.UI.Controls"
    FontSize="20" Title="MainWindow" Height="450" Width="800"&gt;
  &lt;Grid&gt;
    &lt;Grid.ColumnDefinitions&gt;
      &lt;ColumnDefinition Width="200"/&gt;
      &lt;ColumnDefinition/&gt;
    &lt;/Grid.ColumnDefinitions&gt;
    &lt;StackPanel&gt;
      &lt;Button Content="Go to Frankfurt" Margin="10"
                Click="ButtonFranfkurt_Click"/&gt;
      &lt;Button Content="Go to Redmond" Margin="10"
                Click="ButtonRedmond_Click"/&gt;
    &lt;/StackPanel&gt;
    &lt;controls:MapControl x:Name="mapControl"
                       Grid.Column="1"/&gt;
  &lt;/Grid&gt;
&lt;/Window&gt;

Dem MapControl wird in der MainWindow.xaml-Datei in Listing 2 der Name mapControl gegeben. Über diesen Namen lässt es sich in der Code-behind-Datei referenzieren. Listing 2 enthält auch zwei Buttons, um die Karte des MapControl auf verschiedene Städte zu setzen. Ein Button navigiert nach Frankfurt, ein anderer nach Redmond. Listing 3 zeigt den Event Handler aus der Code-behind-Datei für den Frankfurt-Button. Dabei wird auf dem MapControl die TrySetViewAsync-Methode aufgerufen. Ein Geopoint und der ZoomLevel von 12 werden an diese Methode übergeben. Der Konstruktor der Geopoint-Klasse nimmt ein BasicGeoposition-Objekt entgegen, auf dem in Listing 3 die Properties Latitude und Longitude auf die Koordinaten von Frankfurt gesetzt werden. Um die Klassen Geopoint und BasicGeoposition in C# zu verwenden, ist eine using-Direktive für den Namespace Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT notwendig. Visual Studio unterstützt nach dem Schreiben der Klassen Geopoint und BasicGeoposition mit dem Hinzufügen dieses benötigten Namespace.

Listing 3: „MainWindow.xaml.cs“

private async void ButtonFranfkurt_Click(object sender, RoutedEventArgs e)
  {
    var zoomLevel = 12;
    await mapControl.TrySetViewAsync(
      new Geopoint(
        new BasicGeoposition
        {
          Latitude = 50.110924,
          Longitude = 8.682127
        }
      ),
    zoomLevel);
  }

Wird die Applikation gestartet, tritt beim Initialisieren des MainWindow eine Exception auf. Wird zur innersten Exception dieser äußeren Exception navigiert, stößt man als Entwickler auf folgende Fehlermeldung: „Catastrophic failure – WindowsXamlManager and DesktopWindowXamlSource are supported for apps targeting Windows version 10.0.18226.0 and later. Please check either the application manifest or package manifest and ensure the MaxTestedVersion property is updated.“

Wie bereits erwähnt funktionieren XAML Islands ab Windows Version 1903, was der Build-Nummer 10.0.18226.0 oder höher entspricht. Jetzt muss sichergestellt werden, dass die WPF-Anwendung auch unter dieser Windows-10-Version läuft. Dazu gibt es zwei Möglichkeiten:

  • Eine app.manifest-Datei zum WPF-Projekt hinzufügen, die die Windows-Version festlegt
  • Ein separates Windows Application Packaging Project hinzufügen, das die Windows-Version festlegt und die WPF-Applikation als MSIX-Paket verpackt

In diesem Artikel nutzen wir die app.manifest-Datei, da diese Variante kein zusätzliches Projekt benötigt, und die WPF-Anwendung sich weiterhin über eine klassische .exe-Datei verteilen lässt.

Eine „app.manifest“-Datei konfigurieren

Mit einem Rechtsklick auf das WPF-Projekt lässt sich über Add > New Item ein neues Element hinzufügen. Dabei gibt es, wie in Abbildung 1dargestellt, das Element Application Manifest File.


Abb. 1: Das Application Manifest File im New-Item-Dialog

Als Name wird in Abbildung 1 lediglich app.manifest angegeben und die Datei hinzugefügt. Nach dem Hinzufügen ist die Datei nicht nur Teil des Projekts, sondern auch automatisch in der .csproj-Datei als Manifest-Datei angegeben, was in Listing 4 zu sehen ist.

 

Listing 4: „WpfAppMapControl.csproj“

&lt;Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"&gt;
  &lt;PropertyGroup&gt;
    &lt;OutputType&gt;WinExe&lt;/OutputType&gt;
    &lt;TargetFramework&gt;netcoreapp3.0&lt;/TargetFramework&gt;
    &lt;UseWPF&gt;true&lt;/UseWPF&gt;
    &lt;ApplicationManifest&gt;app.manifest&lt;/ApplicationManifest&gt;
  &lt;/PropertyGroup&gt;
  ...
&lt;/Project&gt;

Ein Blick in die app.manifest-Datei zeigt, dass es darin ein compatibility-Element gibt, das, wie in Listing 5 dargestellt, ein application-Element mit verschiedenen auskommentierten Windows-Versionen enthält. Darin lässt sich die OS-ID für Windows 10 aktiv schalten, womit festgelegt ist, dass die WPF-Anwendung nur unter Windows 10 laufen kann.

Listing 5: „app.manifest“

&lt;compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"&gt;
  &lt;application&gt;
    &lt;!-- A list of the Windows versions that this application has been tested on
    and is designed to work with. Uncomment the appropriate elements
    and Windows will automatically select the most compatible environment. --&gt;
    &lt;!-- Windows Vista --&gt;
    &lt;!--&lt;supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /&gt;--&gt;

    &lt;!-- Windows 7 --&gt;
    &lt;!--&lt;supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /&gt;--&gt;

    &lt;!-- Windows 8 --&gt;
    &lt;!--&lt;supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /&gt;--&gt;

    &lt;!-- Windows 8.1 --&gt;
    &lt;!--&lt;supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /&gt;--&gt;

    &lt;!-- Windows 10 --&gt;
    &lt;supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /&gt;

  &lt;/application&gt;
&lt;/compatibility&gt;

Mit der app.manifest-Datei und der Angabe von Windows 10 in dieser Datei lässt sich die WPF-Anwendung jetzt ohne Exception starten. In Abbildung 2 ist das UWP MapControl in der WPF-Anwendung zu sehen. Dabei wurde bereits auf den Button Go to Frankfurt geklickt, womit die Karte nach Frankfurt bewegt wurde.


Abb. 2: Das UWP MapControl in WPF

Damit ist das Einbetten eines Wrapped Control abgeschlossen. Das Vorgehen für Windows Forms ist dabei identisch. Auch die app.manifest-Datei lässt sich in Windows Forms wie in einem WPF-Projekt einsetzen, um Windows 10 als Ziel festzulegen.

CalendarView einbetten

Um ein gewöhnliches UWP Control in WPF zu verwenden, das eben nicht als Wrapped Control bereitgestellt wird, kommt die WindowsXamlHost-Klasse zum Einsatz. In diesem Abschnitt wird gezeigt, wie damit die UWP CalendarView aus dem Namespace Windows.UI.Xaml.Controls in WPF eingebunden wird.

Als erster Schritt muss zum WPF-Projekt das NuGet-Package Microsoft.Toolkit.Wpf.UI.XamlHost hinzugefügt werden, da dieses die WindowsXamlHost-Klasse enthält. In einer Windows-Forms-Applikation wird das NuGet-Package namens Microsoft.Toolkit.Forms.UI.XamlHost benötigt.

Zu beachten ist, falls in einer WPF-Anwendung bereits das Package Microsoft.Toolkit.Wpf.UI.Controls mit den Wrapped Controls referenziert wurde, ist das NuGet-Package Microsoft.Toolkit.Wpf.UI.XamlHost automatisch als Abhängigkeit vorhanden. Es muss in einem solchen Fall nicht explizit nochmals referenziert werden.

Nach dem Hinzufügen des NuGet-Packages lässt sich die WindowsXamlHost-Klasse einsetzen, um ein beliebiges UWP UIElement zu laden. Listing 6 zeigt die MainWindow.xaml-Datei eines WPF-Projekts mit einem WindowsXamlHost-Element. Das Element hat das Präfix xamlHost, das auf dem Window-Element dem CLR Namespace Microsoft.Toolkit-Wpf.UI.XamlHost zugeordnet ist. Auf dem WindowsXamlHost-Element ist die InitialTypeName-Property gesetzt, die den voll qualifizierten Klassennamen des zu erstellenden UI-Elements enthält, in diesem Fall Windows.UI.Xaml.Controls.CalendarView. Das WindowsXamlHost-Element hat den Namen windowsXamlHost, damit über diesen Namen in der Code-behind-Datei auf das WindowsXamlHost-Element zugegriffen werden kann. Wird die CalendarView-Instanz erstellt, wird das ChildChanged-Event ausgelöst. In Listing 6 ist für dieses Event ein Event Handler definiert. In diesem Event Handler soll der Code geschrieben werden, damit der ebenfalls in Listing 6 sichtbare WPF TextBlock mit dem Namen txtSelectedDate das selektierte Datum der UWP CalendarView darstellt.

Listing 6: „MainWindow.xaml“

&lt;Window ...
    xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;
                         assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"&gt;
  &lt;Grid&gt;
    ...
    &lt;xamlhost:WindowsXamlHost x:Name="windowsXamlHost"
      InitialTypeName="Windows.UI.Xaml.Controls.CalendarView"
      ChildChanged="WindowsXamlHost_ChildChanged"
      Margin="10"/&gt;
    &lt;StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10"&gt;
      &lt;TextBlock Text="Selected Date: "/&gt;
      &lt;TextBlock x:Name="txtSelectedDate"/&gt;
    &lt;/StackPanel&gt;
 &lt;/Grid&gt;
&lt;/Window&gt;

Listing 7 zeigt den Event Handler für das ChildChanged-Event. Darin wird auf die Child-Property des WindowsXamlHost-Objekts zugegriffen. Ist diese vom Typ CalendarView, wird auf der CalendarView ein Event Handler für das SelectedDatesChanged-Event installiert. In diesem Event Handler wiederum wird die Text-Property des TextBlock mit dem Namen txtSelectedDate auf den Wert des ausgewählten Datums gesetzt.

 

 

,

 

Listing 7: „MainWindow.xaml.cs“

private void WindowsXamlHost_ChildChanged(object sender, System.EventArgs e)
{
  if (windowsXamlHost.Child
    is Windows.UI.Xaml.Controls.CalendarView calendarView)
  {
    calendarView.SelectedDatesChanged += CalendarView_SelectedDatesChanged;
  }
}

private void CalendarView_SelectedDatesChanged(Windows.UI.Xaml.Controls.CalendarView sender,
  Windows.UI.Xaml.Controls.CalendarViewSelectedDatesChangedEventArgs args)
{
  txtSelectedDate.Text =
    sender.SelectedDates.Count() == 1
    ? sender.SelectedDates.Single().ToString("dd.MM.yyyy")
    : "-";
}

Zum erfolgreichen Ausführen der Anwendung ist wieder eine app.manifest-Datei notwendig, wie es bereits im vorherigen Abschnitt beschrieben wurde. Abbildung 3 zeigt die laufende WPF-Anwendung. Im Hauptteil des Fensters wird die UWP CalendarView dargestellt. Dabei wurde der 24. Dezember ausgewählt. Im unteren Teil des Fensters ist der WPF TextBlock zu sehen, der das ausgewählte Datum ebenfalls anzeigt.


Abb. 3: Die UWP CalendarView in WPF

Auf die in diesem Abschnitt gezeigte Art und Weise lässt sich mit dem WindowsXamlHost-Element jedes in Windows 10 vorhandene UWP Control in einer WPF- oder Windows-Forms-Anwendung einbetten. Doch was ist mit eigenentwickelten UWP Controls?

UWP Controls
werden mehrere UWP Controls eingesetzt, und auf diese viele Event Handler installiert, wird es eventuell mit dem Verwenden von WindowsXamlHost-Elementen etwas unübersichtlich. Dann wären natürlich Wrapped Controls besser. Und natürlich lässt sich auch ein eigenes Wrapped Control für WPF oder Windows Forms schreiben. Dazu muss eine Klasse von WindowsXamlHostBase abgeleitet werden. So ließe sich bspw. eine CalendarViewWrapper-Klasse von WindowsXamlHostBase ableiten, womit sich die UWP CalendarView indirekt über die Wrapper-Klasse einsetzen lässt. Wer in diese Richtung gehen will, schaut am besten die von Microsoft entwickelten Wrapped Controls an, die Open Source sind. Auf GitHub befindet sich der Code, wie beispielsweise jener des MapControl Wrapper.

Eigene UWP Controls hosten

Wie bereits zu Beginn des Artikels erwähnt, ist das Hosten eines eigenen UWP Controls in .NET-Framework-Anwendungen nur dann möglich, wenn das UWP Control in C++ geschrieben wurde. Handelt es sich um ein Managed UWP Control, das in C# geschrieben wurde, lässt es sich nicht in einer .NET-Framework-Anwendung hosten, in einer .NET-Core-Anwendung dagegen schon.

Zum Hosten eines eigenen Control kommt wieder die WindowsXamlHost-Klasse zum Einsatz. Im Folgenden wird ein einfaches, selbstgeschriebenes UWP Control in eine WPF-Anwendung eingebunden. Dazu wird im ersten Schritt eine UWP-Bibliothek mit dem Namen MyUwpControls erstellt, als Minimum-Version wird beim Erstellen Windows 1903 ausgewählt. In der Bibliothek wird das UserControl mit dem Namen SayHelloControl angelegt, das in XAML das in Listing 8 dargestellte StackPanel enthält. Wird in der TextBox des UserControl ein Name eingegeben, wird dieser im TextBlock dank des Data Bindings ausgegeben. Das ist die Funktionalität dieses einfachen Control. Es sagt einfach auf Englisch Hallo, gefolgt von dem eingegebenen Namen.

Listing 8: „SayHelloControl.xaml“

&lt;StackPanel&gt;
  &lt;TextBox x:Name="txt" Header="Firstname" Margin="10"/&gt;
  &lt;StackPanel Orientation="Horizontal" Margin="10"&gt;
    &lt;TextBlock Text="Hello" Margin="0 0 5 0"/&gt;
    &lt;TextBlock Text="{x:Bind txt.Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/&gt;
  &lt;/StackPanel&gt;
&lt;/StackPanel&gt;

Damit das MyUwpControls-Projekt mit XAML Islands eingesetzt werden kann, muss in der .csproj-Datei noch folgende PropertyGroup hinzugefügt werden:

&lt;PropertyGroup&gt;
  &lt;EnableTypeInfoReflection&gt;false&lt;/EnableTypeInfoReflection&gt;
  &lt;EnableXBindDiagnostics&gt;false&lt;/EnableXBindDiagnostics&gt;
&lt;/PropertyGroup&gt;

Damit ist das MyUwpControls-Projekt mit dem SayHelloControl abgeschlossen und bereit. Im nächsten Schritt wird ein .NET-Core-basiertes WPF-Projekt mit dem Namen WpfXamlIslandsThirdParty angelegt. Nach dem Anlegen werden die folgenden, bereits in diesem Artikel beschriebenen und somit bekannten Schritte ausgeführt:

  • Hinzufügen einer app.manifest-Datei und darin Windows 10 aktivieren
  • Referenzieren des NuGet-Packages Microsoft.Toolkit.Wpf.UI.XamlHost

Im nächsten Schritt wird vom WPF-Projekt eine Referenz auf die UWP-Bibliothek MyUwpControls hinzugefügt. Anschließend wird in der MainWindow.xaml-Datei des WPF-Projekts das in Listing 9 dargestellte User Interface definiert. Wie zu sehen ist, befindet sich darin ein WindowsXamlHost-Element, dessen InitialTypeName-Property den Namen MyUwpControls.SayHelloControl enthält. Neben dem WindowsXamlHost-Element enthält die MainWindow.xaml-Datei noch einen WPF TextBlock mit etwas Text.

Listing 9: „MainWindow.xaml“

&lt;Grid&gt;
  &lt;Grid.RowDefinitions&gt;
    &lt;RowDefinition/&gt;
    &lt;RowDefinition Height="Auto"/&gt;
  &lt;/Grid.RowDefinitions&gt;
  &lt;xamlhost:WindowsXamlHost InitialTypeName="MyUwpControls.SayHelloControl"/&gt;
  &lt;TextBlock Grid.Row="1" Margin="10" FontSize="20"&gt;
    Im oberen Teil ist das UWP Control, bestehend aus TextBox und TextBlöcken
  &lt;/TextBlock&gt;
&lt;/Grid&gt;

Die Solution kompiliert, doch beim Starten der Anwendung kommt es in SayHelloControl zur in Abbildung 4 dargestellten Exception, die lediglich sagt: „XAML parsing failed“. Bei einem genauen Blick fällt auf, dass es sich natürlich um eine UWP XamlParseException handelt, die aus dem Namespace Windows.UI.Xaml.Markup stammt.


Abb. 4: XamlParseException in „SayHelloControl“

Das in C# geschriebene UWP Control benötigt ein UWP-XamlApplication-Objekt, und beim Einsatz mit XAML Islands muss dieses Objekt explizit bereitgestellt werden. Dazu muss ein drittes Projekt zur Solution hinzugefügt werden, nämlich eine leere UWP-Anwendung. Diese wird hier einfach UwpApplication genannt, und als Minimum-Version wird beim Erstellen Windows 10 1903 angegeben. Die Solution sieht somit wie in Abbildung 5 dargestellt aus, bestehend aus drei Projekten.


Abb. 5: Die Solution in Visual Studio

Jetzt wird im UwpApplication-Projekt eine Referenz auf das MyUwpControls-Projekt hinzugefügt. Anschließend wird im UwpApplication-Projekt das NuGet-Package Microsoft.Toolkit.Win32.UI.XamlApplication installiert, das Helferklassen für XAML-Islands-Szenarien enthält. Jetzt wird die App.xaml-Datei des UwpApplication-Projekts geöffnet und darin als Wurzelelement anstatt eines Application-Elements ein XamlApplication-Element definiert. Visual Studio schlägt dabei für das XamlApplication-Element den Namespace Microsoft.Toolkit.Win32.UI.XamlHost vor, was zum Resultat in Listing 10 führt. In Listing 10 hat das XamlApplication-Element das Präfix xamlHost, das genau auf jenen CLR Namespace zeigt.

Listing 10: „App.xaml“

&lt;xamlhost:XamlApplication
  xmlns:xamlhost="using:Microsoft.Toolkit.Win32.UI.XamlHost"
  x:Class="UwpApplication.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:UwpApplication"&gt;
&lt;/xamlhost:XamlApplication&gt;

Im nächsten Schritt geht es in der Code-behind-Datei App.xaml.cs des UwpApplication-Projekts weiter. Dort wird sämtlicher Code aus der App-Klasse entfernt, die App-Klasse von XamlApplication abgeleitet und im Konstruktor die Initialize-Methode aufgerufen. Eventuell benötigt Visual Studio den ein oder anderen Rebuild, damit die Initalize-Methode via IntelliSense erscheint.

Listing 11: „App.xaml.cs“

sealed partial class App: Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
{
  public App()
  {
    this.Initialize();
  }
}

Im letzten Schritt wird das UwpApplication-Projekt von der WPF-Anwendung der Solution referenziert, die den Namen WpfXamlIslandsThirdParty trägt. Jetzt ist es an der Zeit, die komplette Solution zu bauen. Üblicherweise schlägt an dieser Stelle der Build fehl, da die WPF-Anwendung mit AnyCPU anstatt mit x64 oder x32 kompiliert wird. Das ist aus den Warnungen im Error-Fenster von Visual Studio ersichtlich. Hier hilft der Configuration Manager von Visual Studio weiter. Abbildung 6 zeigt die angepasste Konfiguration im Configuration Manager, bei der alle Projekte mit x86-Ziel kompiliert werden. Nach dem Speichern der Konfiguration klappt es dann auch mit dem Kompilieren der Solution.


Abb. 6: Im Configuration Manager x86 einstellen

Jetzt steigt die Spannung beim Starten der Anwendung. Die Anwendung startet erfolgreich, und wie erwartet und wie in Abbildung 7 zu sehen, wird im oberen Teil der WPF-Anwendung das UWP Control SayHelloControl bestehend aus TextBox und TextBlock zum Hallo sagen angezeigt.


Abb. 7: Im oberen Teil mit dunklem Hintergrund ist das UWP Control

Somit hat das Einbinden eines eigenen UWP Controls in einer .NET-Core-basierten WPF-Anwendung mit XAML Islands funktioniert. Wie auch bei Standard UWP Controls, für die es kein Wrapped Control gibt, wird die WindowsXamlHost-Klasse eingesetzt.

Von UWP zu WinUI 3.0

Die in diesem Artikel beschriebene Version von XAML Islands ist XAML Islands 1.0, die ab Windows 10 Version 1903 verfügbar ist. Doch in Zukunft werden XAML Islands auch auf älteren Windows-10-Versionen funktionieren. Doch wie das? Um das zu verstehen, muss man auf die Entwicklung der Universal Windows Platform (UWP) schauen.

Microsoft hat schon vor ein paar Jahren festgestellt, dass die Universal Windows Platform das Problem hat, dass Entwickler neue Controls immer nur dann verwenden können, wenn ein Benutzer auch die neue Windows-10-Version installiert hat. Um dieses Problem zu lösen, hat Microsoft verschiedenste UWP Controls von Windows 10 entkoppelt und in Form von NuGet-Packages ausgeliefert. Somit lassen sich neueste Controls auch unter älteren Windows-10-Versionen verwenden. Diese NuGet-Packages sind unter dem Namen WinUI bekannt, und jenes mit den UWP Controls trägt den Namen Microsoft.UI.Xaml.

Die UWP Controls als solches von Windows 10 zu entkoppeln war nur der erste Schritt.
Im nächsten Schritt plant Microsoft, nicht nur UWP Controls via NuGet-Packages bereitzustellen, sondern auch die ganze UWP XAML Runtime und auch die Rendering Pipeline. Also alles, was zu einem kompletten UI Framework gehört. Dieses UI Framework beabsichtigt Microsoft für das Jahr 2020 in der finalen Version zu veröffentlichen. Es trägt den Namen WinUI 3.0 und wird Open Source sein, das Repository gibt es bereits heute auf GitHub.

Microsoft plant in Visual Studio eine neue Projektvorlage namens „Windows Application“, mit der ein WinUI-3.0-Projekt erstellt wird. Das Besondere an WinUI 3.0 ist, dass es sowohl das UWP-Modell mit der Sandbox als auch das klassische Win32-Modell mit gewöhnlicher .exe-Datei unterstützt. Somit ist die aus UWP bekannte Sandbox für WinUI-3.0-Applikationen optional. Diese Sandbox war bei einer Entscheidung zwischen UWP und WPF oft der Grund, warum Unternehmen am Ende WPF genommen haben, da es eine klassische .exe-Datei gibt. Doch mit WinUI 3.0 lässt sich ebenso eine klassische .exe-Datei erstellen, und zudem lassen sich die ganzen modernen UWP Controls und XAML-Features wie kompilierte Data Bindings direkt in einer WinUI-3.0-Anwendung nutzen. WinUI 3.0 ist somit das Beste aus UWP und WPF.

Es sieht also sehr stark danach aus, dass ab 2020 die WinUI 3.0 das moderne und empfohlene UI Framework für Windows-Desktopapplikationen sein wird. Und mit WinUI 3.0 wird Microsoft auch eine neue Variante von XAML Islands einführen, mit der sich WinUI 3.0 Controls problemlos in WPF- und Windows-Forms-Anwendungen integrieren lassen. Und da WinUI 3.0 von Windows 10 entkoppelt ist, wird die nächste Version von XAML Islands auch auf Windows-10-Versionen funktionieren, die älter als Version 1903 sind.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Fazit

Dieser Artikel hat die Grundlagen zu XAML Islands gezeigt. Mit XAML Islands lassen sich WPF- und Windows-Forms-Anwendungen mit UWP Controls modernisieren. .NET Core unterstützt dabei alle Szenarien. Es lassen sich sowohl UWP Controls von Microsoft als auch eigene, in C++, aber auch in C# geschriebene UWP Controls in der eigenen WPF- oder Windows-Forms-Applikation hosten.

XAML Islands erlaubt es, bestehende Applikationen mit Windows-10-Features aufzufrischen, ohne dass diese Anwendungen dafür neu geschrieben werden müssen. Microsoft ist zudem dabei, den kompletten UWP-XAML-Stack von Windows 10 zu entkoppeln und als eigenes, XAML-basiertes UI Framework unter dem Namen WinUI 3.0 bereitzustellen. WinUI 3.0 Controls sind moderne UWP Controls, die sich weiterhin via XAML Islands in WPF und Windows Forms Apps einbinden lassen werden. Für brandneue Windows-Desktopapplikationen stellt WinUI 3.0 wohl ab dem Release im Jahr 2020 die modernste und beste Wahl dar. Microsoft ist und bleibt also auch im Windows-Desktopumfeld innovativ und aktiv, und als Entwickler können wir gespannt und freudig auf die Zukunft mit WinUI 3.0 blicken, und uns freuen, dass bestehende .NET-Applikationen dank XAML Islands auch von dieser Zukunft profitieren können.

The post XAML Islands: Integration von WPF und Windows Forms für moderne Anwendungen appeared first on BASTA!.

]]>
Azure Functions und noch mehr Serverless: Effiziente Entwicklung ohne Serververwaltung https://basta.net/blog/azure-functions-und-noch-mehr-serverless/ Tue, 29 Oct 2019 14:30:04 +0000 https://basta.net/?p=30514 Warum muss es „Serverless oder Microservices“ sein? Es sollte vielmehr „Microservices
mit Serverless“ heißen! Basierend auf einigen der allgemein anerkannten Prinzipien von
Microservices können wir serverlose Architekturen und Technologien verwenden, um
hochfokussierte Microservices zu bauen. Schauen wir uns gemeinsam überblicksmäßig
einen pragmatischen Ansatz zum Erstellen von Microservices mit Azure Functions, Azure
Service Bus, Azure Storage und weiteren Diensten und Tools an. Und das gilt für fast alle
Softwareentwickler: .NET, Java, Node.js und sogar Python.

The post Azure Functions und noch mehr Serverless: Effiziente Entwicklung ohne Serververwaltung appeared first on BASTA!.

]]>
Microservices sind seit einigen Jahren in aller Munde und es wird heftig über deren Vorzüge
aber auch über die damit verbundenen Nachteile diskutiert. Wenn man sich für einen Microservices-
Ansatz entscheidet, stellt sich relativ schnell die Frage nach dem technologischen Lösungsstapel. Mit
Serverless und Functions as a Service existiert seit relativ kurzer Zeit ein alternativer Weg für die
Realisierung.

Microservices – aber pragmatisch

Ohne hier eine komplette Abhandlung über Microservices zelebrieren zu wollen –einige der zentralen Prinzipien für erfolgreiche Microservices-Architekturen möchte ich gerne thematisieren, um dann die Überleitung in die Serverless-Welt daran festmachen zu können. Die vier mir wichtigsten aus Architektursicht sind:

  • Single Responsibility: Ein Microservice macht eine (Business-)Sache, und die macht er richtig. Es ist in der Tat eine hohe Kunst, Microservices richtig zu schneiden und das Gleichgewicht aus Zuständigkiet und Komplexität zu finden.
  • Isolation: Ein Microservice ist isoliert von anderen Services. Das bedingt eine physikalische Trennung und die Möglichkeit, mit dem Microservice über wohl definierte und technologieunabhängige Schnittstellen kommunizieren zu können. Zudem ist es manchmal sinnvoll, eine spezielle Technologie im Innenleben eines Microservices zu nutzen, um Use Cases optimal umsetzen zu können – die Wahl der Technologien pro Microservice sollte über Isolation gewährleistet sein.
  • Autonomie: Die Königsdisziplin bei der Gestaltung von Microservices-Architekturen – jeder Service hat seine eigene Datenhaltung und teilt sich keine Datenbank o. Ä. mit anderen Diensten. Dieses Prinzip konsequent umzusetzen, bedeutet eine weitgehende Entkopplung der Services – die allerdings einen Preis mit sich bringt.
  • Entkopplung: Um Services auf der Prozess- und Kommunikationsebene von einander zu entkoppeln und die Gesamtarchitektur dafür weniger fehlerfällig gestalten zu können, bietet sich der Einsatz asynchroner Kommunikationsmuster z. B. über Message-Queues an.

Vermutlich ist es für Sie als Leser am einfachsten, die Microservices-Prinzipien in einer Serverless-Welt anhand eines kleinen Beispiels vor Augen geführt zu bekommen. Dann schauen wir uns doch mal eins an. Die Beispielanwendung ist auch mit komplettem Sourcecode in einem GitHub Repository zugänglich.

End-to-End-Betrachtungen

Wichtig für die Betrachtungen von Microservices – egal ob Serverless oder nicht – ist eine End-to-End-Sicht auf ein System. Es hilft meist nicht wirklich, sich nur einzelne Services oder gar einzelne Implementierungsdetails von Services anzuschauen: Eine Gesamtbetrachtung umschließt die Clientanwendungen ebenso wie auch nichtfachliche Services wie z. B. ein Authentifizierungs- oder Notifizierungsdienst.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Unser exemplarisches Order-Monitoringsystem (Abb. 1) – das natürlich für den Zweck des Artikels stark vereinfacht dargestellt und implementiert wurde – ist vom Grundgedanken her recht simpel: Wann immer ein angemeldeter Benutzer über die Shoppingwebsite einkauft und eine Bestellung aufgibt, wollen wir mit einer mobilen App – als Cross-Plattform-SPA umgesetzt – als ebenfalls angemeldeter Benutzer benachrichtigt werden. Wir können zudem über diese SPA immer die aktuelle Liste von Bestellungen sowie deren Auslieferungszustand einsehen.

Abb. 1: Serverless-Microservices-Beispiel-Architektur (Order-Monitoring)

Wie in Abbildung 1 zu sehen ist, sind hier fünf Microservices involviert. Die Interaktionen sind über nummerierte Schritte nachvollziehbar. Die Client-SPA ist in diesem Fall nicht als Sammelsurium von Microfrontends umgesetzt, das würde den Rahmen des Beispiels bei weitem sprengen. Vielmehr bedient sich die SPA der unterschiedlichen APIs der Microservices. Für ein vereinfachtes URL-Management und eine bessere Entkopplung der physikalischen Dienste wird in der Gesamtarchitektur ein API-Gateway eingesetzt. Selbstverständlich ist ein API-Gateway kein Muss in einer solchen Architektur, wird aber oft gerne zur weiteren Abstraktion angewendet.

Die erste Frage, die sich stellt, wenn man aus Sicht des Endanwenders auf die Anwendung schaut: Wo kommt die SPA her und wie kommt Sie in meinen Browser?

Zuhause für SPAs: Azure Blob Storage

Eine SPA ist am Ende des Tages ein Paket bestehend aus HTML, CSS, Bildern und anderen Assets. Dieses Paket muss man nicht umständlich und teuer auf einen Webserver oder gar Application-Server deployen. Bei jeder Cloudplattform bietet sich die sehr schlanke und zudem kostengünstige Option, die SPA einfach als statisches Gesamtgebilde über einen Storage-Service bereitzustellen. Im Fall von Microsofts Cloud ist das Azure Blob Storage.

Praxistipp: Azure Portal, CLI und REST API

Für alle Azure Services kann man die Erstellung und die Konfiguration entweder über das Azure Portal, über ein REST API oder aber über das sehr mächtige Azure CLI (Command Line Interface) konfigurieren und automatisieren.

 

Ist der Storage-Account angelegt, kann man das Statische-Website-Feature aktivieren. Danach steht ein Container namens $web zur Verfügung. Dieser dient gewissermaßen als Root-Verzeichnis für einen statischen Webserver, nur eben über Blog Storage bereitgestellt. In Abbildung 2 kann man die Client-SPA aus dem Beispiel sehen, hier über das Werkzeug Azure Storage Explorer manuell in die Cloud deployt.

Abb. 2: Angular SPA über Azure Blob Storage bereitgestellt

Selbstverständlich werden die Endpunkte für den Blob-Storage-basierten Webserver über HTTPS veröffentlicht. Mit zusätzlichen Azure-Diensten wie Azure DNS und Azure CDN lassen sich zudem eigene Domains, SSL-Zertifikate und letztendlich auch superschnelle CDN-Cacheknoten etablieren. Alles, ohne einen einzigen Server anfassen oder gar kennen zu müssen.

Wenn die SPA nun also per HTTPS in unserem Browser läuft und Daten abrufen möchte, dann braucht es freilich APIs und Schnittstellen in unseren Diensten. Die werden wir nun Serverless-like implementieren und bereitstellen.

Microservice-Code und Serverless – Azure Functions

Serverless-Code für Ihre Businesslogik oder Geschäftsprozesse kann man über Azure Functions ausführen. Und Azure Functions können sogar noch mehr, als dort vorgestellt wird. Eine wichtige Eigenschaft ist etwa, dass man nicht nur .NET-Core- und C#-Code schreiben, hochladen und ausführen kann – nein, ebenso lässt sich F#, Java, JavaScript, TypeScript, PowerShell oder aber Python nutzen. Details zu den zum Zeitpunkt des Verfassens dieses Beitrags unterstützten Plattformen und Sprachen finden Sie in Tabelle 1.

Sprache Release Version
C# Final .NET Core 2.2
F# Final .NET Core 2.2
JavaScript Final Node 8 und 10
TypeScript Final (Transpilierung nach JS)
Java Final Java 8
Python Preview Python 3.6
PowerShell Preview PowerShell Core 6

Tabelle 1: In Azure Functions unterstützte Plattformen und Sprachen

Freie Wahl der Technologie

Die Plattform- und Sprachvielfalt ist in einer Microservices-Welt nicht zu unterschätzen. Denn wie wir gelernt haben, ist die freie Wahl der zur Problemstellung passenden Technologien ein mögliche Erfolgsfaktor. Wenn Sie z. B. eine Menge Java-Code haben und den mit in die Serverless Cloud nehmen wollen, dann tun sie das einfach mit Azure Functions. Oder wenn Sie Algorithmen im Umfeld von Data Science Serverless umsetzen möchten, dann können Sie das mit dem Azure-Functions-Python-Support umsetzen. Oder Sie wollen das schier unendliche Universum an Node-Modulen nutzen, dann schreiben Sie JavaScript- oder TypeScript-Code für Azure Functions.

Physikalische Isolation

Damit jeder einzelne Microservice auch wirklich von den anderen isoliert läuft, werden Functions in sogenannte Azure-Functions-Apps gekapselt. Abbildung 3 illustriert eine Azure Ressourcengruppe für die gesamte Serverless-Beispielanwendung. Hier sind alle beteiligten Azure-Ressourcen aufgeführt. In der Summe sind das wesentlich mehr, als wir hier in diesem Artikel behandeln können. Einen wichtigen Part spielen dabei die Azure-Functions-Apps (gekennzeichnet als App Service) für unsere Microservices wie Identity, Notifications, Orders, Products und Shipping (jeweils mit dem Namensschema „cw-serverless-microservices-SERVICENAME“).

Abb. 3: Azure-Ressourcengruppe mit u. a. einzelnen Azure-Functions-Apps für die jeweiligen Microservices

Praxistipp: Azure-Functions-Apps und Functions

Eine Azure-Functions-App basiert auf einem sogenannten Azure App Service, einem der klassischen PaaS-Angebote in Azure. Eine Functions-App ist somit ein komplett eigenständiges physikalisches Deployment. Darin befindet sich die Azure Functions Runtime, immer in einer ganz bestimmten Version und immer nur für eine Plattform (also .NET Core oder Java oder eine der anderen unterstützten Technologien). Ein klassischer Microservice besteht sicherlich aus mehr als einer Function. Daher ist eine Functions-App als Sammelbehälter für beliebige Functions ein geeigneter Container für Serverless Microservices.

 

Wir stellen unsere Microservices also als Verbund von Functions in Functions-Apps bereit. Doch wie kann die Client-SPA mit unseren Serverless Microservices reden?

HTTPS APIs

Wie im erwähnten Artikel von Boris Wilhelms gezeigt wird, kann man mit Azure Functions sehr einfach HTTPS APIs implementieren. Genau das tun wir auch hier für unser Order-Monitoringsystem. Der Orders Microservice hat eine in C# geschriebene Function namens SubmitNewOrder, über die einen Client mittels eines HTTPS POST eine neue Bestellung abschicken kann. Listing 1 zeigt den kompletten Code hierfür. Die Connection-Strings, die im Beispiel verwendet werden, sind nicht im Code, sondern über die Application-Settings abgebildet und im Artikel nicht zu sehen. Vertraut dürfte dem Leser vor allem der HttpTrigger für die POST-Operation in den Zeilen 5-7 vorkommen – wenn man den genannten Artikel gelesen hat.

Listing 1

1    public class SubmitNewOrderFunctions
2    {
3      [FunctionName("SubmitNewOrder")]
4      public async Task<IActionResult> Run(
5        [HttpTrigger(AuthorizationLevel.Anonymous,
6          "POST", Route = "api/orders")]
7        HttpRequest req,
8
9        [ServiceBus("ordersforshipping", Connection="ServiceBus")]
10       IAsyncCollector<Messages.NewOrderMessage> messages,
11
12       ILogger log)
13     {
14       if (!await req.CheckAuthorization("api"))
15       {
16         return new UnauthorizedResult();
17       }
18
19       var newOrder = req.Deserialize<DTOs.Order>();
20       newOrder.Id = Guid.NewGuid();
21       newOrder.Created = DateTime.UtcNow;
22
23       var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
24       var userId = identity.Name;
25
26       var newOrderMessage = new Messages.NewOrderMessage
27       {
28         Order = Mapper.Map<Entities.Order>(newOrder),
29         UserId = userId
30       };
31
32       try
33       {
34         await messages.AddAsync(newOrderMessage);
35         await messages.FlushAsync();
36       }
37       catch (ServiceBusException sbx)
38       {
39         // TODO: retry policy & proper error handling
40         log.LogError(sbx, "Service Bus Error");
41         throw;
42       }
43
44       return new OkResult();
45     }
46   }

Doch was genau passiert da zu Beginn des Funktionskörpers in Zeile 14-17? Die Methode CheckAuthorizationauf dem req-Parameter zieht hier unsere volle Aufmerksamkeit auf sich.

Securitytokens für alle: IdentityServer

Damit wir unser Microservices-System an den API-Grenzen und auch über die entkoppelten Kommunikationswege auf Anwendungsebene absichern können, verwenden wir JSON Web Tokens aus den OAuth-2.0- und OIDC-Standards. Zeile 14 in Listing 1 schaut auf dem HTTP Request in der Azure Functions Pipeline, ob es ein gültiges JWT gibt. Den exakten Code können Sie wie den gesamten restlichen Code für das Beispiel im zugehörigen GitHub Repository einsehen. Doch wo kommt dieses Token ursprünglich her?

Eine Option in Azure für Authentifizierung von Benutzern und Anwendungen ist das Microsoft-eigene Azure Active Directory (AAD) für Organisationsinterne Benutzer und deren Daten, oder aber auch alternativ AAD B2B für die Integration externer Benutzer. Wenn man jedoch mehr Flexibilität und vollen Sourcecodezugriff in Form eines Open-Source-Projekts genießen möchte, kann man zum beliebten IdentityServer-Projekt greifen. IdentityServer ist ein Framework, kein vollwertiger Server oder gar Service. Man passt die Basisfunktionalität an sein jeweiliges Projekt an und hostet es dann beispielsweise in der Cloud, etwa über Azure App Service.

Mit ein paar Kniffen lässt sich IdentityServer – da es ein ganz normales ASP.NET-Core-MVC-Projekt ist – auch in Azure Functions bereitstellen. Abbildung 4 zeigt in recht unspektakulärer Art eine exemplarische IdentityServer-Instanz, jedoch vollständig Serverless über Azure Functions zur Verfügung gestellt. Anders ausgedrückt: ein Wolf im Schafspelz.

Abb. 4: IdentityServer als vollwertiges OAuth 2.0 und OIDC Framework in Azure Functions gehostet – Serverless-Tokens, sozusagen

Über das vom IdentityServer ausgestellte Token können konsumierende Client oder Services Anfragen z. B. an unseren Products oder Orders Service stellen. Diese überprüfen das Token, ob es von einem bekannten Aussteller und noch nicht abgelaufen ist. Zusätzlich kann jeder fachliche Service für sich entscheiden, ob weiter sogenannte Claims überprüft werden müssen, um dem Aufrufer Zugang zur Businesslogik zu gewähren. Dieses Token verwendet nun aber nicht nur die Frontend Services, die direkt von der SPA angesprochen werden, nein – Vielmehr werden Informationen aus dem Token auch bei den entkoppelten und asynchron arbeitenden Microservices wie dem Shipping Service genutzt. Doch wie funktioniert diese viel besagte Entkopplung?

Asynchrone entkoppelte Kommunikation: Azure Service Bus

Für die Entkopplung auf der Zeitachse, also für eine asynchrone Kommunikation und damit die Möglichkeit der asynchronen Ausführung von Code nutzen wir ein Message-Queue-System. Im Fall von Serverless Azure ist das der Azure Service Bus. Service Bus ist ein sehr robuster, weil auch schon seit vielen Jahren existierender Dienst in der Microsoft-Cloud. Er nutzt zum Zwecke der Interoperabilität das allseits anerkannte Advanced Message Queuing Protocol (AMQP) in der Version 1.0. Neben simplen Nachrichtenschlangen (Queues) kann man mit dem Service Bus auch weiterführende Patterns implementieren, etwa Topics und Subscriptions.

Damit wir nun also in unserem Beispiel den Orders Service vom Shipping Service trennen können, legt der Orders Service eine Nachricht in eine Message-Queue namens ordersforshipping. Dies sieht man gut im C#-Code in Listing 1 (Zeilen 19-35). Mit dem API-Aufruf messages.FlushAsync()wird die Service-Bus-Client-Library angewiesen, alle bislang im Code angesammelten Nachrichten in die konfigurierte Queue zu senden. In bester Azure-Functions-Manier ist messagesan ein Functions-Output-Binding gebunden. Dieses Binding für den Service Bus wird im Beispiel asynchron genutzt: Wir nutzen also ein asynchrones API, um asynchron Daten in Nachrichten auszutauschen. Ergibt Sinn.

Ein Blick auf Abbildung 1 verrät uns, dass der Shipping Service nur über diese Message-Queue zu erreichen ist und ansonsten keinerlei API bietet. Das bedeutet, dass er in Ruhe seine Businesslogik für die Abarbeitung von Bestellungen und die Abwicklung von Auslieferungen ausführen kann. Ebenso bedeutet es, dass etwaige Resultate oder fachliche Antworten auf Bestellvorgänge ebenfalls als Nachrichten formuliert werden und in eine neue Queue geschrieben werden müssen.

Während der Orders Service in C# mit .NET Core umgesetzt wurde, handelt es sich beim Shipping Service um eine Java-basierte Lösung. Somit sehen wir auch, dass Azure Functions wirklich über mehrere Plattformen hinweg funktioniert. Listing 2 zeigt die ganze Pracht des simplen Java-Codes in einer Function mit Service-Bus-Trigger und Service-Bus-Output-Binding. Über den Trigger in Zeile 11 lauscht der Java-Code auf der Message-Queue ordersforshipping. Nach Durchlaufen des überaus komplexen Codes mit all seiner aufwendigen Businesslogik wird eine Ergebnisnachricht erzeugt, befüllt und schließlich als Rückgabewert der Function in die Queue shippingsinitiated gesendet (Zeile 19 bis 31).

Listing 2

1    public class CreateShipment {
2      /**
3       * @throws InterruptedException
4       * @throws IOException
5       */
6      @FunctionName("CreateShipment")
7      @ServiceBusQueueOutput(name = "$return",
8        queueName = "shippingsinitiated",
9        connection = "ServiceBus")
10     public String run(
11       @ServiceBusQueueTrigger(name = "message", 
12         queueName = "ordersforshipping", 
13         connection = "ServiceBus")
14       NewOrderMessage msg,
15       final ExecutionContext context
16     ) throws InterruptedException, IOException {
17       // NOTE: Look at our complex business logic ;-)
18       // Yes - do the REAL STUFF here!
19       Thread.sleep(5000);
20
21       ShippingCreatedMessage shippingCreated =
21           new ShippingCreatedMessage();
22       shippingCreated.Id = UUID.randomUUID();
23       shippingCreated.Created = new Date();
24       shippingCreated.OrderId = msg.Order.Id;
25       shippingCreated.UserId = msg.UserId;
26
27       Gson gson = new GsonBuilder().setDateFormat(
28         "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'").create();
29       String shippingCreatedMessage = gson.toJson(shippingCreated);
30
31       return shippingCreatedMessage;
32     }
33   }

Sie sehen und spüren regelrecht, wie sehr man sich mit Azure Functions, dessen Triggers und Bindings sehr stark auf die Implementierung der eigentlichen Logik konzentrieren kann und sich recht wenig mit dem infrastrukturellen Drumherum auseinandersetzen muss. So soll es sein, denn wir wollen ja Softwarelösungen bauen, keine Infrastrukturmonster.

Der Shipping Service hat mittlerweile seine Schuldigkeit getan und seine ihm wichtigen Informationen in einer Message-Queue abgelegt. Dem Orders Service ist das ziemlich egal – aber es gibt jemanden im Gesamtsystem, dem diese Info durchaus wichtig ist – denn der Endanwender hat sich registriert, um über Neuigkeiten bezüglich der Auslieferungen (Shippings) benachrichtigt zu werden.

Webbasierte Realtimebenachrichtigungen: Azure SignalR Service

Durch die Pushnachrichten aus dem mobilen Umfeld unserer Lieblingsapps entfacht, werden Realtimebenachrichtigungen auch im Businesskontext immer wichtiger. Im Order-Monitoringsystem möchte der User der SPA gerne wissen, wenn es einerseits eine neue, erfolgreich erstellte Bestellung gibt, und vor allem andererseits, wenn diese Bestellung zur Auslieferung kommt.

Für Realtimebenachrichtigungen im Webumfeld nimmt man auf Anwendungsebene gerne WebSockets als unterliegende Technologie. WebSockets funktionieren jedoch nicht immer und überall (Browserversionen, Netzwerk-Proxies etc.). Zudem müssen Verbindungsabbrüche selbst behandelt werden und das eigentliche anwendungsspezifische Protokoll zum Datenaustausch muss auch jedes Mal definiert und implementiert werden. Um all diese Anforderungen ein bisschen besser handhabbar zu gestalten, gibt es SignalR vom ASP.NET-Core-Team und den Signal Service vom Azure-Team. Für SignalR gibt es Clientbibliotheken für alle möglichen Plattformen und Programmiersprachen, allen voran natürlich JavaScript. Diese nutzen wir hier in unserer SPA.

Für unsere Azure Functions gibt es zudem Bindings für den Azure SignalR Service, wer hätte das gedacht? Der Notifications Service (vgl. Abb. 1) bedient sich eines Service-Bus-Triggers, um Nachrichten aus der shippingsinitiated Queue zu holen (siehe Listing 3). Diese werden dann in der Function aufbereitet, um anschließend, wie in Zeile 16 zu sehen ist, über das SignalR-Service-Output-Binding über den SignalR Service an die vorher registrierten Clients gesendet zu werden. Fertig.

Die Clients erhalten dann per SignalR-Verbindung (in den meisten Fällen wird das physikalisch eine WebSocket Connection sein) die Information, dass es neue Daten/Argumente für das Ziel shippingInitiated gibt. Der Client kann dann entscheiden, wie der damit umgeht. In der SPA wird einfach ein Event lokal ausgelöst, um danach die aktuelle Liste an Orders mit allen Produktinformationen von den jeweiligen Services zu laden.

Listing 3

1    public class NotifyClientsAboutOrderShipmentFunctions
2    {
3      [FunctionName("NotifyClientsAboutOrderShipment")]
4      public async Task Run(
5        [ServiceBusTrigger("shippingsinitiated", Connection = "ServiceBus")]
6        ShippingCreatedMessage msg,
7
8        [SignalR(HubName="shippingsHub", ConnectionStringSetting="SignalR")]
9        IAsyncCollector<SignalRMessage> notificationMessages,
10
11       ILogger log)
12     {
13       var messageToNotify = new { 
14         userId = message.UserId, orderId = message.OrderId };
15
16       await notificationMessages.AddAsync(new SignalRMessage
17       {
18         Target = "shippingInitiated",
19         Arguments = new[] { messageToNotify }
20       });
21     }
22   }

Wir haben bis hierhin schon einiges umgesetzt bekommen mit unserer Idee von Serverless Microservices. Und das Schöne an Serverless ist eben immer wieder, dass man komplexe verteilte Architekturen relativ schnell und unkompliziert umgesetzt bekommt. Hier ein Service, da ein Service. Hier ein Client, da eine Queue, dort ein Notification-Dienst. Doch wer behält da den Überblick? Was passiert wo und wie?

„Ich sehe was, was du nicht siehst …“ – Azure Application Insights

Sicherlich hat eine derart verteilte und lose gekoppelte Architektur nicht nur Vorteile. Die Sicht auf ein solches System zur Laufzeit ist enorm wichtig, um beispielsweise Fehler mitzubekommen und darauf reagieren zu können. Für Azure können wir uns in diesem Fall der Application Insights bedienen (diese werden gerade in den sogenannten Azure Monitor integriert).

Mit Application Insights können wir vor allem das Laufzeitverhalten unserer Systeme monitoren. Für Fehler oder auftretenden Schwellwerten bei spezifischen Metriken können wir auch Regeln festlegen, was darauffolgend passieren soll –beispielsweise das Versenden von Emails o. Ä. Im Rahmen unserer Serverless-Beispielanwendung für diesen Artikel werfen wir mal einen Blick auf das werte Befinden unseres Identity Services. Über den Live Metrics Stream können wir tatsächlich in Echtzeit in das Herz unseres Identity Services blicken.

Wie man in Abbildung 5 sehen kann, bekommen wir Einsicht in die Telemetriedaten, Requests, Speicher- und CPU-Auslastung und auch auf die automatisch verwaltete Anzahl von Serverinstanzen in unserer Serverless-Azure-Functions-Anwendung. Eine Info, die uns ja eigentlich egal sein sollte, den wir wollen ja explizit nichts mehr mit Servern zu tun haben. Aber es ist durchaus interessant zu beobachten, wie sich Azure Functions unter Last verhält. Im Screenshot sieht man, dass wir ca. 800-900 Request pro Sekunde verarbeiten und die Azure Functions Runtime bzw. der Scale-Controller mittlerweile auf bis zu zwölf Serverinstanzen skaliert hat – ohne jegliches Zutun unsererseits. Und wenn die Last abnimmt, dann wird der Scale Controller auch wieder für das Scale-In sorgen. Wichtig ist, nochmals festzuhalten, dass wir nur dann Geld bezahlen, wenn unser Code auch wirklich ausgeführt wird, egal, wie viele Server in der Zwischenzeit Däumchen drehen.

Abb. 5: Echtzeiteinblicke in Serverless Microservices mit Application Insights Live Metrics

End-to-End Microservices mit Serverless

Wenn man nun alle der in Abbildung 1 aufgemalten Artefakte nimmt und sie auf Azure Services abbildet, landet man bei Abbildung 6: Die Umsetzung des Serverless-Order-Monitoringsystems mit Serverless Azure – voilà.

Im Verlauf des Artikels haben wir uns gemeinsam angeschaut, wie man auf pragmatische Art und Weise End-to-End Microservices mit Serverless und der Azure-Cloud umsetzen kann. Mit Azure Functions und den dazu passenden anderem Azure-Diensten ist das mit relativ wenig Aufwand in kurzer Zeit möglich. Natürlich steckt der Teufel im Detail, keine Frage. Und selbstverständlich kann jedwede Darstellung im Rahmen einess Beitrags nur ein Kratzen an der Oberfläche sein. Daher zum Schluss nochmals der Aufruf, einen Blick in den Sourcecode des Beispiels zu werfen.

Abb. 6: Serverless-Microservices-Beispiel – umgesetzt mit Serverless-Azure-Diensten

 

Azure-DevOps-Spickzettel


Der Azure-DevOps-Spickzettel von Dr. Holger Schwichtenberg zeigt Ihnen kurz und knapp, wie Sie mit wenigen Befehlen das Kommando über Azure DevOps übernehmen können.

Download for free

The post Azure Functions und noch mehr Serverless: Effiziente Entwicklung ohne Serververwaltung appeared first on BASTA!.

]]>
Keynote | Vom Core zum Frontend: Nahtlose Integration für moderne Webanwendungen https://basta.net/blog/vom-core-zum-frontend/ Wed, 09 Oct 2019 12:31:41 +0000 https://basta.net/?p=30277 Der Blick in die Zukunft zeigt immer wieder, dass Veränderungen - und auch die damit einhergehenden Überraschungen - bei Microsoft Programm sind. Das gilt auch für den Blick in das kommende Jahr, mit dem Dr. Holger Schwichtenberg und Jörg Neumann am 24. September die BASTA! in Mainz eröffnet haben.

The post Keynote | Vom Core zum Frontend: Nahtlose Integration für moderne Webanwendungen appeared first on BASTA!.

]]>

Ein Schwerpunkt der BASTA! 2019 war .NET Core 3.0, das passender Weise am Abend vor der Eröffnungskeynote von Microsoft released wurde. Daher lautete dann auch das Thema der Keynote „Vom Kern zum Frontend“ – mit den Speakern Dr. Holger Schwichtenberg, www.IT-Visions.de/5Minds IT-Solutions, und Jörg Neumann, CGI Deutschland B.V. & Co. KG.

Zum Einstieg gibt „Dotnet-Doktor“ Holger Schwichtenberg einen Überblick über die sich wandelnde .NET-Landschaft – von .NET Framework, über das eben veröffentlichte .NET Core 3.0 bis hin zu .NET 5.0 mit dem geplanten Release-Termin im November 2020. In Summe stehen .NET-Entwicklern damit ab dem kommenden Herbst grob drei unterschiedliche .NET-Versionen zur Verfügung, zwischen denen sie sich entscheiden müssen – wobei Holger ganz klar empfiehlt, dass man jedes neue Projekt aktuell mit .NET Core 3 beginnen sollte.

Das Frontend wird vielfältiger

Im zweiten Teil der Eröffnung widmete sich Jörg Neumann den Themen Micro Frontends und Cross-Plattform-Entwicklung mit Hilfe einer Frontend-Technologie-Matrix, um etwas Ordnung in die Vielzahl der Möglichkeiten zu bringen. Denn als .NET-Entwickler hat man heute in der Regel mehr als eine Zielplattform und sogar auf dem Windows Desktop bieten sich immer mehr Optionen an, zwischen denen es mit Blick auf die Zukunft zu wählen gilt.

Weitere Überraschungen kommen sicher

Im Hinblick auf die Zukunft weisen die beiden Themen auf die Veränderungen hin, die im nächsten Jahr für Entwickler relevant werden. Windows 7 wird im Januar aus dem Support auslaufen, sodass weitgehend Windows 10 auf dem Desktop eingesetzt werden wird. Dank einiger vorgestellter Neuerungen in .NET Core 3 können Entwickler bestehende WPF- und WinForms-Anwendungen mit modernen Funktionen ausstatten, aber eben auch für andere Plattformen wie Android oder iOS entwickeln. Und eine Überraschung wurde von Microsoft erst eine Woche nach der Keynote bekannt gegeben. Ab Herbst 2020 wird mit Windows X eine Variante des Betriebssystems für Geräte mit zwei Monitoren zur Verfügung stehen und auch für die Entwickler auf der Android-Plattform wird es Veränderungen geben. Informationen dazu finden Sie in Zukunft natürlich auf der BASTA!

 

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

The post Keynote | Vom Core zum Frontend: Nahtlose Integration für moderne Webanwendungen appeared first on BASTA!.

]]>
Azure DevOps: Einfach per Kommandozeile steuern für effektive CI/CD-Prozesse https://basta.net/blog/azure-devops-einfach-per-kommandozeile-steuern/ Mon, 02 Sep 2019 12:32:27 +0000 https://basta.net/?p=29986 Die Konfiguration und die Prozesse in Microsofts Clouddienst „Azure DevOps“ kann man auch per Kommandozeile statt per Klick-Orgien steuern. In diesem Artikel wird erklärt, wie es geht und was alles zu beachten ist.

The post Azure DevOps: Einfach per Kommandozeile steuern für effektive CI/CD-Prozesse appeared first on BASTA!.

]]>

Azure DevOps ist die Nachfolgemarke von Team Foundation Server (TFS) und Visual Studio Team Services (VSTS). Microsoft differenziert durch die Zusätze „Azure DevOps Server“ und „Azure DevOps Services“ zwischen der lokal installierbaren Version und der Cloudversion. Aktuelle Version des Servers ist 2019.0.1, siehe Release Notes. Das zum Redaktionsschluss aktuelle Release der Cloudvariante ist” Sprint 154. Hierin findet man in den Release Notes den Eintrag „Azure DevOps CLI general availability“.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Von VSTS CLI zu Azure DevOps CLI

Die Steuerung von Azure DevOps per Kommandozeile (CLI, Command Line Interface) bietet sich für wiederkehrende Aufgaben oder Anwendungsintegration an. So könnte man zum Beispiel aus einer Monitoringsoftware heraus automatisch Bugs in Azure DevOps anlegen, wenn es zu Fehlern im laufenden Betrieb kommt. Per Kommandozeile lassen sich derzeit viele, aber nicht alle Funktionen von Azure DevOps steuern. Man kann zum Beispiel Projekte, Git Repositories, Pipelines, Work Items, Wikis, Banner sowie Teams, Gruppen und Benutzer anlegen und verwalten. Allerdings kann man keine Repositories mit Team Foundation Version Control (TFVC) damit administrieren. Auch das Hinzufügen von Benutzern zu Teams ist noch nicht im Angebot.
Das Azure DevOps CLI gibt es seit Februar 2019 als Preview, doch es ist nun am 8. Juli 2019 im Stadium „General Availability“ keineswegs fertig, wie der Beitrag noch aufzeigen wird. Es gab schon ein Vorgängerprodukt, das VSTS CLI, das trotz seines auf die Cloudvariante hindeutenden Namens auch mit dem lokalen TFS (ab Version 2017 Update 2) funktionierte.
Bei GitHub findet man noch einen Abzweig des alten Entwicklungsstands mit der Aussage „VSTS CLI is a new command line interface for Visual Studio Team Services (VSTS) and Team Foundation Server (TFS) 2017 Update 2 and later“. Diese Aussage ist jedoch veraltet; auf der echten Projektseite von Microsoft liest man dann auch klar: „The Azure CLI with the Azure DevOps Extension has replaced the VSTS CLI. The VSTS CLI has been deprecated and will no longer be receiving new features. We recommend that users of the VSTS CLI switch to the Azure CLI and add the Azure DevOps extension.“

 

Azure CLI als Grundlage

Während das VSTS CLI ein in Python implementiertes, eigenständiges Werkzeug (vsts, installiert in C:\Program Files (x86)\Microsoft SDKs\VSTS\CLI) war, ist das Azure DevOps CLI eine Erweiterung zum allgemeinen Azure CLI (az). Auch az ist in Python geschrieben und installiert sich im Standard in C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2. Die 2 am Ende dieses Dateipfads weist darauf hin, dass das Azure CLI die zweite Generation der Kommandozeilenwerkzeuge für Azure darstellt. Die erste Generation hatte als Befehlsnamen „azure“. Die zweite Version (az) wurde zunächst Azure CLI 2.0 genannt. Seit Juni 2018 differenziert Microsoft nun aber mit Azure CLI und Azure Classic CLI zwischen den beiden Produkten.
Für einen Nutzer, der Azure DevOps per Kommandozeile steuern möchte, bedeutet dies, dass er zuerst auf seiner Festplatte das aktuelle Azure CLI installieren muss. Die jeweils neueste Version erhält man unter [7]. Für Windows findet man dort einen MSI-Installer. Unter macOS ist eine Installation per homebrew (brew update && brew install azure-cli) vorgesehen. Für Linux-basierte Betriebssysteme kann man apt, yum, zypper oder eine manuelle Installation verwenden.
Zum Redaktionsschluss ist beim Azure CLI die Version 2.0.68 vom 3. Juli 2019 aktuell. Um die Azure-DevOps-Erweiterung für das Azure CLI nutzen zu können, ist mindestens Version 2.0.49 erforderlich. Der Benutzer kann die Kommandozeilenshell seiner Wahl verwenden. Unter Windows z. B. Windows PowerShell, PowerShell Core, die klassische Windows-Shell (cmd), das neue Windows Terminal oder die bash Shell im Windows-Subsystem für Linux (WSL). Hier soll PowerShell 7.0 Preview 1 zum Einsatz kommen, um die objektorientierte Pipeline nutzen und die Ergebnisse des Azure CLI weiterverarbeiten zu können. Ältere Varianten der PowerShell funktionieren aber gleichermaßen. Die meisten der hier vorgestellten Befehle funktionieren freilich auch ohne PowerShell Pipelines in der cmd oder bash.
Um die aktuelle Version des Azure CLI abzufragen, gibt man az –version ein und erhält dann eine lange Tabelle mit allen im Azure CLI installierten Befehlsmodule und den Erweiterungen. Falls man noch eine ältere Version des CLI verwendet, d. h. eine neuere vorliegt, wird man am Ende dieser Liste praktischerweise darauf hingewiesen.
Normalerweise zeigt diese Versionsausgabe keine Erweiterungen. Eine Erweiterung muss man erst explizit installieren. Dafür braucht man kein MSI-Paket mehr, denn dafür hat das Azure CLI den Befehl az extension add. Die Azure-DevOps-CLI-Erweiterung installiert man im Azure CLI mit:

az extension add --name azure-devops

Um nur die Liste der installierten Erweiterungen zu sehen, gibt man ein:

az extension list --output table

Zur Aktualisierung des Azure CLI führt man das aktuelle MSI-Paket von [9] aus. Hierdurch werden aber nicht die installierten Erweiterungen aktualisiert, man muss sie mit az extension update separat aktualisieren, z. B. folgendermaßen:

az extension update --name azure-devops

Die aktuelle Version der Azure-DevOps-CLI-Erweiterung, die bei Redaktionsschluss verfügbar ist, trägt die Versionsnummer 0.11.0. Beim modernen, agilen Microsoft bedeutet „Azure DevOps CLI general availability“ also keineswegs, dass ein Produkt die Versionsnummer 1.0 erreicht hat.

 

 

 

Weitverzweigte Hilfe

Hilfe zu den verfügbaren Befehlen bekommt man mit: az -h. Zu beachten ist, dass man wegen der Vielzahl der Befehle in dieser Hilfeausgabe nur Befehlsgruppen (z. B. ad, artifacts, boards, devops, iot, pipelines, repos, sql, storage, network, vm, webapp) zu sehen bekommt. In eine bestimmte Befehlsgruppe steigt man ein, indem man den Namen der Befehlsgruppe zusätzlich angibt: az pipelines -h.
Nun bekommt man eine kürzere Hilfeliste mit den Befehlen dieser Befehlsliste, wobei wieder unterschieden wird zwischen direkten Befehlen und Untergruppen (Abb. 1). Und auch hier bedeutet „General Availability“ also nicht, dass ein Produkt insgesamt fertig ist. Die meisten Befehlsgruppen des Azure DevOps CLI verweilen auch in der Preview, wie man in Abbildung 1 sieht.


Abb. 1: Hilfe zum Befehlsgebiet der Pipeline-Verwaltung mit „az pipelines -h“: Viele Befehle sind immer noch in der Preview

So sagt die Hilfeausgabe in Abbildung 1, dass man zur Auflistung aller definierten Pipelines eingeben kann: az pipelines list. Man kann aber auch noch mehr Hilfe anfordern zur Untergruppe agents mit: az pipelines agent -h. Dort sieht man, dass es auch auf dieser Ebene wieder einen Befehl list gibt, um die Liste der verfügbaren Pipeline-Agents zu erhalten: az pipelines agent list. Die Befehlsebene bringt feine Unterschiede zu Tage:

  • Mit az pipelines list listet man die Definitionen aller JSON-basierten Build Pipelines und der neueren YAML Pipelines.
  • Mit az pipelines build definition list erhält man nur die JSON-basierten Build Pipelines.
  • Mit az pipelines Release definition list werden die klassische Release-Pipelines geliefert.
  • Durch Eingabe von az login öffnet sich der Standardwebbrowser auf dem Computer und bietet eine interaktive Anmeldung.
  • Durch Eingabe von az devops login –organization https://dev.azure.com/OrganisationsName wird man an der Kommandozeile aufgefordert, ein Personal Access Token (PAT) einzugeben. Dies kann man im Azure-DevOps-Webportal erstellen. Das PAT kann dort hinsichtlich der Rechte beschränkt werden.
  • Alternativ kann man das PAT auch in einer Umgebungsvariable hinterlegen; damit vermeidet man jeden weiteren Anmeldevorgang:

$env:AZURE_DEVOPS_EXT_PAT = bjlhfo7evyvre5rsxhyhxy6…..tv6hklzrrl3ydwfan2k7d5awq"

Voreinstellungen

Wer mit mehreren Organisationen und Projekten arbeitet, wird beim Azure DevOps CLI immer wieder bei den einzelnen Befehlen die Organisation und den Projektnamen festlegen müssen:

az pipelines list --organization https://dev.azure.com/ITVDemo --project "MiracleList"

Dabei ist man bei –organization gezwungen, den ganze URL anzugeben. Eine Abkürzung der Parameternamen ist möglich, soweit noch eindeutig:

az pipelines list --org https://dev.azure.com/ITVDemo --p "MiracleList"

Man kann aber die Organisation nicht mit –o abkürzen, da es auch noch den Parameter –output gibt. Die ständige Wiederholung dieser allgemeinen Parameter kann man durch eine zentrale Vorgabe vermeiden:

az devops configure --organization https://dev.azure.com/ITVDemo --project "MiracleList"

Dadurch kann man im Folgenden verkürzt schreiben: az pipelines list

 

SIE LIEBEN AZURE?

Entdecken Sie die BASTA! Tracks

 

Ausgabeformate zur Wahl

Azure DevOps CLI liefert im Standard alle Ausgaben im JSON-Format. Dies ist zwar für Maschinen gut weiterzuverarbeiten, doch für Menschen nicht gut lesbar. Insgesamt stehen zur Verfügung:

  • json (einfarbige JSON-Ausgabe)
  • jsonc (mehrfarbige JSON-Ausgabe)
  • none (keine Ausgabe)
  • table (ASCII-Tabelle)
  • tsv (tabulatorgetrennt)
  • yaml (YAML)

Das Ausgabeformat kann man bei jedem einzelnen Azure-CLI-Befehl mit dem Parameter –output, abgekürzt mit -o,festlegen. Wie Abbildung 3 zeigt, beschränkt sich die Tabellenausgabe auf die wesentlichen Spalten.

Das Standardausgabeformat kann der Benutzer persistent in der Datei C:\Users\BenutzerName\.azure\config ändern (Listing 1). Diese Datei kann er auch interaktiv mit dem Befehl az configure bearbeiten.

[cloud]
name = AzureCloud
[core]
first_run = yes
output = table
collect_telemetry = no
cache_ttl = 10
[logging]
enable_log_file = no

 

Weiterverarbeitung der Ergebnisse

Als eingefleischter Liebhaber der objektorientierten Programmierung und des objektorientierten Pipelinings mit Objekten in der PowerShell fehlt dem Autor dieses Beitrags in der Liste der Ausgabeformate natürlich object. Man muss sich die Frage stellen, warum Microsoft zur Implementierung Python verwendet hat. C# oder Visual Basic .NET gibt es ja nun seit einiger Zeit auch plattformneutral, und diese Sprachen hätten den Vorteil gehabt, dass sie auch direkt .NET-Objekte für die PowerShell produzieren könnten. Wie so oft erleben wir auch hier, dass bei Microsoft leider viele technische Entscheidungen eher willkürlich in einzelnen Fachbereichen statt zentral und strategisch getroffen werden.
Die Azure DevOps-CLI-Befehle in der PowerShell auszuführen, lohnt sich dennoch, denn die PowerShell kann leicht JSON in .NET-Objekte verwandeln; dafür gibt es seit Windows PowerShell 3.0 das eingebaute Commandlet ConvertFrom-Json. Dieses Commandlet wandelt eine Zeichenkette im JSON-Format in .NET-Objekte um. Intuitiv würde man als PowerShell-Benutzer dann schreiben:

az pipelines list -o JSON | convertfrom-JSON | where-object { $_.name -notlike "*Backend*" } | sort-object name | format-table name, url

Allerdings wundert man sich, dass zwar die Tabellenausgabe mit den beiden gewünschten Spalten vorhanden ist, aber weder der Filter mit dem Commandlet where-object noch die Sortierung mit sort-object wirkt. Das liegt am Commandlet ConvertFrom-Json, bei dem das PowerShell-Entwicklerteam bei der Implementierung etwas verbockt hat. Wenn sich an der Wurzel der JSON-Datei ein Array befindet, legt das Commandlet das Array in die Pipeline. Üblicherweise entpacken aber Commandlets ein Array in Einzelobjekte. Microsoft hat das Problem leider bis heute nicht behoben>/a>. Die Umgehung dieses Problems ist, entweder Foreach-Object explizit in die Pipeline zu setzen:

az pipelines list -o JSON | convertfrom-JSON | foreach-object { $_} | where-object { $_.name -notlike "*Backend*" } | sort-object name | format-table name, url

oder den vorderen Teil zu klammern:

(az pipelines list -o JSON | convertfrom-JSON) | where-object { $_.name -notlike "*Backend*" } | sort-object name| format-table name, url

 

Work Items behandeln

Einige der Azure-DevOps-CLI-Befehle haben bereits eingebaute Filter, so kann man beispielsweise Work Items mit der SQL-ähnlichen Work Item Query Language (WIQL) aussortieren:

az boards query --wiql "SELECT System.ID, System.Title, System.WorkItemType, System.AreaPath, System.AssignedTo, System.State from workitems order by System.ID asc"

Mit PowerShell-Variablen kann man diesen Befehl natürlich auch in mehrere Zeilen aufteilen:

$wiql = "SELECT System.ID, System.Title, System.WorkItemType, System.AreaPath, System.AssignedTo, System.State from workitems order by System.ID asc"
az boards query --wiql $wiql

Ebenso ist es möglich, WIQL-Filter und das Filtern mit Where-Object in der PowerShell zu kombinieren. Der folgende Befehl filtert erst alle an einen Benutzer zugewiesenen Work Items, die nicht erledigt sind. Dann erfolgt in der PowerShell Pipeline ein weiterer Filter auf das Wort „Angular“ im Titel:

$wiql = "SELECT System.ID, System.Title, System.AssignedTo, System.State from workitems WHERE [System.AssignedTo] = '[email protected]' and state <> 'Done' order by System.ID asc"
(az boards query --wiql $wiql -o json | convertfrom-JSON).fields | where-object { $_."System.Title" -like "*Angular*" } | format-table "System.ID", "System.Title"

Der explizite Zugriff .fields ist hier notwendig, da die JSON-Struktur, die das Azure CLI liefert, etwas komplexer ist. An der Wurzel ist ein Array von Objekten. Die Objekte enthalten die Eigenschaften des Work Items aber nicht direkt, sondern ein Unterobjekt “fields” (Listing 2).

[
  {
    "fields": {
      "System.AssignedTo": {
        "_links": {
          "avatar": {
            "href": "https://nlhsdemo.visualstudio.com/_apis/GraphProfile/MemberAvatars/msa.N2NlNTFmMWYtODBhYy03MzVlLThmOWEtY2FhNzgyNjAzNGQz"
          }
        },
        "descriptor": "msa.N2NlNTFmMWYtODBhYy03MzVlLThmOWEtY2FhNzgyNjAzNGQz",
        "displayName": "Dr. Holger Schwichtenberg",
        "id": "0a31ed99-da1e-4412-913b-5da20050c88f",
        "imageUrl": "https://nlhsdemo.visualstudio.com/_apis/GraphProfile/MemberAvatars/msa.N2NlNTFmMWYtODBhYy03MzVlLThmOWEtY2FhNzgyNjAzNGQz",
        "uniqueName": "[email protected]",
        "url": "https://spsprodcus1.vssps.visualstudio.com/A7fbad96a-dc90-4656-af24-f37c38c5694b/_apis/Identities/0a31ed99-da1e-4412-913b-5da20050c88f"
      },
      "System.Id": 18,
      "System.State": "New",
      "System.Title": "Sicherheit verbessern: Secrets, z.B. im ConnectStrings, müssen verschlüsselt gespeichert werden"
    },
    "id": 18,
    "relations": null,
    "rev": 5,
    "url": "https://itvdemo.visualstudio.com/_apis/wit/workItems/18"
  },
…
]

Außerdem ist bei dem PowerShell-Befehl zu beachten, dass die WorkItem-Eigenschaftennamen in Anführungszeichen stehen müssen, falls sie Punkte enthalten (wie z. B. bei System.State und Microsoft.VSTS.Common.Severity). Den Punkt interpretiert die PowerShell genauso wie andere objektorientierte Sprachen als Trennzeichen zwischen Objekt und Eigenschaft. Hier Punkte im Namen zu verwenden, war nicht sehr weise von Microsoft.
Die Namen der Work-Item-Eigenschaften für die Prozesse Scrum und Agile findet man auf dem Azure-DevOps-Spickzettel, der dieser Ausgabe beiliegt. Vielfach findet man hier noch VSTS im Namen; hier wird Microsoft den alten Namen nicht loswerden, außer man riskiert Breaking Changes oder implementiert Aliase.

 

Praxisbeispiele

Zum Abschluss noch einige schöne Praxisbeispiele zum Einsatz des Azure DevOps CLI. Das erste Beispiel zeigt das Anlegen eines Work Items mit mehreren Eigenschaften (Listing 3). Von az boards work-item create wird eine JSON-Ausgabe angefordert, die mit Hilfe der PowerShell ausgewertet wird. Hier ist eine Klammer um ConvertFrom-Json nicht notwendig, denn der Azure-DevOps-CLI-Befehl liefert nur ein einzelnes Objekt, keine Menge. In dem Befehl fällt zudem auf, dass man einige wenige Eigenschaften direkt setzen kann über Parameter des CLI-Befehls; die meisten Eigenschaften kann man jedoch nur über den generischen Parameter –fields setzen. Auf –fields folgt eine Liste von durch ein Gleichheitszeichen getrennten Name-Wert-Paaren:

$wi = (az boards work-item create --type Bug --title "Absturz bei der Eingabe von 0 im Feld Preis" --description "Die Spalte Preis erlaubt nicht die Zahl 0!" --discussion "Das hatte ich in der letzten Version schon festgestellt!" --field "Microsoft.VSTS.TCM.ReproSteps=

  1. Maske ‘Rechnung’ öffnen
  2. neue Rechnung
  3. Klick in Rechnungsposition, Spalte Preis
  4. 0 eingeben

” “Microsoft.VSTS.Common.AcceptanceCriteria=

Die Eingabe 0 muss zu einer Warnmeldung beim Benutzer führen

” “Microsoft.VSTS.TCM.SystemInfo=Windows 10 v1903” –assigned-to “[email protected]” -o json)
$workItemID = ($wi | ConvertFrom-Json).ID

Das zweite Beispiel zeigt die Änderung eines Work Items:

az boards work-item update --id 30 --field "Microsoft.VSTS.TCM.SystemInfo=Windows 10 v1903" "Microsoft.VSTS.Common.Priority=1" "Microsoft.VSTS.Common.Severity=2 - High"

Das dritte Beispiel zeigt die Zuordnung zwischen zwei Work Items:

az boards work-item relation add --id 21 --target-id 24 --relation-type "Tests"

Eine Liste aller möglichen Zuordnungstypen in einem Projekt erhält man mit:

az boards work-item relation list-type

Zum Schluss noch das Starten einer Build Pipeline:

az pipelines build queue --definition-name MiracleListAngularClientBuild

Weitere sinnvolle Anwendungen des Azure DevOps CLI finden Sie auf dem dieser Ausgabe des Windows Developer beiliegenden Spickzettel.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

[mc4wp-simple-turnstile]

 

Azure-DevOps-Spickzettel


Der Azure-DevOps-Spickzettel von Dr. Holger Schwichtenberg zeigt Ihnen kurz und knapp, wie Sie mit wenigen Befehlen das Kommando über Azure DevOps übernehmen können.

Kostenlos downloaden

The post Azure DevOps: Einfach per Kommandozeile steuern für effektive CI/CD-Prozesse appeared first on BASTA!.

]]>
Azure Blob Storage für .NET-Entwickler https://basta.net/blog/azure-blob-speicher-eine-einfuehrung/ Wed, 14 Aug 2019 08:24:32 +0000 https://basta.net/?p=29831 Azure Blob Storage ist Microsofts Cloud-Dienst zum Speichern von Binary Large Objects (BLOBs). Das sind unstrukturierte Daten wie Bilder, Videos und Textdokumente. Doch neben dem Speichern dieser Daten bietet Blob Storage weitaus mehr. Thomas Claudius Huber von der Trivadis AG, stellt in seiner Session einiges davon vor.

The post Azure Blob Storage für .NET-Entwickler appeared first on BASTA!.

]]>

Mit Azure Blob Storage lassen sich beispielsweise einfach Snapshots eines Blobs erstellen, um eine Art Versionierung zu bauen. Entwickler können Metadaten auf einem Blob abspeichern, Daten für das Video- bzw. Audio-Streaming zur Verfügung stellen. Auch ganze Angular-Applikationen könnte man direkt in Azure Blob Storage für wenig Geld hosten. Das sind nur wenige Szenarien aus den wahrscheinlich unzähligen Beispielen, die sich im Arbeitsalltag jedes Entwicklers finden lassen.

Denn eins ist heutzutage gewiss, vor allem an unstrukturierten Daten mangelt es nicht. Der effiziente Umgang damit ist die eigentliche Herausforderung. Gleichzeitig gibt es genau dafür immer mehr Lösungen, die von Entwicklern passend ausgewählt werden müssen. Wenn es um Effizienz, leichte Verfügbarkeit und niedrige Einstiegshürden geht, bieten sich Cloud-Angebote zuerst an. Daher zeigt Thomas in diesem Video, neben einem Einstieg in Blob Storage, wie man Blob Storage in .NET verwenden kann. Außerdem erfahren Sie, wie Metadaten auf den BLOBs gespeichert, Snapshots erstellt und statische Webseiten im Blob Storage gehostet werden können.

 

Azure-DevOps-Spickzettel


Der Azure-DevOps-Spickzettel von Dr. Holger Schwichtenberg zeigt Ihnen kurz und knapp, wie Sie mit wenigen Befehlen das Kommando über Azure DevOps übernehmen können.

Download for free

The post Azure Blob Storage für .NET-Entwickler appeared first on BASTA!.

]]>
ML.NET: Machine Learning mit .NET Core 3 – Einstieg und Anwendungsfälle https://basta.net/blog/ml-net-machine-learning-mit-net-core-3/ Mon, 15 Jul 2019 12:24:33 +0000 https://basta.net/?p=29667 Mit .NET Core werden nicht nur WPF und WinForms Teil der neuen quelloffenen Implementierung von .NET, Microsoft möchte nun auch Machine Learning für jedermann einsetzbar machen. Darum hält nunmehr Machine Learning mit dem ML.NET Framework Einzug in .NET Core. Aber was kann ML.NET 1.0, welche Möglichkeiten gibt es dem Entwickler an die Hand, wie sehen das Tooling und die APIs aus und was passiert eigentlich unter der Haube?

The post ML.NET: Machine Learning mit .NET Core 3 – Einstieg und Anwendungsfälle appeared first on BASTA!.

]]>

Mit ML.NET (Machine Learning für .NET) veröffentlicht Microsoft ein für die Softwareentwicklung revolutionäres Framework. Möchte man maschinelles Lernen in den eigenen Anwendungen einsetzen, ist es nicht länger notwendig, sich im Detail mit der Mathematik und der Implementierung von z. B. Transformations-, Klassifikations-, Regressions- oder SVM-Algorithmen auszukennen oder sich damit auseinanderzusetzen, denn all das bringt ML.NET mit. Das Tooling um das Framework herum tut seinen Rest: Es war nie einfacher, den richtigen Algorithmus für den gewünschten Anwendungsfall auszusuchen, die eigenen Daten darauf zu trainieren und schließlich in den eigenen Anwendungen einzusetzen. Damit dreht Microsoft den Spieß um: Daten und Anwendungsfall stehen im Vordergrund, nicht länger die Implementierung.

Kurze Rede, schnell zum Sinn: Ein Beispiel sagt mehr als tausend Worte. Starten wir also mit einer praktischen Demo, um das Gesagte zu untermauern.

Los geht’s

Um ein Cross-Plattform-Developer-Tooling anzubieten, bringt ML.NET ein Command Line Interface (CLI) mit. Wir werden später etwas mehr darüber erfahren. Für das erste schnelle
Beispiel werden wir jedoch Visual Studio und die Model Builder Extension heranziehen. Die
Erweiterung für Visual Studio benutzt unter der Haube seinerseits das CLI.

Die Extension kann über das Hauptmenü EXTENSIONS | MANAGE EXTENSIONS installiert oder
alternativ heruntergeladen werden. Für diese erste Demo benutzen wir Visual Studio 2019.
Nach erfolgreicher Installation beginnen wir mit einer einfachen, leeren .NET-Core-
Konsolenanwendung. Im Kontextmenü des Projektknotens im Solution Explorer befindet sich ein
neuer Eintrag: ADD | MACHINE LEARNING. Die Auswahl öffnet den ML.NET Model Builder, wie in
Abbildung 1 zu sehen.

Abbildung 1: Model Builder Extension in Visual Studio

Der Model Builder bietet einen einfachen Assistenten, der durch fünf Schritte führt. Zuerst wählen wir das Szenario aus, in diesem Fall SENTIMENT ANALYSIS. Schritt zwei verlangt von uns die Daten, mit denen das neue Machine Learning Model erstellt und trainiert werden soll. Dazu laden wir das Wikipedia Detox Dataset herunter. Dieses enthält Userkommentare und eine Klassifikation, ob der Kommentar positive oder negative Aussagen im Sinne von Emotionalität enthält. Das ML Model soll mit diesen Daten trainiert werden, um zu bestimmen, ob ein beliebiger anderer Kommentar positive oder negative Aussagen enthält. Nach dem Download wählen wir die Datei im zweiten Schritt des Model Builders aus. Eine kleine Vorschau wird angezeigt, und die vorherzusagende Spalte muss ausgewählt werden. Für dieses Beispiel ist das die Spalte SENTIMENT.

Es folgt Schritt drei – das Trainieren des Models. Ein Klick auf den Button START TRAINING genügt und der Model Builder beginnt sofort damit, verschiedenste ML-Algorithmen zu durchlaufen und den für unser Problem am besten passenden auszuwählen, um anschließend das Model zu trainieren. So viel sei an dieser Stelle verraten: Unter der Haube wird AutoML für die Algorithmenselektion eingesetzt. Eventuell sollte man hier ein wenig mehr Zeit einplanen, als die vorgegebenen 10 Sekunden. Nach 30 Sekunden Trainingszeit findet der Model Builder einen Algorithmus mit einer Genauigkeit von 88,24 Prozent. Das bedeutet, dass das erzeugte Model bei 88,24 Prozent aller Vorhersagen in der Trainingsphase richtig lag.

Schritt vier des Assistenten zeigt uns anschließend eine Zusammenfassung des durchgeführten Prozesses, und ein paar Kennwerte zur Performance des Models. Mehr dazu später in diesem Artikel.

Schließlich werden in Schritt fünf das finale Model und der zugehörige Code in Form von zwei neuen Projekten generiert und der Solution hinzugefügt. Das Codebeispiel in Listing 1 zeigt, wie einfach das Model für Vorhersagen in der eigenen Anwendung benutzt werden kann.

var context = new MLContext();
var model = context.Model.Load(GetAbsolutePath(MODEL_FILEPATH), out DataViewSchema inputSchema);
var engine = context.Model.CreatePredictionEngine&lt;ModelInput, ModelOutput&gt;(model);
var input = new ModelInput()
{
  Sentiment = false,
  SentimentText = "This is not correct and very stupid."
};
var output = engine.Predict(input);
var outputAsText = output.Prediction == true ? "negative" : "positive";

Console.WriteLine($"The sentence '{input.SentimentText}' has a {outputAsText} sentiment with a probability of {output.Score}.");


Führt man den Code aus Listing 1 mit dem trainierten Model aus, lautet die Ausgabe: The sentence ‘This is not correct and very stupid.’ has a negative sentiment with a probability of 0.7026196. Der eine oder andere Leser wird sicher zustimmen, dass es schon fast gar nicht mehr einfacher geht, Machine Learning in die eigene Anwendung zu integrieren. Eine detaillierte Beschreibung und Guidelines für die Optimierung der erzeugten Models findet man hier.

Bevor wir uns nun aber in die Details der Algorithmen, Models, APIs und des Toolings stürzen, soll ein Überblick über ML.NET das Gesamtbild vermitteln.

Überblick

ML.NET war ursprünglich ein Projekt von Microsoft Research. Laut Microsoft hat sich dieses Projekt seit über einer Dekade bewährt und zunehmend etabliert. Es gewann für viele Bereiche innerhalb von Microsoft an Wichtigkeit und hielt Einzug in bekannte Produkte des Unternehmens (z. B. in Windows, Power BI, Office, Defender, Outlook und Bing). ML.NET soll nunmehr seinen Weg in die breite Öffentlichkeit finden und hält daher Einzug in .NET Core.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Version 1.0 wurde im April 2019 veröffentlicht, seit Juni steht Version 1.1 (beinhaltet Bugfixes und Performanceoptimierungen) zur Verfügung. ML.NET läuft auf .NET Core 2.1 und setzt die Installation der entsprechenden Runtime und des SDKs voraus.

Als Teil von .NET Core wird ML.NET unter dem Dach der .NET Foundation als Open-Source-Projekt gemeinsam mit der Community auf GitHub entwickelt. Das Framework ist plattformunabhängig und kann unter Windows, Linux und Mac verwendet werden. Abbildung 2 zeigt eine Übersicht von Microsoft über .NET Core 3 und veranschaulicht, wie ML.NET sich als Bestandteil in das Ökosystem einreiht.

Abbildung 2: ML.NET in .NET Core 3

Ziel von ML.NET ist es, Machine Learning für .NET-Entwickler so einfach wie möglich zu machen, ohne dabei das gewohnte Umfeld, also die Entwicklungsumgebung und die Programmiersprache, verlassen zu müssen. Microsoft möchte alles soweit abstrahieren, dass tiefgreifende Kenntnisse von Machine Learning nicht mehr notwendig sind. Das Beispiel zu Beginn des Artikels beweist ein Stück weit, dass dieser Vorsatz eingehalten wurde. Ob Web-, Desktop- oder IoT-Anwendung, Mobile Apps oder Spiele, als .NET-Entwickler können wir nun also KI/ML in jede Art von Anwendung integrieren – ohne ML-Vorkenntnisse.

Wir haben gesehen, dass mit ML.NET und dem dazugehörigen Tooling eigene ML Models auf einfache Art und Weise gebaut, trainiert, verteilt und genutzt werden können. Gehen die eigenen Bedürfnisse über das hinaus was ML.NET out of the box bietet (z. B. Optimierung von Hyperparametern etc.), steht es dem Entwickler frei, auf andere Tools (z. B. Infer.NET, TensorFlow, ONNX) zurückzugreifen. Ermöglicht wird das durch die Erweiterbarkeit von ML.NET, die von Beginn an Teil des Konzeptes und der Umsetzung ist.

Schließlich noch ein Wort zur Performance von ML.NET. Basierend auf einem 9 GB großem Dataset von Amazon, das Produktbewertungen enthält, hat Microsoft ein ML Model trainiert, dass zu 93 Prozent zuverlässige Vorhersagen treffen kann (Accuracy/Genauigkeit). Man muss dazu sagen, dass lediglich 10 Prozent der Daten aus dem Set zum Trainieren verwendet wurden. Der Grund dafür war, dass andere ML-Frameworks, die zum Vergleich herangezogen wurden, wegen Speicherproblemen nicht mit mehr Daten zurechtkamen. Im Ergebnis des Benchmarks erzielte ML.NET die höchste Genauigkeit und die beste Geschwindigkeit, wie in Abbildung 3 gezeigt wird. Die Details zu dieser Untersuchung können im Paper nachgelesen werden.

Abbildung 3: Benchmarkergebnisse von ML.NET und anderen Machine Learning Frameworks

Das soll für den schnellen Überblick über ML.NET an dieser Stelle genügen. Werfen wir nun einen Blick auf die Einsatzgebiete von ML.NET und welche Algorithmen das Framework dafür mitbringt.

Einsatzszenarien

Wirft man einen Blick in den Namespace Microsoft.ML.Trainers, der aus der DLL Microsoft.ML.StandardTrainers stammt, die wiederum mit dem NuGet-Package Microsoft.ML kommt, erhält man einen ersten Eindruck von der Vielzahl der von Microsoft implementierten Algorithmen. Abbildung 4 zeigt einen Ausschnitt.

Abbildung 4: Trainer-Klassen aus dem Namespace „Microsoft.ML.StandardTrainers“

Da eine Auflistung aller Algorithmen nicht zielführend wäre, und sowieso den Rahmen dieses Artikels sprengen würde, beschränken wir uns für die Übersicht auf die Klassen/Arten der unterstützten Algorithmen und deren Einsatzszenarien.

Möchte man beispielsweise Gefühle oder Stimmungen erkennen, wie im Beispiel der Sentiment-Analyse oben, kann man sich der binären Klassifikation bedienen. Binär bedeutet in diesem Fall, dass sich sämtliche Ergebnisse der Vorhersagen in zwei Ergebnismengen aufteilen. Eine Vorhersage kann entsprechend nur das eine oder das andere sein. In der Sentiment-Analyse entspricht das z. B. einer Vorhersage, dass eine bestimmte Aussage negativ oder positiv gemeint ist. Diese Art von ML-Algorithmen wird auch für die Implementierung von Spamfiltern, für die Betrugserkennung und die Vorhersage von Krankheiten eingesetzt.

Die Multiklassenklassifikation hingegen wird verwendet, wenn das vorhergesagte Ergebnis aus mehr als zwei Ergebnismengen stammen kann. So wird diese Art der
Algorithmen z. B. dazu verwendet, um Handschriften zu erkennen oder Pflanzen zu bestimmen.

Mit ML.NET können auch Models für Empfehlungen erstellt werden. Vorstellbar ist es z. B., dem Benutzer bestimmte Produkte oder Filme auf Grund seines (Konsum-)Verhaltens oder dem Kaufverhalten anderer Kunden zu empfehlen. Das ist hinreichend von E-Commerce-Shops bekannt. Zum Einsatz kann hier z. B. Matrixfaktorisierung kommen.

Mittels Regressionsalgorithmen lassen sich numerische Werte vorhersagen. Regression wird oftmals für die Vorhersage von Nachfrage, Preis und Verkaufszahlen für ein Produkt eingesetzt.

Mit Clustering werden Gruppierungen innerhalb von Daten gefunden und vorhergesagt, z. B. für die Unterteilung von Kunden und Märkten in bestimmte Segmente. Auch für die Einordnung von Pflanzen wird Clustering eingesetzt.

Anomaly Detection Models dienen der Anomalieerkennung. Anomalien möchte man z. B. in Strom- und Kommunikationsnetzen finden, um beispielsweise einen Cyberangriff aufzudecken. Auch die Vorhersage von Verkaufsspitzen fällt in diese Kategorie.

Zuletzt sei noch Computer Vision genannt. Computer Vision wird in der Bilderkennung und -Klassifikation eingesetzt. So möchte man z. B. Objekte und Szenen innerhalb einer visuellen Darstellung erkennen und beschreiben. Hier kommt das sogenannte Deep Learning zum Einsatz. ML.NET bringt dafür nicht direkt Algorithmen mit. In solchen Szenarien benötigt man z. B. ein ONNX oder TensorFlow Model. ML.NET versteht diese Models und bietet somit die Möglichkeit für Erweiterungen um beliebige Szenarien.

Für all die genannten Einsatzszenarien und Arten von ML ist ML.NET gedacht und bringt entsprechende Algorithmen mit, bzw. kann externe Models einbeziehen. Letztlich stehen damit sämtliche Türen des ML offen. Schwierig wird es, wenn es um das Finden des richtigen Ansatzes für das eigene jeweilige Problem geht. Hier muss sorgsam überlegt werden, was für ein Problem genau vorliegt, welche Daten zur Verfügung stehen und wie das Problem optimal gelöst werden kann. Der beste und schnellste Algorithmus nützt nichts, wenn er für das vorliegende Problem der falsche ist.

Eine Auflistung und Dokumentation aller Algorithmen in ML.NET findet sich hier. Um die Auswahl eines geeigneten Algorithmus bzw. Models zu erleichtern, pflegt Microsoft auf GitHub diverse praktische Beispiele für den Einstieg in ML. Die Community stellt ebenfalls eine Sammlung von Beispielen bereit.

Command Line Interface

Kehren wir nun zurück zur Praxis und schauen uns noch einmal das Tooling an. Wie zuvor erwähnt, benutzt die Model Builder Extension für Visual Studio unter der Haube das ML.NET CLI, um Models zu trainieren etc. Schauen wir uns also an, was dieses Tooling uns bietet und wie es funktioniert.

Das CLI ist ein .NET Core Global Tool und kann via Powershell, im Mac-Terminal oder in der Linux Bash mit dem Befehl dotnet tool install -g mlnet installiert werden. Voraussetzung dafür ist die Installation des .NET Core 2.2 SDKs. Nach der Installation kann das Tool mit mlnet –version aufgerufen werden. Der Parameter –version bewirkt, dass das CLI die aktuelle Version auf der Konsole ausgibt.

Um mit dem CLI ein ML Model zu trainieren, kann der folgende Befehl abgesetzt werden:

mlnet auto-train -d .\MyDataSet.csv --label-column-name ColumnToPredict --has-header --task regression.

Zuvor sollte man jedoch in ein leeres Verzeichnis bzw. in das Zielverzeichnis wechseln. Gegebenenfalls können die Trainingsdaten ebenfalls in das Zielverzeichnis kopiert werden. Die Trainingsdatendatei wird im obigen Aufruf nach dem Schalter -d angegeben. Weitere Parameter geben die vorherzusagende Spalte in den Trainingsdaten an, ob die Inputdatei einen Header besitzt, und welche ML-Art angewendet werden soll. In diesem Fall wurde die Regression gewählt. Mit dem Aufruf mlnet auto-train –help lassen sich alle Optionen für die Operation auto-train anzeigen.

Abbildung 5: AutoML mit der ML.NET CLI

Abbildung 5 zeigt die Ausgabe der Ergebnisse eines Trainingsvorgangs. Während der Prozedur wendet das CLI eine AutoML-Strategie an, um einen Algorithmus / ein Model zu finden, das die besten Ergebnisse in Bezug auf die Vorhersagegenauigkeit für die angegebene Spalte liefert (Kasten: „AutoML“). Das Beispiel in Abbildung 5 zeigt, dass der FastTreeRegression-Algorithmus die zuverlässigsten Ergebnisse mit einer Genauigkeit von 0.9317 (ca. 93 Prozent) liefert. Die Trainingszeit betrug insgesamt 30 Minuten. Evaluiert wurden verschiedene Models. Der Abbildung ist zu entnehmen, dass die FastTreeRegression jeden Platz unter den Top 5 der evaluierten Models einnimmt.

Am Ende des Trainingsvorgangs generiert das CLI Code. Um genauer zu sein, zwei Projekte. Das erste Projekt enthält das generierte binäre Model verpackt in einer ZIP-Datei, sowie zwei Model-Klassen. Eine Klasse repräsentiert die Struktur der Trainingsdaten, die wir hineingeben, und dient als Input-Model für Vorhersagen. Die andere Klasse repräsentiert die Ausgabe der Vorhersage des Models für einen bestimmten Input. Das Model-Projekt ist die Komponente, die wir dann in unseren eigenen Anwendungen verwenden können, um Vorhersagen auf Basis des erzeugten Models zu treffen.

Das zweite Projekt, dass durch das CLI generiert wird, ist eine Konsolenanwendung und zeigt beispielhaft, wie das Model verwendet werden kann. Das Projekt enthält ebenfalls eine Klasse namens ModelBuilder. Der ModelBuilder implementiert Methoden zum Trainieren des Models. Diese Klasse kann später im eigenen Code verwendet werden, um das vorhandene Model erneut zu trainieren (z. B. mit neuen, aktuellen Trainingsdaten). Dazu wird dann einfach die statische Methode ModelBuilder.CreateModel aufgerufen (Listing 2).

public static void CreateModel()
{
  // Load Data
  IDataView trainingDataView = mlContext.Data.LoadFromTextFile<ModelInput>(
                               path: TRAIN_DATA_FILEPATH,
                               hasHeader: true,
                               separatorChar: '\t',
                               allowQuoting: true,
                               allowSparse: false);

  // Build training pipeline
  IEstimator<ITransformer> trainingPipeline = BuildTrainingPipeline(mlContext);

  // Evaluate quality of Model
  Evaluate(mlContext, trainingDataView, trainingPipeline);

  // Train Model
  ITransformer mlModel = TrainModel(mlContext, trainingDataView, trainingPipeline);

  // Save model
  SaveModel(mlContext, mlModel, MODEL_FILEPATH, trainingDataView.Schema);
}

Die Methode lädt zuerst die Trainingsdaten über einen MLContext in einen sogenannten Data View. Die Klasse MLContext ist eine zentrale Komponente und Ausgangspunkt aller Operationen in ML.NET – ohne Kontext geht gar nichts. Ein Data View wird immer als IDataView dargestellt und repräsentiert die Sicht auf die Trainingsdaten für ML.NET.

Anschließend wird die Trainings-Pipeline erzeugt. Hier werden Algorithmen für die Datentransformation (z. B. zur Normalisierung von Spaltenwerten) und der Trainingsalgorithmus selbst in einer Pipeline aneinandergehängt. Nach der Evaluierung zur Bestimmung der Genauigkeit des Models mit den gegebenen Trainingsdaten, wird das Model dann trainiert. Das resultierende Model wird als ITransformer dargestellt. Der Name Transformer rührt daher, dass mit diesem Objekt Eingabedaten in eine Vorhersage transformiert werden können. Schließlich wird das Model dann in die Datei gespeichert, die in MODEL_FILEPATH angegeben ist.

AutoML

AutoML steht für Automated Machine Learning und beschreibt eine Ende-zu-Ende-Lösung zum Finden eines optimalen ML Models für ein gegebenes Problem innerhalb bestimmter Restriktionen (z. B. in Bezug auf Genauigkeit, Laufzeitverhalten, Speicherverbrauch etc.). AutoML ist Forschungsgegenstand. Es existieren aber bereits einige Implementierungen, so auch von Microsoft Research. Und genau diese Implementierung von Microsoft findet in ML.NET Anwendung.
Was bedeutet es, einen ML-Algorithmus zu finden? Was genau macht AutoML? Um ein ML Model zu kreieren, müssen zuerst einmal valide Trainingsdaten zur Verfügung gestellt werden. Diese Daten müssen aufbereitet werden. Dazu gehört u. a., die Daten zu bereinigen, unnötige und redundante Daten zu entfernen, Lücken in den Daten aufzufüllen und Werte zu normalisieren. Dann kommt das Domänenwissen ins Spiel. An Hand von Features, Eigenschaften der Trainingsdaten, lernt das ML Model die Domäne kennen und Vorhersagen für diese zu treffen. Features sind fundamental für ein ML Model und müssen entsprechend sorgfältig definiert werden. Anschließend müssen sie aus den Daten extrahiert und selektiert werden. Eine aufwendige Aufgabe, möchte man das von Hand erledigen. AutoML versucht automatisch, Features zu erlernen und zu selektieren. Die selektierten Features fließen dann in einen Algorithmus. Auch der muss sorgfältig ausgesucht werden. Auch das übernimmt AutoML für uns. Und schließlich folgt noch die Optimierung der Hyperparameter für das Model. Und auch die Optimierung wird durch AutoML automatisiert. Weitere Details können hier nachgelesen werden. Es passiert also eine Menge hinter der Bühne. AutoML reduziert die Aufwände für uns Entwickler signifikant und macht den allgemeinen Einsatz erst möglich.

Verwendung des Models

Wie kann das erzeugte Model nun aber in der eigenen Anwendung benutzt werden, um Vorhersagen zu treffen? Das ist relativ einfach. Zuerst einmal wird das durch das CLI generierte Model-Projekt dort referenziert, wo es eingesetzt werden soll. Das ist aber nicht zwingend notwendig, denn zur Verfügung stehen müssen die Klassen ModelInput und ModelOutput sowie die ZIP-Datei mit dem ML Model. Dafür können auch einfach die Klassendateien in das eigene Projekt kopiert werden. Zugriff auf das Model erfolgt zur Laufzeit durch die Pfadangabe zur ZIP-Datei. Man muss also sicherstellen, dass die ZIP-Datei zur Laufzeit zur Verfügung steht. Als letztes wird noch das NuGet-Package Microsoft.ML benötigt. Das kann einfach über den NuGet-Package-Manager oder die Kommandozeile installiert werden.

Ziehen wir noch einmal das Beispiel vom Anfang des Artikels heran, die Sentiment-Analyse; oder besser gesagt, das im Beispiel erzeugte Model. Die Verwendung könnte dann etwa so aussehen wie in Listing 3.

var context = new MLContext();

var model = context.Model.Load(GetAbsolutePath(MODEL_FILEPATH), out DataViewSchema inputSchema);

var engine = context.Model.CreatePredictionEngine<ModelInput, ModelOutput>(model);

var input = new ModelInput()
{
  Sentiment = false,
  SentimentText = "This is not correct and very stupid."
};

var output = engine.Predict(input);

var outputAsText = output.Prediction == true ? "negative" : "positive";

Console.WriteLine($"The sentence '{input.SentimentText}' has a {outputAsText} sentiment with a probability of {output.Score}.");

Zuerst wird eine Instanz von MLContext erzeugt. Wie gesagt, ist die Klasse MLContext Dreh- und Angelpunkt von ML.NET. Über die Eigenschaft Model des Contexts wird dann das zu verwendende ML Model in den Kontext geladen. Hier verwenden wir das vom CLI erzeugte Model zur Sentiment-Analyse. Es wird über den Pfad referenziert. Mit der Methode Model.CreatePredictionEngine instanziiert man dann zunächst die Komponente, die auf Basis des Models Vorhersagen treffen kann. Dazu wird das geladene Model als Parameter übergeben. Diese Schritte genügen zur Vorbereitung. Das ML Model ist einsatzbereit und wir können loslegen, Vorhersagen für Eingaben zu erzeugen. Jetzt kommen die generierten Model-Klassen zum Einsatz. Für Die Eingabe erstellen wir ein Objekt der Klasse ModelInput. Wir möchten herausfinden, ob der Satz „This is not correct and very stupid“ eine positive oder eine negative Aussage enthält. Der Satz wird für die Eigenschaft SentimentText der ModelInput-Instanz festgelegt. Nun wird die Eingabe an die Methode Predict der Vorhersage-Engine übergeben. Diese Methode schickt die Eingabe durch das Model und erzeugt eine Vorhersage in Form einer Instanz der Klasse ModelOutput. Über die Eigenschaft Prediction erfahren wir, ob der Satz eine negative oder positive Aussage enthält. Über die Eigenschaft Score erfahren wir darüber hinaus, wie wahrscheinlich die Vorhersage zutrifft. Beide Ergebnisse werden im obigen Beispiel in der Konsole ausgegeben. Wurde das Model mit denselben Daten trainiert wie in diesem Artikel, lautet die Ausgabe zum obigen Codebeispiel: The sentence ‘This is not correct and very stupid.’ has a negative sentiment with a probability of 0.7026196. Der gegebene Satz enthält laut Vorhersage also mit einer 70-prozentigen Wahrscheinlichkeit eine negative Aussage.

Fazit

Wie wir sehen, kann mit ML.NET ein ML Model mit nur wenigen Zeilen Code direkt in der eigenen Anwendung benutzt werden. Es geht fast nicht einfacher. Durch das CLI von ML.NET ist es gleichermaßen einfach, ML Models zu erzeugen. Unter der Haube kümmert sich AutoML um die Vorbereitung der Trainingsdaten, das Identifizieren und Selektieren von Features und die Auswahl sowie Optimierung des eingesetzten Algorithmus. Die Model Builder Extension bietet zusätzlich einen entsprechenden UI-Aufsatz und eine Integration in Visual Studio an.

Mit ML.NET abstrahiert Microsoft Machine Learning soweit, dass es für jeden Entwickler möglich wird, die eigenen Anwendungen auch ohne ML-Kenntnisse mit intelligenten Funktionen zu erweitern. Wer mehr wissen möchte, sei auf die ausführliche Dokumentation verwiesen.

 

The post ML.NET: Machine Learning mit .NET Core 3 – Einstieg und Anwendungsfälle appeared first on BASTA!.

]]>
Blockchains mit Hyperledger Fabric: Aufbau und Nutzung von Enterprise-Blockchains https://basta.net/blog/blockchains-mit-hyperledger-fabric/ Wed, 26 Jun 2019 10:24:00 +0000 https://basta.net/?p=29583 Hyperledger Fabric ist eine mit Go entwickelte Open Source Blockchain. Hyperledger selbst wurde Ende 2015 unter dem Dachverband The Linux Foundation gegründet. Unter dessen Schirmherrschaft werden Open Source Blockchains und dazu passende Tools entwickelt. Und auch .NET-Entwickler können damit gut arbeiten.

The post Blockchains mit Hyperledger Fabric: Aufbau und Nutzung von Enterprise-Blockchains appeared first on BASTA!.

]]>

Mit Hyperledger wird nicht nur der Ansatz einer Blockchain, sondern vielmehr verschiedener Blockchain-Plattformen verfolgt. Genaugenommen ist Hyperledger Fabric (kurz: Fabric) eine Technologie für Permissioned Blockchains bzw. private Blockchains – eine Blockchain, die nicht für alle öffentlich ist, z. B. wie Ethereum oder Bitcoin, sondern nur bestimmten Teilnehmern zugänglich ist.

In dieser Session stellt Ingo Rammer Open-Source-Blockchain-Basistechnologie Hyperledger Fabric vor. Er vermittelt die Grundlagen von Fabric für den Betrieb von privaten bzw. berechtigungsgestützten Blockchains und zeigt die dafür geeigneten Einsatzgebiete, Architekturentscheidungen sowie Sicherheitsaspekte auf. Diese Session ist eine optimale Grundlage, um in die Blockchaintechnologie einzusteigen. Dabei zeigt sich vor allem wie weit, eine Blockchain vom Hype entfernt sein kann und ein wirklich nützliches und nutzbares Tool in der modernen und sicheren B2B-Softwareentwicklung sein kann.

Auf der BASTA! 2019 stellt Ingo Rammer in seinem Workshop “Von Null auf Hundert Workshop: Blockchain-Anwendungen mit Hyperledger Fabric” das Komplettpaket vor. Wer nach dem Session-Video mehr wissen will, sollte sich den Workshop nicht entgehen lassen.

 

The post Blockchains mit Hyperledger Fabric: Aufbau und Nutzung von Enterprise-Blockchains appeared first on BASTA!.

]]>
5 Top-Argumente für Ihren BASTA!-Besuch: Warum Sie diese Konferenz nicht verpassen sollten https://basta.net/blog/5-top-argumente-fuer-ihren-basta-besuch/ Wed, 19 Jun 2019 09:28:55 +0000 https://basta.net/?p=29521 Ein .NET-Entwickler konzeptioniert, designt und entwickelt Softwareanwendungen, welche auf die Bedürfnisse des Unternehmens zugeschnitten sind. Neben der Bedarfsermittlung und Analyse der Anforderungen, gehören auch der Support und die stetige Weiterentwicklung zu seinen Aufgaben.

The post 5 Top-Argumente für Ihren BASTA!-Besuch: Warum Sie diese Konferenz nicht verpassen sollten appeared first on BASTA!.

]]>
Kontinuierliches Lernen hat hohen Stellenwert

In einer schnelllebigen Industrie wie der IT müssen Sie als Entwickler Ihr Wissen laufend aktualisieren. Die digitale Transformation und der damit einhergehende Aufbruch in eine neue digitale Ära bringt unzählige neue Möglichkeiten mit sich und führt dazu, dass eingefahrene Prozesse und Strukturen hinterfragt, neue Trends eingeschätzt und in die konkreten Projekte implementiert werden müssen. Mit einem BASTA!-Besuch können Sie bestehende Probleme lösen und das nötige Wissen erlangen.

Wenn Sie noch nicht überzeugt sind, warum es sich wirklich lohnt, an der BASTA! teilzunehmen, haben wir für Sie 5 unschlagbare Argumente parat. Machen Sie sich schon mal auf den Weg!

 

 

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

The post 5 Top-Argumente für Ihren BASTA!-Besuch: Warum Sie diese Konferenz nicht verpassen sollten appeared first on BASTA!.

]]>
.NET Framework, .NET Core und Mono werden .NET 5.0! https://basta.net/blog/net-framework-net-core-und-mono-werden-net-5-0/ Tue, 14 May 2019 09:16:58 +0000 https://basta.net/?p=29385 Vom 6.–8. Mai 2019 hielt Microsoft seine jährliche Entwicklerkonferenz Build in Seattle ab und bot einen Ausblick auf die Pläne für die kommenden Jahre – vor allem im Bereich .NET wird sich mächtig was tun.

The post .NET Framework, .NET Core und Mono werden .NET 5.0! appeared first on BASTA!.

]]>
Abb. 1: Abschied von .NET Core

 

Für .NET-Entwickler gab es auf der Microsoft BUILD 2019 wieder einmal einen ordentlichen Paukenschlag: .NET 5.0 (kurz .NET 5) wird der gemeinsame Nachfolger der drei bisherigen .NET-Varianten (.NET Core, .NET Framework und Mono) sein. Alle .NET-Anwendungsarten von Desktop (WPF und Windows Forms) über Webserver (ASP.NET) und Webbrowser (Blazor/WebAssembly) bis zu Apps (UWP, Xamarin) und Spielen (Unity) sollen damit zukünftig eine gemeinsame .NET-Basis haben.

.NET 5 soll im November 2020 (Abb. 2) erscheinen und im Wesentlichen (aber nicht komplett) plattformneutral sein. Einige Anwendungsarten, die auf .NET 5 basieren, werden nur auf bestimmten Plattformen laufen, z. B. UWP-Apps nur auf Windows 10, WPF- und Windows-Forms-Desktopanwendungen nur auf Windows ab Version 7. .NET 5 darf nicht verwechselt werden mit .NET Core 5 – dem Namen, den Microsoft für .NET Core 1.0 geplant hatte, bevor man sich entschloss, die Versionszählung wieder von vorne zu beginnen.

 

Abb. 2: .NET 5 im Verhältnis zu .NET Core, .NET Framework und Mono

 

 

BUILD 2019


Die BUILD-Konferenz fand vom 6. bis 8. Mai 2019 in Seattle statt. Während die Veranstaltung früher immer sehr schnell ausgebucht war, konnte Microsoft sie nun zum zweiten Mal in Folge nicht ganz füllen. Einige Teilnehmer nennen als Grund dafür, dass Microsoft nicht mehr wie früher Hardwaregeschenke verteile. Die Aufzeichnungen der Vorträge kann jedermann kostenfrei unter https://www.microsoft.com/en-us/build ansehen.

Nicht alle Features kommen in .NET 5

Technisch gesehen ist .NET 5 die Weiterführung von .NET Core (mit den zugehörigen Werkzeugen und Deployment-Optionen), in das große Teile (aber nicht alles) von .NET Framework und Mono einfließen (Abb. 1). Nach derzeitigem Stand werden in .NET 5 zum Beispiel Windows Workflow Foundation (WF) und Windows-Communication-Foundation-(WCF-)Server sowie Windows Forms aus Mono fehlen. Im Rahmen von .NET 5 wird aus Mono die statische Ahead-of-Time-(AOT-)Kompilierung als Alternative zur bisherigen Just-in-Time-(JIT-)Kompilierung in .NET einfließen, sodass .NET-Entwickler zukünftig zwischen JIT und AOT wählen können. AOT bietet .NET-Entwicklern kleinere Installationspakete und schnellere Anwendungsstarts, aber wegen der Vorkompilierung in Maschinencode eben keine plattformunabhängigen Installationspakete.

.NET 5 wird auch neue Funktionen beinhalten. Dazu gehört eine Integration mit anderen Frameworks und Programmiersprachen (Java, Objective-C und Swift) für die App-Entwicklung auf iOS und Android.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Der Begriff „Core“ verschwindet wieder

„.NET Core“ hatte Microsoft als Namen am 12.11.2014 für das schon am 13. Mai 2014 verkündete „cloud-optimized .NET Framework“ alias „Project K“ eingeführt. Version 1.0 erschien dann am 26. Juli 2016. Danach gab es die Versionen 1.1, 2.0, 2.1, 2.2. Version 3.0 von .NET Core soll im September 2019 erscheinen; auf der BUILD gab es die Preview-5-Version; die Veröffentlichung des Release Candidate ist für Juli dieses Jahres geplant, dann soll es noch ein .NET Core 3.1 im November 2019 geben.

Damit soll dann die .NET-Core-Ära auch schon wieder enden (Abb. 3). Es wird keine weiteren Versionen mehr mit „Core“ im Namen geben. Als Nächstes kommt .NET 5 (Abb. 3), denn Microsoft findet den Begriff inzwischen nicht mehr geeignet in der Kommunikation, gerade für neu in die .NET-Welt eintretende Softwareentwickler.

 

Abb. 3: Zeitplan für die kommenden .NET-Versionen (Quelle: Microsoft)

 

Mit .NET Core Version 2.0 hatte Microsoft begonnen, massiv Klassen aus dem .NET Framework nach .NET Core zu übernehmen. Man kehrte damit von dem .NET-Core-1.x-Gedanken ab, das neue .NET Core klein und aufgeräumt zu halten. Stattdessen stand nun auf der Agenda, möglichst kompatibel zum .NET Framework zu werden, damit bestehende klassische .NET-Framework-Anwendungen auf .NET Core umziehen können. In Version 2.1 führte Microsoft diese Trendwende mit dem Windows Compatibility Pack (WCP) fort: Erstmals gab es in .NET Core nur Klassen, die ausschließlich auf Windows laufen (z. B. für die Registry-Programmierung). Dies wird verstärkt auch in .NET Core 3.0 gelten: Dort gibt es zwar dann die GUI-Frameworks WPF und Windows Forms, aber sie sind weiterhin nicht plattformunabhängig, sondern an Windows gebunden. Der Sinn von WPF und Windows Forms liegt darin, dass Softwareentwickler bestehende .NET-Framework-basierte WPF- und Windows-Forms-Anwendungen auf .NET Core umziehen können. Auch in .NET 5 wird sich an dieser Plattformeinschränkung erst einmal nichts ändern.

.NET Core 3.0 liefert darüber hinaus .NET Standard 2.1, Unterstützung für gRPC und das neue ASP.NET-SignalR-basierte Webanwendungsmodell Server-side Blazor – nicht zu verwechseln mit dem WebAssembly-basierten Client-side Blazor, das zwar nun eine offizielle Vorschau ist, für das es aber weiterhin keinen Veröffentlichungstermin gibt.

.NET Framework 4.8 auf dem Abstellgleis

Softwareherstellern und Softwareentwicklern, die heute bestehende WPF- und Windows-Forms-Anwendungen besitzen oder neu planen, machte Microsoft auf der Build-Konferenz klar, was sie schon am 4. 10. 2018 in einem Blogeintrag angedeutet hatten: Die am 18. April 2019 erschienene Version 4.8 ist die letzte Version des .NET Frameworks, die signifikante neue Funktionen bieten wird. Alles, was danach noch an Updates kommen wird, wird lediglich Sicherheitslücken und kritische Softwarefehler betreffen. Vielleicht wird das ein oder andere Netzwerkprotokoll noch auf den neuesten Stand gebracht, das wars dann aber.

Deterministische Erscheinungstermine

Microsoft will bei .NET 5 bei der in .NET Core etablierten, agilen und transparenten Entwicklungsweise eines Open-Source-Projekts auf GitHub bleiben. Einen Unterschied soll es aber zu .NET Core geben: Anstelle der bisherigen unregelmäßigen Erscheinungstermine soll es jedes Jahr im November eine neue Hauptversion mit Breaking Changes geben, d. h. nach .NET 5 im November 2020 gibt es dann .NET 6 im November 2021, .NET 7 im darauffolgenden Jahr usw. (Abb. 2).

Zwischendurch sind Unterversionen mit neuen Features, aber ohne Breaking Changes bei Bedarf angedacht. Jede zweite Version soll eine Long-Term-Support-(LTS-)Version mit drei Jahren Unterstützung durch Microsoft sein. .NET Core 3.0 wird genau wie .NET Core 2.0 keine LTS-Version sein; die auf .NET Core 2.1 folgende LTS-Version wird .NET Core 3.1 sein. Dann geht es in der LTS-Linie erst 2021 weiter. Weitere Neuigkeiten von der Microsoft Build:

Windows:

  • Ein verbessertes Linux-Subsystem in Windows (WSL) bietet schnellere Dateisystemoperationen sowie die Unterstützung für Linux-basierte Docker-Container.
  • Microsoft wird seinem Windows-Betriebssystem endlich ein neues Kommandoeingabefenster (Windows Terminal) mit zeitgemäßer Zeichensatzunterstützung inklusive Emoticons, Registerkarten, Layoutthemen und einem Erweiterungsmodell mit eigenem Marktplatz spendieren. Das Windows Terminal wird für die klassische CMD, die PowerShell und das Shell für Windows for Linux (WSL) zur Verfügung gestellt werden. Die erste Version soll es aber erst im Juni 2019 geben.
  • Der neue Chromium-basierte Microsoft-Edge-Webbrowser erhält einen Kompatibilitätsmodus zum Internet Explorer 11 (IE Mode). Unternehmen, die auf Funktionen des Internet Explorers für alte Intranetanwendungen angewiesen sind (z. B. ActiveX), sollen damit auf Edge umsteigen können.

Werkzeuge:

  • Von Visual Studio 2019 gibt es eine dritte Vorschauveröffentlichung auf Version 16.1. Die bisher eigenständige IntelliCode-Erweiterung ist nun für die Sprachen C# und XAML enthalten. Auch die GitHub-Erweiterungen gehören nun zum Standard. Ebenso soll die Performance für C++- und .NET-Entwickler in einigen Punkten verbessert sein. In C# gibt es nun IntelliSense auch für Typen, die noch nicht importiert sind.
  • Die Visual-Studio-Abonnementoptionen werden erweitert um „Visual Studio Professional with GitHub Enterprise“ und „Visual Studio Enterprise with GitHub Enterprise“ im Rahmen von Enterprise Agreements (EA).
  • Entwickler können mit Visual Studio Online (VSO) im Browser coden. Für diesen Onlineeditor verwendet Microsoft wieder den Namen VSO, den man zwischen dem 14. 9. 2011 und dem 13. 12. 2013 als Name für die heutigen Azure DevOps Services verwendete.

.NET:

  • Mit .NET Core 3.0 Preview 3 liefert Microsoft eine erste Version des „Single-File Bundlers“ aus, die alle Dateien einer .NET-Core-Anwendungen in eine selbstentpackende Executable verpackt. Eine echte .exe mit AOT-Compiler soll es erst in .NET 5.0 geben.
  • Die auf der Build 2018 angekündigten „XAML-Inseln“ werden in Windows 10 mit dem Update im Mai 2019 verfügbar sein. Damit können Steuerelemente der Universal Windows Platform (UWP) in WPF und Windows Forms eingebunden werden (natürlich setzt dies Windows 10 voraus!).
  • Mit MSIX Core können Entwickler das MSIX-Installationsformat auch auf Windows-Versionen vor Windows 10 verwenden.
  • Ergänzend zur Microsoft Authentication Library (MSAL) for JavaScript sowie Objective-C für iOS und Android gibt es nun diese Hilfsbibliothek für Azure Active Directory auch für .NET.
  • ML.NET, die auf der BUILD 2018 angekündigte Machine-Learning-Bibliothek für .NET, erreicht Version 1.0.
  • NET-Programmierung ist nun auch dem Cluster-Computing-Framework Apache Spark möglich mit „.NET for Apache Spark“.
  • Xamarin Forms 4.0 gibt es als Public Preview, u. a. mit dem neuen CollectionView-Steuerelement, das ListView ersetzen soll, indem es schneller ist.

SQL Server:

  • Mit Azure SQL Database Edge liefert Microsoft nun eine sowohl auf x64- als auch auf ARM-Systemen installierbare SQL-Server-basierte Datenbank mit niedrigen Systemanforderungen. Das API der Azure SQL Database Edge soll kompatibel zu SQL Server und SQL Azure sein.

DevOps:

  • Für Azure DevOps gibt es ein neues Preismodell. Es gibt keine Preiskategorien mehr: Wer mehr als fünf Benutzer hat, zahlt 6 Dollar pro Benutzer. Für in der Cloud gespeicherte Artefakte zahlt man zukünftig zwischen 2 und 0.25 Dollar pro Gigabyte, wobei die ersten zwei Gigabyte frei sind.
  • Durch die Azure-Active-Directory-(AAD-)Unterstützung in GitHub können Entwickler nun Benutzergruppen zwischen dem AAD und GitHub synchronisieren.
  • Auf der anderen Seite unterstützten die Webportale für Azure und Azure DevOps nun auch die Benutzeranmeldung mit GitHub-Benutzerkonten.
  • Bisher konnten Azure-DevOps-Nutzer die neuen YAML-basierten Pipeline-Definitionen nur für Build-Pipelines nutzen. Nun gibt es dieses Feature auch für Release-Pipelines. Ein einziges YAML-Dokument kann dabei sowohl die Build- als auch die Release-Pipeline enthalten. YAML-Pipeline-Definitionsdokumente können im Quellcodeverwaltungssystem eingecheckt sein. Das gibt den Entwicklern die Möglichkeit, Pipeline-Definitionen auf einfache Weise spezifisch für einzelne Branches zu erstellen.
  • Für das Deployment auf Azure Kubernetes Services und Red Hat OpenShift gibt es nun Vorlagen in Azure DevOps, sowohl für Cloud als auch On-Premise.

 

The post .NET Framework, .NET Core und Mono werden .NET 5.0! appeared first on BASTA!.

]]>
Keynote | Die Cloud, DevOps und wir Entwickler: Erfolgreiche Zusammenarbeit im modernen Software-Ökosystem https://basta.net/blog/die-cloud-devops-und-wir-entwickler/ Tue, 23 Apr 2019 08:39:14 +0000 https://basta.net/?p=29260 Das Thema Cloud Computing spielte speziell in unserer Region lange eine untergeordnete Rolle. Das hat sich in letzter Zeit gewandelt. Inzwischen ist die Cloud eine feste Größe in IT-Abteilungen und in vielen Fällen auch schon erste Wahl - vor allem für Entwickler.

The post Keynote | Die Cloud, DevOps und wir Entwickler: Erfolgreiche Zusammenarbeit im modernen Software-Ökosystem appeared first on BASTA!.

]]>

Aber natürlich nicht die ganze Cloud. Die Anzahl unterschiedlicher Cloud-Lösungen und Services steigt stetig an und bietet Unternehmen und Entwicklern immer mehr Möglichkeiten. Auf der BASTA! Legen wir den Fokus daher auf die Cloud für Entwickler und berücksichtigen Microsoft Azure etwas mehr als die Angebote anderer Anbieter. Es ist aber wichtig zu sehen, dass die Cloud in ihrer Vielfalt Entwicklern offen steht und auch genutzt werden sollte.

Vielfalt in der Cloud für Entwickler

In der Keynote zur Eröffnung der BASTA! Spring 2019 stellen Rainer Stropek, Advisor des Cloud Developer Day, und Neno Loje, Advisor des DevOps Pipelines Day, zunächst ihre Sicht auf das Verhältnis zwischen Cloud und Entwicklern vor. Im Anschluss diskutieren sie mit Mirko Schrempp, Program Chair der BASTA!, weitere Aspekte. Denn die Cloud ist nicht für jeden die Cloud. Es gibt unzählige praktische Möglichkeiten die Cloud als Entwickler zu nutzen – auch dann, wenn sie noch nicht für ein Produkt des eigenen Unternehmens eingesetzt wird. So können sich DevOps-Teams beispielsweise ein Bündel von Diensten und Lösungen zusammenstellen, das für ihre spezifischen Herausforderungen in CI/CD-Projekten passend ist, aber mit dem eigentlichen Produkt nichts zu tun hat. Oder Entwickler gewinnen über Telemetriedaten Einblick in die Nutzung oder den Zustand der eigenen Software. Die Beispiele sind so vielfältig wie die Cloud. Zur Eröffnung der BASTA! wurden daher keine Schlagwörter aufgezählt, sondern diskutiert, was DevOps und Cloud für Entwickler und Unternehmen in der Praxis wirklich bedeuten und warum vieles damit einfacher geht.

Die Cloud auf der BASTA!

Selbstverständlich haben wir mit Rainer Stropek und Neno Loje wieder zwei Special Days für die BASTA! 2019 zusammengestellt. Deren Sessions bieten ganz im Sinne der Diskussion aus dem Frühjahr Informationen, Einblicke und Anregungen. Besuchen Sie im September doch einfach den Cloud Developer Day und den DevOps Pipelines Day.

DevOps Pipelines Day ist übrigens der neue Name des “traditionellen” TFS Days. Da Microsoft seinen Team Foundation Server (TFS) und die online Variante Visual Studio Team Services (VSTS) umbenannt hat, haben wir dem Tag einen neuen Namen gegeben, der vor allem die DevOps-Zielsetzung einer reibungslosen Entwicklungs-Pipeline betont und vorstellt. Die Hintergründe und Folgen der Umbenennung erklärt Neno im Video-Interview “Azure DevOps – “Kurzfristig ist es nur ein neuer Name”” hier auf dem Blog.

Aber auch über die beiden Special Days hinaus finden Sie in vielen Session und Workshops der BASTA! 2019 Informationen zum Einsatz der Cloud in Ihren Projekten.

 

The post Keynote | Die Cloud, DevOps und wir Entwickler: Erfolgreiche Zusammenarbeit im modernen Software-Ökosystem appeared first on BASTA!.

]]>
.NET Core 3.0 wird ein großes Ding für die Zukunft der Softwareentwicklung https://basta.net/blog/net-core-3-0-wird-ein-grosses-ding/ Fri, 15 Mar 2019 13:36:22 +0000 https://basta.net/?p=28863 Rund um das .NET Framework gibt es in den letzten Jahren viele Veränderungen. Angefangen dabei, dass .NET von Microsoft Open Source gestellt wurde, bis hin zu der Tatsache, dass mit .NET Core eine modularisierte Variante zur Verfügung steht, die .NET für die Zukunft auf jeder Plattform fit machen soll. Welche Neuerungen und Möglichkeiten sich mit .NET Core 3.0 ergeben, beschreibt Dr. Holger Schwichtenberg, IT-Visions.de, im Interview.

The post .NET Core 3.0 wird ein großes Ding für die Zukunft der Softwareentwicklung appeared first on BASTA!.

]]>

Neben den Neuerungen von .NET Core 2.2, das aktuell schon zur Verfügung steht, beschreibt Dr. Holger Schwichtenberg alias der „Dotnet Doktor“ vor allem, was in Version 3.0 zu erwarten ist. Die Version ist ein Major Release mit zahlreichen großen Änderungen, das voraussichtlich im Herbst 2019 erscheinen wird. Daher wird es bei der BASTA! 2019 natürlich auch im Fokus stehen und zahlreiche Sessions dazu geben.

Mit Version 3.0 kommen WPF- und sogar Windows-Forms-Anwendungen nach .NET Core. Entwickler können dann in (bestehende) Anwendungen .NET Core 3.0 einbauen. Damit lassen sich Anwendungen einfach modernisieren und zum Teil unter Windows 7, zum Teil aber nur unter Windows 10 nutzen, z. B. dann, wenn UWP-Inseln oder Inking-Funktionen eingesetzt werden. Damit bleibt für Windows 7, das im Januar 2020 aus dem Support läuft, noch ein gewisser Bestandsschutz bestehen, aber die Zielrichtung Windows 10 ist klar – Holger spricht von der Renaissance des Desktops. Selbstverständlich werden auch ASP.NET Core und Entity Framework Core in Version 3.0 ebenfalls Neuerungen erhalten, die ihre Flexibilität und Plattformunabhängigkeit erweitern. Vorteile für Entwickler sind in jedem Fall bessere Werkzeuge und bessere Deployment-Prozesse sowie kleinere, ausführbare Dateien, da sie nur noch mitbringen, was zum Ausführen der Anwendung gebraucht wird.

Ein großer Sprung wird allerdings durch Blazor in .NET Core 3.0 eingeführt, wenn auch noch immer nicht vollständig. Zum einen wird es Blazor für den Client geben, d.h. C# im Browser, aber auch die Möglichkeit von Blazor auf dem Server – darauf gibt Holger Schwichtenberg am Ende des Interviews einen interessanten Ausblick.

 

The post .NET Core 3.0 wird ein großes Ding für die Zukunft der Softwareentwicklung appeared first on BASTA!.

]]>
Produktiver durch Serverless-Architekturen: Eine neue Ära der Softwareentwicklung https://basta.net/blog/produktiver-werden-durch-serverless/ Fri, 01 Mar 2019 10:28:44 +0000 https://basta.net/?p=28817 In der heutigen, sich schnell entwickelnden Technologielandschaft revolutioniert die serverlose Architektur die Art und Weise, wie Entwickler Anwendungen erstellen und bereitstellen. Durch die Beseitigung der Komplexität des Infrastrukturmanagements ermöglichen serverlose Lösungen wie AWS Lambda und Azure Functions den Entwicklern, sich auf das Schreiben effizienter Codes und die Erstellung skalierbarer Microservices zu konzentrieren. In diesem Artikel werden die wichtigsten Vorteile der Annahme serverloser Technologien, der Steigerung der Cloud-Produktivität und der Förderung von Innovationen in der Softwareentwicklung beleuchtet. Entdecken Sie, wie die Integration von Serverless-Computing Ihre Arbeitsabläufe optimieren und Ihre Herangehensweise an die Anwendungsentwicklung in der Cloud revolutionieren kann.

The post Produktiver durch Serverless-Architekturen: Eine neue Ära der Softwareentwicklung appeared first on BASTA!.

]]>

Der Einsatz von Cloud-Diensten ist in vielen Bereichen der IT zum Standard geworden: von der praktischen SaaS-Lösung, über die unendlich skalierbare Datenbank, bis hin zu großen Testszenarien und natürlich dem container-basierten Betrieb eigener Dienste. Christian Weyer erklärt im Video-Interview, warum Serverless wohl „das nächste große Ding“ ist und Entwickler wieder produktiver macht.

Der Einsatz von Cloud-Diensten ist in vielen Bereichen der IT zum Standard geworden. Von der praktischen SaaS-Lösung, über die unendlich skalierbare Datenbank, bis hin zu großen Testszenarien und natürlich dem container-basierten Betrieb eigener Dienste – das, was vor einiger Zeit noch als Hype diskutiert wurde, ist inzwischen „normales Business“. Christian Weyer, Vorstand und Gründungsmitglied der Thinktecture AG, erklärt im Video-Interview warum Serverless wohl „das nächste große Ding“ ist und Entwickler wieder produktiver macht.

Die Cloud ist bei weitem nicht so diffus, wie es ihr Name vermuten lässt, aber was sie mit ihren namensgebenden Verwandten am Himmel teilt, ist die Fähigkeit stetig zu wachsen und sich zu verändern. Für Entwickler bedeutet das, sich ebenso stetig mit neuen Möglichkeiten und Angeboten auseinander zu setzten. Dabei haben nach Christian Weyers Ansicht alle Entwickler nur ein Ziel – sie wollen ordentlichen Code schreiben und gute Business-Anwendungen erstellen.

Serverless statt Infrastruktur-Sorgen

Wer sich neu mit der Cloud beschäftig, habe aber zunächst viel mit Infrastruktur zu tun, mit Containern, mit verteilter Programmierung und vielen anderen technischen Themen, die weit von dem weg sind, was man z. B. im Rahmen des Rapid Application Developments kannte – vor allem habe man viel Produktivität verloren. Aber das Cloud Computing bringe die Möglichkeiten zurück, die Produktivität auf einem höheren Niveau wieder zu gewinnen. Für Weyer ist „Serverless“ eines der Cloud-Angebote, das Entwickler davon befreit, sich in zu vielen Infrastrukturfragen zu verzetteln. Angebote wie AWS Lambda oder Azure Functions erlauben es, in fertig konfigurierten Ereignissen zu denken, die sich über APIs und Microservices für Entwickler einfach anprogrammieren lassen, typische Anwendungsfälle einfacher machen und damit die Produktivität wieder steigern.

 

Wie das im Detail aussehen kann, erfahren Sie im Interview und z. B. im MODERN BUSINESS APPLICATIONS DAY, den Christian Weyer mit uns zusammengestellt hat. Auch der Power Workshop „Moderne Businessanwendungen mit Angular, .NET Core und Azure – Weitblick für Backend- und Frontendentwickler“ am Montag bietet einen kompakten Einblick.

 

The post Produktiver durch Serverless-Architekturen: Eine neue Ära der Softwareentwicklung appeared first on BASTA!.

]]>
Cosmos DB in eigenen Projekten einsetzen: Grundlagen und Einsatzszenarien https://basta.net/blog/cosmos-db-eigenen-projekten-einsetzen-grundlagen-und-einsatzszenarien/ Tue, 15 Jan 2019 11:26:38 +0000 https://basta.net/?p=28549 Dokumentendatenbanken wie Microsofts Cloud-basierte Azure Cosmos DB bieten neue Ansätze und Möglichkeiten, um mit Daten umzugehen – zumindest neu für die Microsoft-Entwicklerwelt, die von relationalen Datenbanken dominiert wurde.

The post Cosmos DB in eigenen Projekten einsetzen: Grundlagen und Einsatzszenarien appeared first on BASTA!.

]]>

Dieser Artikel gibt Ihnen einen kleinen Überblick über die Grundlagen, Möglichkeiten und unterstützten APIs, die Azure Cosmo DB für Ihre Projekte bereitstellt und bietet Überlegungen zu möglichen Einsatzszenarien an.

Einführung

Microsofts Azure Cosmos DB ist eine Dokumentendatenbank, ein NoSQL JSON Data Storage, das neben einer ganzen Reihe interessanter Features mehrere APIs bietet, um mit diesen zu arbeiten. „NoSQL“ steht dabei übrigens für „Not only SQL“, was einen Hinweis auf die unterschiedlichen APIs darstellt.

Dabei ist der Ansatz, Daten zu speichern, ein völlig anderer als z. B. bei relationalen Datenbanken. So werden Daten nicht in Tabellen abgelegt, sondern in Collections (auch Container genannt). Eine Dokumentendatenbank speichert Daten in Form von Dokumenten im JSON-Format. Dies erlaubt, diese Daten verschachtelt und – und das ist wichtig – heterogen abzuspeichern. Das heißt, Dokumente, die sich einen Container (Collection) teilen, müssen nicht über den gleichen Aufbau verfügen. Es können also mal mehr, mal weniger oder schlicht unterschiedliche Informationen in einem solchen Dokument untergebracht werden. Auch können von Dokument zu Dokument für gleichnamige Eigenschaften unterschiedliche Datentypen verwendet werden. Listing 1 zeigt ein kleines JSON-Dokument als Beispiel. Bei Abfragen wird dies (je nach API) entsprechend berücksichtigt.

Listing 1

  {
  "BusinessEntityID": 1,
  "PersonType": "EM",
  "NameStyle": false,
  "Title": null,
  "FirstName": "Ken",
  "MiddleName": "J",
  "LastName": "Sánchez",
  "Suffix": null,
  "EmailPromotion": 0,
  "AdditionalContactInfo": null,
  "Demographics": "0",
  "rowguid": "92c4279f-1207-48a3-8448-4636514eb7e2",
  "ModifiedDate": "2009-01-07T00:00:00",
  "Password": {
    "Hash": "pbFwXWE99vobT6g+vPWFy93NtUU/orrIWafF01hccfM=",
    "ModifiedDate": "2009-01-07T00:00:00",
    "Salt": "bE3XiWw="
  }
}

 

Für den Zugriff auf diese Daten stehen aktuell fünf unterschiedliche APIs zur Verfügung (mehr dazu später). Existieren also schon Code für und Erfahrung und mit z. B. MongoDB oder Apache Cassandra, steht einem Einsatz von Azure Comos DB eigentlich nichts im Weg. In Abbildung 1 sehen Sie eine Übersicht aller APIs.

 

Abb. 1: Die unterstützten APIs

 

Die unterschiedlichen APIs können dabei prinzipiell alle Dokumente nutzen, die mit Hilfe eines anderen APIs erstellt wurden. In der Praxis gibt es jedoch deutliche Einschränkungen, da z. B. mit dem Gremlin API auch Graphdaten erzeugt werden, deren Strukturen von anderen APIs nicht verstanden werden.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Features

Welches sind nun die erwähnten interessanten Features? Als Cloud Service ist Azure Cosmos DB „fully managed“, sodass keinerlei administrative Aufgaben anfallen. Mit einem SLA (Service Level Agreement) garantiert Microsoft eine hohe Verfügbarkeit und schnelle Antwortzeiten mit der Option, bei Bedarf zu skalieren. Eine automatische und für eine Anwendung völlig transparente Verschlüsselung sorgt für die notwendige Sicherheit der Daten.

Globale Verteilung („globally distributed“) erlaubt die Festlegung, in welchem Rechenzentrum die Daten gespeichert werden. Dabei ist vorgesehen, dass dies mehr als nur ein Rechenzentrum ist. Die Infrastruktur im Hintergrund sorgt für die notwendige Replikation. Der Vorteil der globalen Verteilung liegt dabei auf der Hand: Beim Zugriff von Orten weltweit wird das nächstgelegene Replikat verwendet. Anwender z. B. aus Australien greifen damit auf eines der beiden Rechenzentren in Down Under zu (wenn dort ein Replikat konfiguriert wurde). Das betrifft übrigens nicht nur Lese- sondern auch Schreibzugriffe. Abbildung 2 zeigt mit Hexagonen die möglichen Standorte weltweit.

 

Abb. 2: Globale Verteilung

 

ACID-Transaktionen (Atomicity, Consistency, Isolation, Durability) sorgen dafür, dass Änderungen an mehr als nur einem Dokument gemeinschaftlich nach dem Alles-oder-nichts-Prinzip durchgeführt werden. So arbeiten z. B. auch Prozeduren und Trigger im Hintergrund mit Transaktionen, um eine Datenkonsistenz zu gewährleisten.

Um Zugriffe auf Dokumente möglichst schnell auszuführen, verwendet Azure Comos DB Indizes, wie andere Datenbanken auch. Diese werden automatisch erstellt und gepflegt. Dazu werden Abfragen analysiert, und ermittelt, welche Eigenschaften wie oft zum Filtern verwendet werden. Diese automatische Indexierung kann durch Policies beeinflusst werden, funktioniert aber durchaus auch ohne weiteres Zutun.

Tools

Für die Entwicklung mit Cosmos DB existieren schon einige praktische Tools. Als Erstes wäre natürlich das schon erwähnte Azure Portal zu nennen, über das die Datenbanken angelegt und verwaltet werden. Außerdem gibt es über das Portal die Möglichkeit, Daten abzufragen und zu modifizieren (Abb. 3).

 

Abb. 3: Azure Portal

 

Wer bei der Entwicklung auf keine (verlässliche) Verbindung zugreifen kann oder schlicht mögliche Kosten vermeiden möchte, dem steht der Azure-Cosmos-DB-Emulator zur Verfügung (Abb. 4). Dieser Emulator, der natürlich ohne Azure Cloud auskommt, kann wahlweise installiert oder als Docker Image genutzt werden.

 

Abb. 4: Azure-Cosmos-DB-Emulator

 

Zugriffschlüssel und Verbindungszeichenfolgen sind (standardmäßig) für alle Installationen und Docker Images gleich, sodass bei einer Entwicklung im Team kein Sicherheitsproblem auftritt und Entwickler immer wieder Anpassungen vornehmen müssten, um auf die Daten im Emulator zuzugreifen.

Und als Letztes sei da noch das Azure Cosmos DB Migration Tool genannt, mit dem Daten von Quellen in ein Ziel geschrieben werden können. Dabei steht eine Reihe von Formaten für die eine und auch die andere Seite zur Auswahl (Tabelle 1)

DynamoDB
HBase

Quelle Ziel
JSON-Datei JSON-Datei
DocumentDB
– SQL API
– Table API
DocumentDB
– SQL API
– Table API
SQL-Server
CSV
AzureTable
Tabelle 1: Quellen und Ziele des Azure Cosmos DB Migration Tools

 

Da einige Quellen wie z. B. relationale Datenbanken wie SQL Server keine verschachtelten Daten unterstützen, gibt es hier die Option, in der Abfrage Joins zu verwenden und in den Namen der zurückgegebenen Spalten einen „Nesting Seperator“ (z. B. einen Punkt) einzubauen, der für die gewünschte Verschachtelung im Ziel sorgt.

 

Abb. 5: Azure Cosmos DB Migration Tool

 

Als Nebeneffekt lassen sich mit diesem Tool z. B. Daten aus einer SQL-Server-Abfrage in eine JSON-Datei schreiben, was sicherlich das eine oder andere Mal ganz nützlich sein kann.

Sicherheit

Selbstverständlich ist, gerade bei einer Cloud-basierten Technologie, das Thema Sicherheit wichtig. Neben der Sicherheit während des Transports über das Netzwerk mittels SSL/TLS und der Absicherung durch die Anbindung an ein virtuelles Netzwerk bietet Azure Cosmos DB eine Sicherheit, die zwischen nur lesenden und Lese-und-Schreib-Zugriffen unterscheidet (von administrativen Aufgaben einmal abgesehen). Gesteuert wird dies über entsprechende Schlüssel.

Eine differenzierte Kontrolle auf Ebene eines Containers (Collection) oder einzelner Elemente (Dokumente) ist nicht vorgesehen. Dafür muss die Anwendung bei Bedarf selbst sorgen.

Der Data Access & Storage Track auf der BASTA!

APIs

Für Anwendungen stehen fünf APIs zur Auswahl, um auf Azure Cosmos DB zuzugreifen. Ihr Vorteil: Wenn Sie bereits Erfahrung mit einem dieser APIs haben oder sogar bereits fertiger Code existiert, kann dieser mit wenigen (oder sogar keinen) Anpassungen verwendet werden. Beispielsweise verwendet der Zugriff via MongoDB API die gleichen Bibliotheken, die auch zum Einsatz kommen, um auf eine „gewöhnliche“ MongoDB-Installation zuzugreifen.

Angeboten wird also das MongoDB API und damit die Schnittstelle zu einer „klassischen“ Open-Source-Dokumentendatenbank. Mit dem API für Gremlin steht eine Graph-Datenbank zur Verfügung. Eine Graph-Datenbank speichert Informationen in Knotenpunkten und deren Beziehungen untereinander. Damit lassen sich Beziehungsgeflechte in sozialen Netzwerken, Nutzungsprofile und Ähnliches abbilden und abfragen. Das Core (SQL) API bietet die Möglichkeit, Informationen mit einer (einfachen) SQL-Syntax abzurufen. Das Anlegen, Löschen und Verändern von Dokumenten geschieht hingegen via der üblichen HTTP-Verben POST, PUT und DELETE, während die Abfrage selbst ein GET verwendet. Mit dem Table API steht ein Schlüsselwertspeicher (Key-Value-Store) à la Azure Table zur Verfügung. Bei dem „Wert“ kann es sich um komplexe Objekte handeln, die als JSON serialisiert abgelegt werden. Ein Zugriff ist mittels der Schlüssel möglich. Und schließlich wird seit einiger Zeit als neuester Zuwachs das Cassandra API verwendet, das von vielen großen Unternehmen wie GitHub, Ebay, Netflix und auch im CERN eingesetzt wird.

Der Einsatz von Entity Framework Core

Ab Version 2.2 bietet Entity Framework Core das erste Mal in der Geschichte von Microsofts O/R-Mapper die Option, auf nicht relationale Datenbanken zuzugreifen. Im Nuget-Paket Microsoft.EntityFrameworkCore.Cosmos befindet sich der benötigte Code. Allerdings ist der aktuelle Stand der Entwicklung noch nicht so weit, ihn tatsächlich sinnvoll einzusetzen. Dafür gibt es noch zu viele Probleme, Ungereimtheiten und nicht umgesetzte Features. Konsequenterweise ist in der stabilen Version von .NET 2.2 (vom 4. Dezember 2018) die Untersetzung für Azure Cosmos DB entfallen. Diese taucht erst im Preview für .NET 3.0 wieder auf. Dennoch ist es ein Schritt in die richtige Richtung und es bleibt spannend zu sehen, was EF Core 3.0 in dieser Richtung bringen wird.

Funktionen, Prozeduren und Trigger

Azure Comos DB erlaubt die Verwendung von Funktionen, Stored Procedures und Trigger, die in Serverside JavaScript geschrieben werden können. Sämtliche Elemente werden für eine Collection geschrieben; auch Funktionen und Prozeduren gehören also, nicht wie bei SQL Server, zu der gesamten Datenbank.

Funktionen können z. B. mit dem Core (SQL) API verwendet werden, um weitere Möglichkeiten in die Abfragen einzubauen. Werden für eine Abfrage reguläre Ausdrücke benötigt, reicht es, eine einfache Funktion wie im Folgenden zu schreiben:

  function RexExpTest(s, p){
  return s.match(p) != null;
}

Für die Entwicklung bieten sich die üblichen Tools und Wege wie online JavaScript Playgrounds, Visual Studio Code mit Code Runner und viele andere Optionen an. Diese neu geschaffene Funktion kann einfach verwendet werden. Achten Sie dabei auf den Präfix udf, der zwingend notiert werden muss. Die folgende Zeile zeigt die Core-(SQL-)Abfrage:

  SELECT * FROM c WHERE udf.RexExpTest(c.Name, 'Speaker [0-2]') = true

Das abschließende Listing zeigt eine kleine Demo für eine Prozedur, die einen kleinen Text an den Client zurücksendet. Der body legt dabei die Funktionalität fest:

  var helloWorldDemoProc = {
  id: "helloWorldDemo",
  body: function () {
    var context = getContext();
    var response = context.getResponse();
    response.setBody("Hello World!");
  }
}

Gründe für den Einsatz

Die große Frage ist nun nur noch, ob sich der Einsatz einer Dokumentendatenbank, speziell Azure Cosmos DB, im eigenen Projekt lohnt. Diese ist pauschal weder einfach noch überhaupt zu beantworten; es sei denn, der Einsatz der Azure Cloud ist ein No-Go. Wenn Sie bis dato (wie der Autor) Ihre Daten in Tabellen und relationalen Datenbanken gespeichert haben, lohnt sich ein neugieriger Blick auf die anderen Möglichkeiten. Wenn die zu verarbeitenden Daten oft in flexiblen Varianten verarbeitet werden müssen oder wenn sie über Beziehungen verfügen, die in allen (oder den meisten) Use Cases aufgelöst werden müssen, dann bietet sich die Flexibilität von JSON-Dokumenten an. Müssen die Daten immer in der gleichen Form vorliegen, kann sich diese Flexibilität als eher nachteilig erweisen – schließlich muss die Anwendung für feste Vorgaben sorgen.

Der .NET-Framework & C# Track auf der BASTA!

Auf der anderen Seite ist jedoch zu bedenken, dass die APIs aktuell keine partiellen Updates der Dokumente unterstützen. Wenn also ein Dokument auch noch so minimal verändert wird, muss das komplette Dokument übertragen werden. Häufige kleine Änderungen können sich also, gerade bei größeren JSON-Dokumenten (>1 Kilobyte), schlecht auf die Performance auswirken.

Bei nicht so großen, flexiblen Daten oder solchen, die kaum geändert werden müssen, kann NoSQL seine Stärke ausspielen. Das gilt auch für Azure Comos DB mit seinen unterschiedlichen APIs.

Fazit

Azure Cosmos DB im Speziellen und NoSQL-Datenbanken im Allgemeinen sind auf jeden Fall einen Blick wert und bieten Möglichkeiten, die mit relationalen Datenbanken nur schwer realisiert werden können. Praktische Tools bis hin zum Emulator und fertige Beispiele machen den Einstieg leicht.

 

Azure Cosmos DB auf der BASTA!


Passend zum Thema dieses Artikels finden Sie auf der BASTA! z. B. den Power Workshop Azure Cosmos DB von Thorsten Kansy am Freitag.

The post Cosmos DB in eigenen Projekten einsetzen: Grundlagen und Einsatzszenarien appeared first on BASTA!.

]]>
Interview | Azure DevOps – “Kurzfristig ist es nur ein neuer Name” https://basta.net/blog/azure-devops-kurzfristig-ist-es-nur-ein-neuer-name/ Thu, 20 Dec 2018 08:13:07 +0000 https://basta.net/?p=28514 Der TFS und die VSTS heißen jetzt Azure DevOps (Server) - was bedeutet das über die Namensänderung hinaus? Seit 2017 gibt es auf der BASTA! schon den TFS & DevOps Day und wir würden gerne behaupten, dass Microsoft nun endlich nachgezogen hat und seinen Team Foundation Server (TFS) und die online Variante Visual Studio Team Services (VSTS) umbenannt hat - soweit wollen wir aber nicht gehen.

The post Interview | Azure DevOps – “Kurzfristig ist es nur ein neuer Name” appeared first on BASTA!.

]]>

Im Interview mit Neno Loje, www.teamsystempro.de, klären wir, was es mit der Namensänderung auf sich hat und wohin der DevOps-Trend zielt.

Über 12 Jahre lang haben Entwickler jetzt mit dem TFS gearbeitet und in dieser Zeit hat er seinen Namen überraschenderweise nicht geändert. Seine Online-Variante war dagegen unter verschiedenen Namen bekannt und hieß bis September 2018 Visual Studio Team Services. Die VSTS heißen jetzt Azure DevOps und analog dazu wird der TFS in der neuen Version Azure DevOps Server (2019) heißen. Zunächst wird sich auch nur der Name ändern, sagt Neno Loje. Der TFS wird Entwicklern und Unternehmen weiterhin On-Premise zur Verfügung stehen, sodass man damit im Prinzip wie gewohnt weiterarbeiten kann.

Aber selbstverständlich steht diese Umbenennung im Zeichen der umfangreichen Veränderungen, die Microsoft mit seiner Cloud-Strategie vorantreibt. Neue Entwicklungen passieren in der Cloud und werden erst später den On-Premise-Produkten hinzugefügt, soweit das denn möglich ist – das gilt auch für den TFS.

Im Zentrum steht Azure

 

Zugleich öffnen die Tools und Microsoft sich immer mehr für Open-Source-Lösungen wie Git und Software von Drittherstellern und nutzen dabei unterschiedliche Cloud-Angebote. Darüber hinaus soll aber auch die Zusammenarbeit zwischen allen Projektbeteiligten gefördert werden. Hierfür steht der Namensteil DevOps, der nicht nur im engeren Sinne die Entwickler und den Betrieb meint. Aber gerade für diese beiden Gruppen bieten die neuen Continuous Integration und Continuous Delivery (CI/CD) Pipelines von Azure DevOps Möglichkeiten, die in dieser Form bisher nicht so einfach zu nutzen waren.

Auf der BASTA! bieten vor allem die Sessions des TFS & DevOps Days sowie des Cloud Developer Days einen fundierten Überblick über die neuen Möglichkeiten und den besten Einstieg für den Einsatz in der täglichen Arbeit.

 

The post Interview | Azure DevOps – “Kurzfristig ist es nur ein neuer Name” appeared first on BASTA!.

]]>
Überzeugen Sie ihren Chef in 60 Sekunden https://basta.net/blog/wie-sie-ihren-boss-von-einem-basta-besuch-ueberzeugen/ Mon, 17 Dec 2018 12:35:09 +0000 https://basta.net/?p=28467 Sie können es kaum erwarten an der BASTA! Spring 2019 teilzunehmen, um Ihre Programmierskills auszubauen und neue Technologien zu verstehen? Ihr Chef lässt sich aber nur sehr schwer davon überzeugen? Wir wissen Ihnen zu helfen mit der Elevator Pitch Technik! Sie werden dafür nicht mehr als drei starke Argumente und 60 Sekunden Zeit brauchen.

The post Überzeugen Sie ihren Chef in 60 Sekunden appeared first on BASTA!.

]]>
Eine gute Vorbereitung ist die halbe Miete

Beschäftigen Sie sich vorab intensiv mit den Programminhalten und stellen Sie sich eine Übersicht aus den für Sie informativsten und lehrreichsten Sessions zusammen. Mit unserer Infografik als Stütze haben Sie alle nötigen Details zur Hand, um Ihren Chef in 60 Sekunden zu überzeugen. Sie sind aber nur gut im Algorithmen-Formulieren? Auch hier stehen wir Ihnen zur Seite! Damit Sie sich nicht mit Formulierungen herumschlagen müssen, haben wir eine ultimative E-Mail-Vorlage für Ihren Boss vorbereitet. Diese benötigt nur minimale Anpassungen Ihrerseits, um einen Ausflug zur BASTA! Spring zu ermöglichen.

 

 


Die E-Mail Vorlage:

Sehr geehrte/r Frau / Herr (…),

Ich möchte Sie gerne um Erlaubnis bitten an der BASTA!-Konferenz teilnehmen zu dürfen, die vom 25 Februar – 1. März in Frankfurt am Main stattfinden wird.

Kontinuierliche Änderungen, die nicht nur Microsoft und seine Produkte, sondern die gesamte IT-Industrie betreffen, erfordern es, dass man ständig auf dem Laufenden bleibt. Damit wir, als Unternehmen, zukünftig noch effektiver und kostengünstiger Produkte entwickeln können, ist es notwendig, sich regelmäßig mit den neuesten Entwicklungen der Industrie zu beschäftigen.

Diese Konferenz bietet genau diese Möglichkeit. In herausragenden Keynotes und Sessions liefert die BASTA! einen gezielten Ausblick auf die kommenden Trends der IT-Welt.

Die Highlights der BASTA!:

  • Hier werden in den 80+ praxisnahen Workshops und Sessions neueste Technologien zu C#, dem .NET-Framework, der Software-Architektur und der agilen Softwareentwicklung aufgegriffen.
  • Die Konferenz vermittelt unter anderem wertvolles Know-how zu Angular, Docker, Azure, DevOps, Cloud und vielem mehr.
  • Best-Practices und Live-Coding Beispiele, die ich garantiert sofort anwenden kann.
  • Direktes Feedback und Unterstützung von mehr als 60 erfahrenen Microsoft-Experten.
  • Inhalte der Sessions sind zum Download verfügbar.

Wenn ich an der Konferenz teilnehmen darf, würde ich gerne im Nachgang eine firmeninterne Schulung zur Zusammenfassung der Konferenz geben. Ebenso verfasse ich, auf Wunsch, einen kurzen Erfahrungsbericht für unseren Firmenblog oder die Mitarbeiterzeitung.

Alle Infos zur Konferenz und zu Frühbucherpreisen finden Sie hier.

Mit besten Grüßen

(Ihr Name)


The post Überzeugen Sie ihren Chef in 60 Sekunden appeared first on BASTA!.

]]>
Das große BASTA!-Nikolaus-Gewinnspiel! https://basta.net/blog/basta-nikolaus-gewinnspiel/ Wed, 05 Dec 2018 23:01:34 +0000 https://basta.net/?p=28291 Heute nichts im Stiefel gehabt?
Dann werden Sie sich sicher über unseren BASTA!-Nikolaus freuen! Der verlost heute großartige Bücher, die er Ihnen gerne noch schnell vorbeibringt.

The post Das große BASTA!-Nikolaus-Gewinnspiel! appeared first on BASTA!.

]]>
 

Die Welt der Webentwicklungen dreht sich unglaublich schnell und kaum meint man, auf dem aktuellen Stand zu sein, kommen die nächsten Neuerungen.
Wenn Sie bisher reine Windows-Desktop-Anwendungen programmiert haben oder mit alten ASP.NET-Konzepten gearbeitet haben, möchten Sie sich vielleicht jetzt mit der Entwicklung von Web- und Cross-Plattformanwendungen beschäftigen? Dann ist heute Ihr Glückstag:

 

Im Gepäck hat er drei Exemplare von: Moderne Webanwendungen für .NET-Entwickler. 

Dr. Holger Schwichtenberg und das IT-Visions-Expertenteam zeigen Ihnen, wie Sie moderne Single-Page-Webanwendungen und mobile Cross-Plattform-Apps mittels HTML, CSS, Java Script und TypeScript entwickeln können.
Damit diese zukünftig auch für jeden nutzbar sind, ist natürlich auch die Programmierung von Webservern und Web-Clients inbegriffen.
Als ganz besonderes Feature kann sämtlicher genutzter Code heruntergeladen werden und der Leser erhält zusätzlich kostenloses Bonusmaterial in Form von drei Kapiteln zu den Themen: React, Open Web Interface for .NET (OWIN) / Katana und ASP.NET Sicherheit.

 

Überzeugt? Alle Neuanmelder für unseren Newsletter, sowie treue BASTA!-Newsletter-Abonnenten, können jetzt eins von drei Exemplaren  „Moderne Webanwendungen für .NET-Entwickler“ gewinnen!

Jetzt anmelden:

 

[mc4wp-simple-turnstile]

Die Teilnahme am Gewinnspiel ist bis zum 13.12.2018 um 23:59 Uhr möglich.

The post Das große BASTA!-Nikolaus-Gewinnspiel! appeared first on BASTA!.

]]>
Office als Plattform: Das Office API und Serverless Backends https://basta.net/blog/office-als-plattform-das-office-api-und-serverless-backends/ Tue, 27 Nov 2018 15:50:15 +0000 https://basta.net/?p=28235 Microsoft Office gehört seit Jahrzehnten zu den Standardprodukten in Unternehmen und hat sich gerade in den letzten Jahren entlang der aktuellen Trends weiterentwickelt. Beispielsweise macht Microsoft Office es dank eines modernen Add-in-Modells sehr einfach, Web-Apps und Services auf allen Plattformen bereitzustellen.

The post Office als Plattform: Das Office API und Serverless Backends appeared first on BASTA!.

]]>

Egal, ob es um Dokumente, E-Mails oder Tabellenkalkulationen geht, Office stellt sämtliche APIs zur Verfügung. Das erlaubt es Entwicklern und Unternehmen, ihre eigenen Produkte eng mit Office zu verzahnen und moderne Nutzungsszenarien vom Backend bis zum mobilen Frontend anzubieten. Thorsten Hans zeigt, wie man gerade als Angular-Entwickler Vorteile und Fähigkeiten des Frameworks nutzen kann, um das Optimum aus den eigenen Apps herauszuholen, ohne eine neue Codebasis erstellen zu müssen. Diesen Ansatz kann man nun mit Serverless Backends kombinieren, sodass sich Entwickler nun vollends auf die fachliche Problemstellung konzentrieren können, anstatt Probleme mit der Bereitstellung und Verteilung zu analysieren. Hierbei spielt es keine Rolle, ob Sie die öffentliche Cloud mit Microsoft Azure oder eine On-Premises-Umgebung bevorzugen. Entsprechende Runtimes sind für beide Kombinationen verfügbar, wodurch das Erstellen von Office-Add-ins noch einfacher wird. Dieses Session Video von der BASTA! Spring 2018 bietet einen guten Einstieg in die Arbeit mit der Office-API und zahlreichen Services.

Nutzen Sie das Video doch als Anregung und Vorbereitung, denn auf der BASTA! Spring 2019 wird Thorsten Hans in seinen Sessions “Office Extensibility in 2019: Cross-Plattform Add-ins mit Angular” und “Mehr Daten! Office Add-ins mit Microsoft Graph” weitergehende Möglichkeiten zeigen, wie APIs und Cloud-Services praxisorientiert und effektiv zusammenspielen.

 

The post Office als Plattform: Das Office API und Serverless Backends appeared first on BASTA!.

]]>
Smart Contracts mit .NET Core https://basta.net/blog/smart-contracts-mit-net-core/ Mon, 19 Nov 2018 13:06:42 +0000 https://basta.net/?p=28169 Hyperledger Fabric, eine von The Linux Foundation unterstütze Blockchain, erlaubt das Entwickeln von Smart Contracts in Go, Node.js und Java. Mit einem experimentellen Paket lassen sich die Smart Contracts auch in .NET entwickeln. Dank .NET Core können die Smart Contracts via Docker – dem Deploymentmodell von Hyperledger Fabric – gestartet werden. In diesem Artikel wollen wir die Entwicklung von Smart Contracts mit C# genauer beleuchten.

The post Smart Contracts mit .NET Core appeared first on BASTA!.

]]>

Hyperledger Fabric ist eine unter dem Dachverband Hyperledger mit Go entwickelte Open Source Blockchain. Hyperledger selbst wurde Ende 2015 von The Linux Foundation gegründet. Unter dem Dachverband werden Open Source Blockchains und dazu passende Tools entwickelt. Große Unternehmen wie Intel, IBM, Cisco oder Red Hat unterstützen Hyperledger. Dabei wird nicht nur der Ansatz einer Blockchain, sondern vielmehr verschiedener Blockchain-Plattformen verfolgt.

Genauer handelt es sich bei Hyperledger Fabric (kurz: Fabric) um eine Technologie für Permissioned Blockchains bzw. private Blockchains – also eine Blockchain, die nicht wie Ethereum oder Bitcoin für alle öffentlich, sondern nur bestimmten Teilnehmern zugänglich ist. Dadurch können wir bei diesen Arten von Netzwerken das klassische Mining (Proof-of-Work, PoW) vernachlässigen. Vielmehr kommt hier der Konsens über Proof-of-Authority (PoA) (Kasten: „Proof-of-Authority“) zum Tragen.

Proof-of-Authority (PoA)

Entgegen dem klassischen Mining (Proof-of-Work), das darauf beruht, dass ein Miner ein kryptografisches Rätsel in Form einer Hashberechnung löst, existieren bei Proof-of-Authority (PoA) sogenannte Validator Nodes (oder auch Authorities, als eher physische Entität), denen die Erzeugung von neuen Blöcken erlaubt ist. Wenn die Mehrzahl von Validator Nodes in einem Netzwerk der Erzeugung des neuen Blocks zustimmen, wird dieser ein permanenter Teil des Netzwerks. Vorteil hierbei ist, dass keine CPU-Zeit zur Lösung des kryptografischen Rätsels benötigt wird, eine bessere Performanz erreicht wird und dadurch eine höhere Transaktionsgeschwindigkeit erzielt werden kann. Daher eignet sich PoA für das Enterprise-Umfeld.

Anstelle einer Domänensprache bietet Fabric die Entwicklung von Smart Contracts – oder auch Chaincode, wie das im Hyperledger-Umfeld genannt wird – in den Sprachen Java, Go und JavaScript (via Node.js) an, experimentell kann auch .NET verwendet werden. Folgend werden wir einen Chaincode mit C# entwickeln, der das Standardbeispiel FabCar von Hyperledger implementiert. FabCar dient der Zuordnung von Autos zu ihrem Besitzer. Schreibend erlaubt der Chaincode sowohl das Anlegen eines neuen Eintrags (z. B.: Hat jemand ein neues Auto gekauft?) als auch den Wechsel des Besitzers. Lesend kann die Information über ein bestimmtes Auto oder eine Liste aller Autos erlangt werden.

 

Der .NET-Framework & C# Track auf der BASTA! Spring 2019

 

Vorbereitungen für das Netzwerk

 

Bevor wir mit der Entwicklung starten können, benötigen wir ein Hyperledger-Fabric-Netzwerk. Hierbei bietet sich das Chaincode-Developer-Network (Dev-Netzwerk) an. Normalerweise muss der Chaincode bei einer Änderung deployt und danach instanziiert werden. Bei diesem Vorgang wird von einem Fabric Peer ein Docker-Container mit dem Chaincode gestartet. Dieser Vorgang nimmt durchaus einige Zeit in Anspruch, was die Entwicklung sehr langsam machen würde. Durch das Dev-Netzwerk können wir diesen Vorgang deutlich verkürzen, da wir den Chaincode außerhalb der Docker-Umgebung von Fabric starten.

Auf der Fabric Website Prerequisites ist beschrieben, welche Software benötigt wird. Windows-Nutzer sollten auf jeden Fall einen Blick auf die Seite werfen, da hier noch einige zusätzliche Software installiert werden muss. Linux- und Mac-User benötigen nur Folgendes:

  • Docker (>= 17.06.2)
  • .NET Core SDK
  • IDE nach Wahl für .NET Core (bspw. VS Code oder JetBrains Rider)

 

Ist die Software installiert, muss das Repository fabric-samples geklont werden. Mit einer Kommandozeile innerhalb von fabric_samples muss folgender Befehl ausgeführt werden:

curl -sSL http://bit.ly/2ysbOFE | bash -s 1.2.0

Dieser Befehl lädt alle benötigen Docker-Images und Binaries für die Zielplattform für die (zum Zeitpunkt des Schreibens aktuelle) Hyperleder-Fabric-Version 1.2.0 herunter.

Wurde der Befehl erfolgreich ausgeführt, wechseln wir in das Verzeichnis chaincode-docker-devmode und öffnen die Datei docker-compose-simple.yaml. Dort finden wir den Eintrag peer: auf Root-Ebene und fügen unter ports: eine weitere Zeile mit dem Inhalt 7052:7052 hinzu. Hierbei bitte auf die Einrückung achten, sodass es genau wie die anderen Ports aussieht. Die fertige Eintragung sollte daher in Listing 1 aussehen.

Listing 1

peer:
  # Zur besseren Lesbarkeit wurden die anderen Attribute entfernt
  ports:
    - 7051:7051
    - 7052:7052
    - 7053:7053

Dieser Port öffnet die Schnittstelle für unseren Chaincode, sodass er außerhalb der Docker-Umgebung gestartet und verbunden werden kann. Mit folgendem Befehl auf der Kommandozeile können wir das Netzwerk starten:

docker-compose -f docker-compose-simple.yaml up

Nach einer kurzen Initialisierungsphase sollte das Fabric-Dev-Netzwerk fehlerfrei (sprich ohne Fehlermeldungen in der Logausgabe) gestartet sein. Jetzt können wir mit der Entwicklung von unserem Chaincode beginnen.

 

Entwicklungsmodelle

Die aktuelle Version der Bibliothek zur Entwicklung von Chaincode bietet zwei Entwicklungsmodelle an. Zum einen ein Low-Level-Modell (Chaincode API), das dem Entwickler die maximale Freiheit zur Entwicklung bietet, aber auch die größte Verantwortung zur fehlerfreien Umsetzung. Zum anderen ein High-Level-Modell (Contract API), das dem Entwickler erlaubt, sich auf die Umsetzung seines Business-Use-Cases zu konzentrieren und etwas Verantwortung an das Framework abzugeben. Innerhalb des Artikels werden wir nur mit dem Contract API – also dem High-Level-Modell – arbeiten.

Zunächst erstellen wir ein neues .NET-Core-Konsolen-Projekt, entweder über die Templates in JetBrains Rider oder via dotnet new console in einer Kommandozeile. Danach öffnen wir die dazugehörige *.csproj-Datei, finden die erste und fügen dort den Eintrag 7.3 hinzu. Falls dieser Eintrag bereits existiert, können wir ihn einfach überschreiben. Mit dem Eintrag stellen wir unser Projekt auf die aktuellste Sprachversion von C# um, um später eine asynchrone Main()-Methode verwenden zu können.

Wieder auf der Kommandozeile führen wir folgenden Befehl zur Installation der Bibliothek zur Chaincode-Entwicklung aus. Es lohnt sich, ein Blick in die NuGet Gallery [6] zu werfen, um die aktuellste Version zu installieren:

dotnet add package Thinktecture.HyperledgerFabric.Chaincode --version 1.0.0-prerelease.78

 

FabCar: Basisklassen

 

Im nächsten Schritt legen wir eine neue Klasse FabCar an und lassen sie von der Klasse ContractBase erben (Listing 2). Diese Klasse ContractBase stellt die Basisklasse für alle Contract API basierten Chaincodes dar. Ihr Pendant für die Low-Level-Programmierung wäre das Interface IChaincode. Alle Klassen, die von ContractBase erben, nennen wir Contract (nicht zu verwechseln mit Smart Contract). Ein Chaincode (der eigentliche Smart Contract) kann aus mehreren Contracts bestehen. Hierbei würde jeder Contract eine bestimmte Funktionalität des gesamten Chaincodes zur Verfügung stellen – sie dienen daher einer Art Gruppierung.

Listing 2: Die Klasse „FabCar“

using Thinktecture.HyperledgerFabric.Chaincode.Contract;

public FabCar(): base("FabCar")
{
}

Der String FabCar beim Aufruf von base() ist der Namespace, der später zum Aufruf unseres Chaincode benötigt wird. Übrigens, die Klasse integriert sich später in den durch .NET Core bereitgestellten Dependency Injection Container. Es kann daher wie gewohnt die Dependency Injection benutzt werden, bspw. könnten wir einen ILogger hinzufügen, wenn wir in unserem Contract loggen möchten.

Wie eingangs erwähnt, wollen wir mit FabCar Autos mit ihren Besitzern speichern und abrufen. Dazu erstellen wir mit dem Code aus Listing 3 eine neue Klasse Car.

Listing 3: Die Klasse „Car“

  public class Car
{
  public Car()
  {
  }

  public Car(string make, string model, string color, string owner)
  {
    Make = make;
    Model = model;
    Color = color;
    Owner = owner;
  }

  public string Make { get; set; }
  public string Model { get; set; }
  public string Color { get; set; }
  public string Owner { get; set; }

  public override string ToString()
  {
    return $"{Make} {Model} in {Color} with owner {Owner}";
  }
}

Die Klasse Car hat einige Eigenschaften wie den Hersteller (Make), das Modell (Model), die Farbe (Color) und den aktuellen Besitzer (Owner). Hier können gerne nach Belieben weitere Eigenschaft hinzugefügt werden, bspw. die Pferdestärken oder das Gewicht. Der parameterlose Konstruktor wird für die (De-)Serialisierung von und nach JSON benötigt.

Als nächstes legen wir alle Methoden aus Listing 4 an, die wir nach und nach implementieren werden. Beim späteren Start des Chaincodes werden via Reflection alle Methoden mit folgendem Schema gesucht:

public [async] Task MethodName(IContractContext context [, string param1]*)

Schnittstellenbedingt werden alle Parameter, die der Methode übergeben werden, als String übergeben. Ein Parsen in den richtigen Datentyp muss die Methode selbst vornehmen. Die gefundenen Methoden werden öffentlich zur Verfügung gestellt und können vom Blockchain-Netzwerk aufgerufen werden.

Listing 4: Methodenrümpfe des Chaincodes

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html"
        ],
        "versionedFiles": [
          "/*.bundle.css",
          "/*.bundle.js",
          "/*.chunk.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**"
        ]
      }
    }
  ],
  "dataGroups": [
    {
      "name": "pokemon",
      "urls": [
        "https://pokemon.co/*"
      ],
      "cacheConfig": {
        "strategy": "performance"
      }
    }
  ]
}

 

FabCar: „Init“-Methode

Starten wir mit der Implementierung der Init()-Methode. Sie wird bei der Instanziierung unserer Chaincodes aufgerufen. Wenn wir nichts zu initialisieren hätten, müssten wir diese Methode nicht implementieren. Listing 5 zeigt die Implementierung unserer Methode, die einige Autos mit ihren Besitzern initialisiert.

Listing 5: Die Methode Init()

public async Task Init(IContractContext context)
{
  var cars = new List
  {
    new Car("Toyota", "Prius", "blue", "Tomoko"),
    new Car("Ford", "Mustang", "red", "Brad"),
    new Car("Hyundai", "Tucson", "green", "Jin Soo"),
    new Car("Volkswagen", "Passat", "yellow", "Max"),
    new Car("Tesla", "S", "black", "Michael"),
    new Car("Peugeot", "205", "purple", "Michel")
  };

  for (var index = 0; index < cars.Count; index++)
  {
    var car = cars[index];
    await context.Stub.PutStateJson($"CAR{index}", car);
  }

  return ByteString.Empty;
}

Schauen wir uns im Detail an, was genau hier passiert. Zunächst erstellen wir eine Liste von Car mit einigen Beispielinhalten. Danach iterieren wir über die Liste und rufen context.Stub.PutStateJson() auf. Die Variable context, die als Parameter in die Methode gereicht wird, beinhaltet den aktuellen Ausführungskontext unserer Contracts. In ihr befindet sich der Stub, der ein API zwischen Chaincode und dem Fabric Peer bereitstellt. Mit Hilfe des Stubs können Daten im Asset Store (Kasten: „Asset Store“) gespeichert, ausgelesen, geändert und gelöscht werden. Und genau das machen wir mit PutStateJson(): Wir speichern unter dem Schlüssel CAR0 einen JSON-String mit dem dazugehörigen Auto. Konnten wir alle Einträge erfolgreich speichern, geben wir einen leeren ByteString zurück. Falls zwischendurch ein Fehler auftritt, wird dieser durch die Bibliothek verarbeitet.

Asset Store

Bei Fabric ist der Asset Store oder auch State Database, also der aktuelle Zustand unserer Daten der Blockchain, zunächst eine LevelDB, ein einfacher Key-Value-Speicher. Dieser erlaubt es nicht, komplexere Query-Abfragen (ähnlich SQL) entgegen zu nehmen. Für diesen Fall kann LevelDB gegen eine CouchDB ausgewechselt werden, die auf Basis von JSON-Strukturen komplexe Query-Abfragen erlaubt. Weitere Informationen hierzu sind in der Dokumentation zu finden.

 

FabCar: „CreateCar“-Methode

Als nächstes implementieren wir die Methode CreateCar() mit dem Inhalt aus Listing 6.

Listing 6: Die Methode „CreateCar()“

public async Task CreateCar(IContractContext context, string carNumber, string make, string model, string color, string owner)
{
  var car = new Car(make, model, color, owner);

  await context.Stub.PutStateJson(carNumber, car);

  return ByteString.Empty;
}

Hier sehen wir, dass die Methode zusätzliche String-Parameter übergeben bekommt. Nämlich genau die, die wir benötigen, um ein neues Auto mit seinem Besitzer zu erstellen. Hier sei angemerkt, dass wir innerhalb der Methode keine Prüfung vornehmen, ob dieses Auto bereits existiert. Das kann gerne als kleine Hausaufgabe selbst implementiert werden. Die Methode ist daher recht einfach gehalten: Neues Auto erstellen, über PutStateJson() speichern und bei Erfolg einen leeren ByteString zurückgeben.

FabCar: „ChangeCarOwner“-Methode

Die Methode ChangeCarOwner() veranlasst einen Besitzerwechsel eines Autos. In Listing 7 ist die Implementierung zu finden.

Listing 7: Die Methode „ChangeCarOwner()“

public async Task ChangeCarOwner(IContractContext context, string carNumber, string owner)
{
  var car = await context.Stub.GetStateJson(carNumber);
  car.Owner = owner;

  await context.Stub.PutStateJson(carNumber, car);

  return ByteString.Empty;
}

Die nächste zu implementierende Methode ist QueryCar(). Sie ruft einen einzelnen Wagen mit all seinen Eigenschaften aus dem Asset Store auf und gibt ihn an den Aufrufer zurück. Die Implementierung ist in Listing 8 zu finden.

FabCar: „QueryCar“-Methode

Die Methode ChangeCarOwner() veranlasst einen Besitzerwechsel eines Autos. In Listing 7 ist die Implementierung zu finden.

Listing 8: Die Methode „QueryCar()“

public async Task QueryCar(IContractContext context, string carNumber)
{
  var carBytes = await context.Stub.GetState(carNumber);

  if (carBytes == null || carBytes.Length == 0) throw new Exception($"Car {carNumber} does not exist.");

  return carBytes;
}

Hier ist anzumerken, dass wir die Methode GetState() verwenden, die uns einen ByteString liefert, und nicht GetStateJson(). Die Methode ist nicht am deserialisierten Zustand des Autos interessiert. Sie prüft daher nur, ob ein Ergebnis vom Asset Store geladen werden konnte (sprich: es ist weder null noch hat der ByteString eine Länge gleich 0). Falls ja, gibt sie das Ergebnis an den Aufrufer zurück. Möchte man die Ausführung der Methode abbrechen (bspw. bei falschen Eingabeparametern oder wie im Beispiel: Auto nicht gefunden) werfen wir eine Exception. Die Bibliothek übersetzt jede geworfene Exception in einen Fehlerstatus und gibt diesen an den Aufrufer zurück, sodass die Ausführung des Chaincodes nicht abgebrochen wird.

FabCar: „QueryAllCars“-Methode

Zu guter Letzt wollen wir die Methode QueryAllCars() implementieren. Sie soll alle gespeicherten Autos abrufen. Die Implementierung ist in Listing 9 zu finden.

Listing 9: Die Methode QueryAllCars

public async Task QueryAllCars(IContractContext context)
{
  var startKey = "CAR0";
  var endKey = "CAR999";

  var iterator = await context.Stub.GetStateByRange(startKey, endKey);

  var result = new List();

  while (true)
  {
    var iterationResult = await iterator.Next();

    if (iterationResult.Value != null && iterationResult.Value.Value.Length > 0)
    {
      var queryResult = new CarQueryResult
      {
        Key = iterationResult.Value.Key
      };

      try
      {
        queryResult.Record = JsonConvert.DeserializeObject(iterationResult.Value.Value.ToStringUtf8());
                
        result.Add(queryResult);
      }
      catch (Exception ex)
      {
        // Error loggen
      }
    }

    if (iterationResult.Done)
    {
      await iterator.Close();
      return JsonConvert.SerializeObject(result).ToByteString();
    }
  }
}

public class CarQueryResult
{
  public string Key { get; set; }
  public Car Record { get; set; }
}

Wie erwähnt handelt es sich beim Asset Store per Standard um eine LevelDB, einen einfachen Key-Value-Speicher. Dieser erlaubt keine komplexeren Abfragen (Kasten: „Asset Store“). Um dennoch alle Autos abzufragen, iterieren wir selbst über den Asset Store. Hierzu definieren wir einen Start- (CAR0) und Endpunkt (CAR999). Das bedeutet, dass wir versuchen, die ersten 1 000 Autos aus dem Asset Store zu laden. Wer möchte, kann diese Parameter auch als Methodenparameter übergeben. Via GetStateByRange() erzeugen wir uns einen Iterator auf den Asset Store innerhalb unserer Grenzen.

Über eine while(true)-Schleife wird eine Iteration über den Asset Store gestartet. Via iterator.Next() erhalten wir das nächste mögliche Ergebnis aus dem Store. Wir prüfen als nächstes, ob wir ein Ergebnis haben und die Länge größer 0 ist. Ist das der Fall, versuchen wir den erhaltenen ByteString via StringToUtf8() in seine UTF-8-Repräsentation umzuwandeln und danach zu deserialisieren. Falls hierbei ein Fehler auftritt, wird dieser in unserem Beispiel ignoriert.

Zum Schluss prüfen wir, ob die Iteration abgeschlossen ist. Wenn ja, schließen wir den Iterator via iterator.Done() und geben unsere Liste an den Aufrufer zurück. Damit ist die Entwicklung unseres Contract abgeschlossen.

Anpassung der „Main“-Methode

Zu Beginn haben wir ein neues Konsolenprojekt erstellt, was uns eine Datei Program.cs mit einer Main()-Methode generiert hat. Diese passen wir gemäß Listing 10 an.

Listing 10: Die Methode „Main()“

class Program
{
  static async Task Main(string[] args)
  {
    using (var provider = ChaincodeProviderConfiguration.ConfigureWithContracts(args))
    {
      var shim = provider.GetRequiredService();
      await shim.Start();
    }
  }
}

Um den Chaincode zu starten, benötigen wir ChaincodeProviderConfiguration.ConfigureWithContracts(args). Hinter ChaincodeProviderConfiguration verbirgt sich der Aufbau der Dependency Injection des Chaincodes. Mit ConfigureWithContracts() teilen wir dem Container mit, dass wir gerne den Contract FabCar in unserem Chaincode nutzen wollen. Via Methodenüberladung können bis zu vier Contracts über eine generische Methode hinzugefügt werden. Alternativ kann der Methode auch ein Array aller Contracts übergeben werden. Damit sind auch mehr als vier Contracts pro Chaincode möglich.

Aus unserem Provider lassen wir uns eine Instanz von Shim via GetRequiredService() erzeugen. Der Shim ist die Schnittstelle zwischen dem Fabric Peer und unserem Chaincode. Er übernimmt jegliche Nachrichten- und Fehlerbehandlungen. Zu guter Letzt starten wir via shim.Start() das Kommunikationsprotokoll zwischen Chaincode und Fabric Peer. Damit sind alle Codearbeiten abgeschlossen.

It compiles, ship it!

Der letzte Schritt ist das Deployment bzw. die Instanziierung des Chaincode. Hierzu benötigen wir ein neues Kommandozeilenfenster, in dem wir folgenden Befehl ausführen:

docker exec -it cli bash

Dieser Befehl verbindet uns zum Fabric CLI, das uns die Kommunikation mit einem Fabric Peer erlaubt. Wie eingangs erwähnt, wird der Chaincode normalerweise in einem Docker-Container gestartet, namentlich innerhalb von fabric-ccenv (ccenv: Chaincode Environment). Dieser bietet aktuell Unterstützung für Go und Node.js Chaincodes, aber noch nicht für .NET Core. Zumal wollen wir nicht zur Entwicklungszeit den Chaincode in einem Docker-Container starten. Dennoch müssen wir ein normales Deployment anstoßen, damit unser Chaincode erkannt wird. Innerhalb der eben geöffneten Kommandozeile, die mit dem Fabric CLI verbunden ist, tippen wir folgende Befehle ein:

mkdir /opt/gopath/src/chaincodedev/chaincode/netcore

echo "dev" > /opt/gopath/src/chaincodedev/chaincode/netcore/dev

peer chaincode install -p /opt/gopath/src/chaincodedev/chaincode/netcore -n fabcar -v 0 -l node

Die ersten beiden Befehle erstellen einen neuen Ordner netcore mit einer Datei namens dev und dem Inhalt dev. Das dient als reiner Fake-Chaincode, damit Fabric etwas deployen kann, denn ein leerer Ordner/Chaincode kann nicht deployed werden. Der letzte Befehl installiert bzw. deployt den Chaincode. Via peer chaincode install teilen wir mit, dass wir gerne neuen Chaincode installieren möchten. -p mit Pfadangabe gibt an, wo unser Fake-Chaincode liegt, -n gibt den Namen, -v die Version und -l die Sprache des Chaincodes an. Hier merkt man stark, wie experimentell unser .NET-Beispiel noch ist, da wir hier tatsächlich node als Sprache angeben. Das führt prinzipiell nur dazu, dass Fabric den angegebenen Ordner in den Peer kopiert. Damit ist unser Fake-Chaincode installiert. Würden wir hier ein reales Deployment anstreben, würden wir selbstverständlich den echten Code bzw. dessen Binary kopieren. Der Fake-Chaincode dient nur zur Entwicklungszeit. Auch wird, sobald Fabric offiziell .NET unterstützt, die Angabe von node in netcore geändert.

Jetzt ist es an der Zeit, unseren Chaincode zu starten. Wichtig ist, dass folgende Umgebungsvariablen gesetzt sind:

  • CORE_PEER_ADDRESS=localhost:7052
  • CORE_CHAINCODE_ID_NAME=fabcar:0

CORE_PEER_ADDRESS gibt an, mit welchem Peer sich unser Chaincode für die Kommunikation verbinden soll. CORE_CHAINCODE_ID_NAME ist die Identifikation unseres Chaincode. Mit fabcar:0 zeigen wir hier auf den eben installierten fabcar mit Version 0. Nach dem Start sollte innerhalb kurzer Zeit im Log des Chaincodes der Eintrag Successfully established communication with peer node erscheinen. Ist das der Fall, hat sich der Chaincode erfolgreich zum Peer verbunden, und wir können diesen jetzt instanziieren. Der Befehl hierzu, der wieder innerhalb des Fabric CLI eingegeben werden muss, lautet wie folgt:

peer chaincode instantiate -n fabcar -v 0 -c '{"Args": ["FabCar.Init"]}' -C myc

Mit den Argumenten -n und -v sprechen wir wieder unseren Chaincode an. -c ist die sogenannte Constructor-Message, ein String mit JSON-Format, der unserem Chaincode gesendet, durch den Shim geparst und dem Contract zur Verfügung gestellt wird. Die Argumente sind eine Liste von Strings. Das erste Argument ist immer die Methode, die im Chaincode aufgerufen werden soll. Es setzt sich zusammen aus dem Namespace (FabCar) und dem Methodennamen (Init) getrennt durch einen Punkt. Den Namespace hatten wir zuvor im Konstruktor der Klasse FabCar gesetzt, und Init meint die tatsächliche Methode Init() der FabCar-Klasse. Wichtig: Die Bezeichnung beachtet Groß- und Kleinschreibung. Das letzte Argument -C bestimmt den Fabric Channel.

Ab diesem Zeitpunkt können wir unseren Chaincode nach Belieben stoppen, Code ändern und neu starten, ohne dass wir ein neues Deployment oder eine neue Instanziierung durchführen müssen, was zu einer erheblichen Entwicklungsgeschwindigkeit beiträgt.

 

Chaincode benutzen

Nachdem unser Chaincode läuft, wollen wir ihn benutzen. Alle folgenden Befehle werden wieder im Fabric CLI eingetippt. Wenn wir uns beispielsweise das Auto mit der Nummer CAR2 anzeigen lassen wollen, benutzen wir folgenden Befehl:

peer chaincode query -n fabcar -c '{"Args":["FabCar.QueryCar","CAR2"]}' -C myc

query gibt an, dass wir nur lesend auf das Netzwerk zugreifen und die Methode FabCar.QueryCar mit dem Parameter CAR2 aufrufen. Wenn alles erfolgreich durchläuft, erhalten wir folgendes Ergebnis:

{"Make": "Hyundai", "Model": "Tucson", "Color": "green", "Owner": "Jin Soo"}

Super! Allerdings gefällt uns der Wagen von Jin Soo so gut, dass wir ihm diesen abkaufen wollen. Diesen Kauf wollen wir vermerken:

    peer chaincode invoke -n fabcar -c 
'{"Args":["FabCar.ChangeCarOwner","CAR2","Manuel Rauber"]}' -C myc

invoke stellt einen schreibenden Zugriff auf das Netzwerk dar, und zwar soll die Methode FabCar.ChangeCarOwner mit den zwei angegebenen Parameter CAR2 und Manuel Rauber aufgerufen werden. Erhalten wir keine Fehlermeldung bei der Ausführung (denn gemäß Listing 7 geben wir einen leeren ByteString zurück), war der Aufruf erfolgreich. Um das zu prüfen, können wir entweder wieder QueryCar benutzen, oder wir lassen uns alle Einträge ausgeben:

peer chaincode query -n fabcar -c '{"Args":["FabCar.QueryAllCars"]}' -C myc

Als Ergebnis erhalten wir eine serialisierte Liste aller Autos und siehe da: Der grüne Hyundai Tucson gehört fortan dem Autor des Artikels.

 

Fazit

Herzlichen Glückwunsch, wir haben es geschafft! Wir haben soeben das Dev-Netzwerk von Hyperleder Fabric gestartet, unseren ersten experimentellen .NET Chaincode entwickelt, ihn deployt, instanziiert und dessen Methoden aufgerufen. Auch haben wir gesehen, dass wir zwar .NET benutzen können, es aber noch an einigen Ecken und Enden fehlt, damit es sich auch wirklich gut anfühlt (und wir nicht via node deployen müssen). Wer sich hier für den Fortschritt interessiert, kann einen Blick auf den entsprechenden JIRA-Task und das dazugehörige GitHub-Repo werfen. Weitere Informationen zum Chaincode in .NET Core sind auch auf dem Thinktecture-Blog zu finden. Ich wünsche viel Spaß beim weiteren Ausprobieren!

 

Hyperledger Fabric und Smart Contracts auf der BASTA!


Passend zum Thema dieses Artikels finden Sie auf der BASTA! z. B. den Power Workshop „Von Null auf Hundert – Blockchain-Anwendungen mit Hyperledger Fabric“ von Ingo Rammer am Freitag.

The post Smart Contracts mit .NET Core appeared first on BASTA!.

]]>
.NET Core 2.1, 2.2 und 3.0 – neue Funktionen https://basta.net/blog/net-core-2-1-2-2-und-3-0-neue-funktionen/ Tue, 30 Oct 2018 09:21:35 +0000 https://basta.net/?p=28087 Bisher war .NET Core für viele Szenarien kein Thema, einfach weil die notwendigen Klassen fehlten. In .NET Core 2.1 hat Microsoft massiv in der Klassenbibliothek nachgerüstet. Mit .NET Core 3.0 kommen sogar WPF und Windows Forms nach .NET Core – aber nur für Windows. Was das im Detail bedeutet, wird im Folgenden erklärt.

The post .NET Core 2.1, 2.2 und 3.0 – neue Funktionen appeared first on BASTA!.

]]>

Nachdem .NET Core 2.0 am 14. August 2017 erschienen war, hatte Microsoft die Version 2.1 für das erste Quartal 2018 angekündigt. Dass dieser Termin nicht zu halten sein wird, wurde dann im Februar 2018 verkündet und als neues Ziel der Sommer 2018 angegeben. Aus dem Sommer wurde dann der Frühsommer: .NET Core 2.1 und damit einhergehend ASP.NET Core 2.1 und Entity Framework 2.1 sind am 30. Mai 2018 erschienen.

Windows Compatibility Pack for .NET Core

Ein wesentliches Thema in den 2.1-Versionen ist die Erweiterung der Funktionen, insbesondere durch die Übernahme weiterer Klassen aus dem klassischen .NET Framework. Das Windows Compatibility Pack for .NET Core bringt viele Hundert Klassen aus der bisherigen .NET Framework Class Library als Nuget-Paket in die .NET-Core-Welt. Das Nuget-Paket Microsoft.Windows.Compatibility ist ein Metapaket über viele andere Nuget-Pakete. Die Paketnamen sind angelehnt an die bisherigen Namensräume aus der .NET Framework Class Library. Funktionen wie Registry-Zugriff, Datenbankzugriffe per ODBC, LINQ für DataSets, das Code Document Object Model (CodeDOM), Zugriff auf serielle Ports und LDAP-Server wie das Active Directory sowie die Windows Management Instrumentation (WMI), mit der man Informationen aus dem Betriebssystem und einigen Anwendungen auslesen und verändern kann, halten damit Einzug in die Welt von .NET Core. Freilich sind viele dieser Klassen nur auf Windows möglich, daher auch der Name „Windows“ im Compatibility Pack.

Man kann in jedem Fall eine Self-contained Application (Abb. 1) auch für Linux und macOS erstellen, auch wenn manche Klassen aus dem Windows Compatibility Pack, die für diese Betriebssysteme nicht von Microsoft implementiert wurden bzw. dort zum Teil keinen Sinn machen (z. B. die Registry-Klassen). Wenn man die Self-contained Application dann auf diesen Betriebssystemen startet, erhält man Laufzeitfehlermeldungen wie z. B.:

  • System.PlatformNotSupportedException: Windows Principal functionality is not supported on this platform.
  • System.TypeInitializationException: The type initializer for ‘Microsoft.Win32.Registry’ threw an exception. —> System.PlatformNotSupportedException: Registry is not supported on this platform.
  • System.PlatformNotSupportedException: System.Management currently is only supported for Windows desktop applications.

 

Abb. 1: Erstellen einer Self-contained App für Linux für eine Konsolenanwendung, die Linux verwendet

 

“Man sieht hier an den unterschiedlichen Fehlerklassen und Meldungstexten deutlich, dass verschiedene Entwickler am Werk waren. Einige Klassen aus dem Windows Compatibility Pack laufen auch außerhalb von Windows. Leider schweigt sich die offizielle Dokumentation dazu aus; es gibt jedoch dazu einen Blogeintrag.

So läuft zum Beispiel System.Runtime.Caching problemlos unter Linux und macOS, denn diese Bibliothek hat keine Abhängigkeiten von Betriebssystemteilen. Im Einzelfall wird man dann aber feststellen, dass mehr notwendig ist, als das Windows Compatibility Pack in die Self-contained Application zu packen. So scheitert ein ODBC-Zugriff mit der .NET-Klasse System.Data.Odbc.OdbcConnection auf Linux auf einen Microsoft-SQL-Server mit dem Fehler „System.DllNotFoundException: Dependency unixODBC with minimum version 2.3.1 is required. Unable to load shared library ‘libodbc.so.2’ or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibodbc.so.2.so: cannot open shared object file: No such file or directory”. Die Lösung ist, dass man erstmal den ODBC-Treiber für Microsoft-SQL-Server auf Linux installieren muss..

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Auch für System.Drawing braucht man unter Linux und macOS eine Zusatzinstallation von libgdiplus, einer Open-Source-Implementierung des GDI+ API aus dem Mono-Projekt.

Mit dem NuGet-Paket Microsoft.DotNet.Analyzers.Compatibility bietet Microsoft immerhin einen Visual-Studio-Analyzer an, der alle Klassen und Klassenmitglieder markiert, die nicht plattformneutral sind, siehe z. B. Registry.LocalMachine.OpenSubKey in Abbildung 2. Auch auf veraltete APIs (z. B. System.Net.WebClient, System.Security.SecureString) weist der Analyzer hin. Der Analyzer ist aber leider auch schon wieder etwas verwahrlost. Die aktuelle Version ist 0.2.12-alpha und ist am 7. März 2018 erschienen.

 

Abb. 2: .NET Framework vs. .NET Core: Neu ab .NET Core 3.0 sind WPF und Windows Forms

 

Abb. 3: Meldung des Visual-Studio-Analyzers „Microsoft.DotNet.Analyzers.Compatibility“

 

Das Windows Compatibility Pack trägt in der ersten Version die Versionsnummer 2.0, was ausdrücken soll: Es läuft nicht nur auf .NET Core 2.1, sondern auch auf 2.0. .NET Core 2.0 sollte man allerdings nun gar nicht mehr verwenden (siehe Kasten „Support für .NET Core 2.0 läuft schon am 1.10.2018 aus!“).

Technisch ist das Windows Compatibility Pack eine Schicht oberhalb von .NET Standard 2.0 (Abb. 3). Daher kann das Windows Compatibility Pack nicht nur in .NET-Core-Projekten, sondern auch in Projekten mit dem Projekttyp „.NET Standard 2.0“ referenziert werden.

Neues in .NET Core 2.1

Neben Klassen aus dem klassischen .NET Framework hat Microsoft auch ganz neue Klassen mit .NET Core 2.1 eingeführt. Dazu gehören die Datenstrukturen Span, ReadOnlySpan, Memory und ReadOnlyMemory, die ab C# 7.2 ein direktes Adressieren von Teilen von Arrays und anderen Speicherbereichen ermöglichen. Neu ist auch die Brotli-Komprimierung nach RFC 7932 im Paket System.IO.Compression.Brotli. Bei den Kryptografie-APIs gibt es viele kleine Neuerungen.

Auch an der Runtime hat Microsoft gefeilt. .NET Core 2.1 kann schneller kompilieren (sowohl im Sprachcompiler zur Entwicklungszeit als auch zur Laufzeit im Just-in-Time-Compiler). Ebenso die Netzwerkbibliotheken (u. a. die Klasse System.Net.Http­Client) hat das Entwicklungsteam optimiert. Die Tiered Compilation, durch die der Just-in-Time-Compiler zunächst den Fokus auf schnelle Übersetzung statt optimalem Ergebnis legt und dann für häufiger verwendete Methoden die Übersetzung nachträglich optimiert, ist in .NET Core 2.1 aber nur in einer Vorschauversion enthalten. Diese Funktion muss der Anwendungsbetreiber explizit durch das Setzen einer Umgebungsvariablen aktivieren: COMPlus_TieredCompilation=”1″. Unglaublich, dass „COMPlus“ als Name auch siebzehn Jahre nach dem Erscheinen der ersten .NET-Version noch weiterlebt.

ASP.NET Core 2.1

In ASP.NET Core 2.1 gibt es einige Neuerungen für Web-APIs: Mit der neuen Annotation [ApiController] auf Ebene der Controllerklassen gibt es mehr Automatismen bezüglich Parameterbindung und Validierung. So sucht der Model Binder nun komplexe Typen – wie früher im klassischen ASP.NET-Web-API – im Inhalt der HTTP-Nachricht statt im Querystring. Wenn Validierungen fehlschlagen und dadurch der Model State ungültig ist, liefert der Controller jetzt automatisch den HTTP-Statuscode 400 (Bad Request) an den Aufrufer.

Mit dem neuen Rückgabetyp ActionResult kann der Entwickler HTTP-Statuscodes setzen und zur gleichen Zeit ein typisiertes Objekt für den Inhalt der Antwort festlegen, was die Generierung von Swagger-OpenAPI-Dokumentationen vereinfacht. Bei der Verwendung der untypisierten Schnittstelle IActionResult musste der Entwickler bisher Swagger durch eine Annotation über den Ergebnistyp informieren. Microsoft unterstützt nun auch die Übermittlung von Problemdetails in Web-APIs nach RFC 7808.

.NET-Famework & C# Track auf der BASTA!

In ASP.NET Core 2.1 hat Microsoft Areas nachgerüstet, um Teilbereiche innerhalb eines Projekts zu trennen – das gab es schon im alten ASP.NET MVC. Ein Einsatz für Areas in ASP.NET Core sind die Themengebiete Benutzeranmeldung und Benutzerverwaltung beim Server-Side-Rendering nach dem Model-View-Controller-Modell oder per Razor Pages. Wenn man in Visual Studio 2017 ab Version 15.7 ein neues Webprojekt für ASP.NET 2.1 anlegt, findet man im Ordner Area einen Unterordner Identity. Dort ist zunächst nur eine einzelne Datei _ViewStart.cshtml hinterlegt. Sowohl die Seitenlayouts als auch den zugehörigen Programmcode findet man in der Assembly Microsoft.AspNetCore.Identity.UI.dll. Microsoft kapselt diese Funktionen, man kann eine einfache Anpassung über die Wahl der Layoutseite in _ViewStart.cshtml vornehmen. Wem das nicht reicht, der kann sich den Quellcode des o. g. Assembly über die Funktion Add Scaffold/Identity in Visual Studio ausgeben lassen. Wenn man sich die dann generierten Seiten ansieht, fällt auf: Es sind Razor Pages, selbst wenn man als Projektvorlage nicht Razor Pages, sondern Model-View-Controller gewählt hat. Microsoft hatte ja schon bei ASP.NET 2.0 klargemacht, dass Razor Pages nun das bevorzugte Modell für Server-Side-Rendering sind.

Für Razor Pages führt Microsoft weitere Neuerungen ein: vereinfachte Datenbindung in Razor mit der Annotation [BindPropertyAttribute] zentral auf eine Page-Model-Klasse statt auf einzelne Properties, und mit der Schnittstelle IPageFilter können Entwickler eigene Logik nun vor und nach dem Razor Page Handler ausführen. Immerhin auch für MVC-Projekte gilt: Die Projektvorlagen berücksichtigen die EU-GDPR (in Deutschland: DSGVO) und fordern nun vom Benutzer das Einverständnis für Cookies ein. Wenn der Benutzer nicht zustimmt, werden automatisch alle Cookies unterdrückt, die nicht als Essential markiert sind.

ASP.NET SignalR für bidirektionale Kommunikation zwischen Webserver und Browser ist nun in einer Core-Version 1.0 als Zusatz zu ASP.NET Core 2.1 verfügbar. Auf später verschoben wurden aber die WebHooks ebenso wie die schnellere In-Process-Integration in Internet Information Services (IIS).

Entity Framework Core 2.1

Microsofts überarbeiteter OR-Mapper macht in Version 2.1 einen größeren Schritt nach vorne, insbesondere wurden vier Funktionen nachgerüstet, die es im klassischen ADO.NET Entity Framework schon gab:

  • Lazy Loading (wahlweise mit Runtime Proxies oder Ergänzungen der Entitätsklassen; anders als im ­ADO. NET Entity Framework ist Lazy Loading eine Option und nicht mehr automatisch aktiv)
  • Übersetzung des LINQ-Operators GroupBy() in SQL (in Version 1.x und 2.0 wurden tatsächlich alle GroupBy()-Operationen im RAM ausgeführt, was bei großen Datenmengen zu indiskutablen Ausführungsgeschwindigkeiten führt. In Version 2.1 werden einige, aber noch immer nicht alle GroupBy()-Fälle in SQL übersetzt)
  • ambiente Transaktionen mit System.Transactions.TransactionScope
  • Data Seeding im Rahmen von Schemamigrationen

Darüber hinaus enthält Entity Framework Core auch einige ganz neue Funktionen, die es nicht im klassischen ADO.NET Entity Framework gab:

  • Wertkonvertierung beim Materialisieren und Speichern von Objekten mit Value-Converter-Funktionen
  • Entitätsklassen dürfen nun Parameter im Konstruktor besitzen
  • Unterstützung für Owned Types mit der Annotation [Owned]
  • Zustandsänderungsereignisse im Change Tracker

Es bleibt aber dabei, dass es Schwächen in Entity Framework Core gibt. Der LINQ-Befehl in Listing 1 wird auch in Entity Framework Core Version 2.1 nicht komplett in SQL übersetzt. Wie Listing 2 zeigt, fehlen Gruppierung und Aggregatoperatoren im SQL-Befehl. Der ORM liest hier weiterhin alle Datensätze aus der Tabelle und macht den Rest im RAM. Von Microsoft liest man dazu: „We now support translating it to the SQL GROUP BY clause in most common cases“. Schade, dass so ein einfacher LINQ-Befehl wie der in Listing 1 nicht dazugehört.

Auch fehlen in Entity Framework Core 2.1 weiterhin die Unterstützung für das Table-per-Hierarchy-(TPH-)Mapping, die Abstraktion von der Zwischentabelle beim N:M-Mapping und Validierung vor dem Speichern. Auch ein Update Model from Database gibt es nicht für den vom Reverse Engineering generierten Programmcode, wenn sich das Datenbankschema danach noch ändert.

Listing 1: LINQ-Befehl mit GroupBy()-Operator

var groups1 = (from p in ctx.FlightSet
               group p by p.Departure into g
               select new { City = g.Key, Min = g.Min(x => x.FreeSeats), Max = g.Max(x => x.FreeSeats), Sum = g.Sum(x => x.FreeSeats), Avg = g.Average(x => x.FreeSeats) });

Listing 2: SQL-Übersetzung von Listing 1 – die Gruppierung fehlt

SELECT [p].[FlightNo], [p].[AircraftTypeID], [p].[AirlineCode], [p].[CopilotId], [p].[FlightDate], [p].[Departure], [p].[Destination], [p].[FreeSeats], [p].[LastChange], [p].[Memo], [p].[NonSmokingFlight], [p].[PilotId], [p].[Price], [p].[Seats], [p].[Strikebound], [p].[Timestamp], [p].[Utilization]
FROM [Flight] AS [p]
ORDER BY [p].[Departure]

 

Version 2.2 soll Ende 2018 erscheinen

Zwischen Version 2.0 und 2.1 gab es sehr viele Neuerungen – mehr als zwischen 1.1 und 2.0. Gemäß Semantic Versioning ist die Änderung der ersten Versionsnummer kein Indikator für den Umfang der Neuerungen, sondern drückt nur aus, dass es Breaking Changes gab. Version 2.2 ist funktional wieder ein kleineres Release, das bis Jahresende 2018 erscheinen soll.

In ASP.Net Core 2.0 will Microsoft das Routing überarbeiten. Das neue Routingsystem (derzeit „Dispatcher“ genannt) soll sehr früh in der Verarbeitungspipeline laufen und einige Szenarien wie die Unterstützung von CORS vereinfachen. Für Web-APIs soll die Generierung von Swagger-OpenAPI-Dokumenten und Hilfeseiten abermals durch Konventionen vereinfacht werden, z. B. soll ein Verb wie Create zu Beginn des Namens einer Action-Methode automatisch dazu führen, dass der Rückgabestatuscode 201 ist.

Bei den Werkzeugen will Microsoft die Analyse von Web-API-Konventionen in Visual Studio ermöglichen. Das Kommandozeilenwerkzeug dotnet soll beim Test HTTP-Anfragen mit beliebigen HTTP-Verben unterstützen. Sowohl für die Kommandozeile als auch Visual Studio will Microsoft einen eigenen Codegenerator für Clientzugriffscode (in C# und TypeScript) für Web-APIs anbieten. SignalR soll Unterstützung für Clients in Java und C++ erhalten. Der Kestrel-Webserver soll HTTP/2 und Health Checks zur Überwachung unterstützen.

Entity Framework Core 2.2 soll beim Reverse Engineering bestehender Datenbanken nun auch Datenbank-Views generieren. Aus dem alten ADO.NET Entity Framework soll auch die Unterstützung für Spatial Types (geometry und geography) übernommen werden. Die in Version 2.0 eingeführten Owned Types sollen in Version 2.2 auch bei Collection-Navigationseigenschaften möglich sein. Bisher sind sie nur für Einzelobjekte erlaubt. Der schon für Version 2.1 geplante, aber dann verschobene Treiber für Azure Cosmos DB soll erscheinen. Das Entwicklungsteam hat außerdem versprochen, mehr Unittests zu schreiben – und damit weniger Fehler zu produzieren, die erst den Kunden auffallen.

 

Mit Version 3.0 kommt .NET Core auf den Desktop

Bisher konnte man mit .NET Core nur Web-APIs, Webanwendungen und Konsolenanwendungen sowie Universal-Windows-Platform-Apps erstellen. Entwickler klassischer Desktopanwendungen schauten in die Röhre. Microsoft hat auf der BUILD-Konferenz 2018 verkündet, dass die Windows Presentation Foundation (WPF) und sogar die „alte Tante“ Windows Forms im Rahmen von Version Core 3.0 auf .NET Core portiert werden. Sie laufen dann als Nuget-Pakete, allerdings nur auf Windows. Weder WPF noch Windows Forms werden durch diese Maßnahme plattformunabhängig. Dazu müsste Microsoft nicht nur den .NET-Teil beider Frameworks, sondern auch die Betriebssystemgrundlagentechniken GDI+ bzw. DirectX portieren. Zumindest GDI+ gibt es ja, denn es gibt in Mono auch Windows Forms.

Immerhin können mit WPF und Windows Forms auf .NET Core aber Softwarehersteller, die nicht an Plattformneutralität interessiert sind, ihre bestehenden Windows-Anwendungen auf .NET Core auf Windows (ab Windows 7 bzw. Server 2008 R2 – auf älteren Windows-Versionen läuft .NET Core heute nicht!) betreiben und damit von den Vorteilen profitieren, die .NET Core im Bereich der Werkzeuge, der Geschwindigkeit und der Softwareinstallation bietet. Neben den schon existierenden Deployment-Optionen für .NET Core als Portable App oder Self-contained App will Microsoft einen neuen .NET Core App Bundler schreiben, der per Tree Shaking nicht notwendigen Programmcode (Dead Code) aus der Intermediate Language herauszieht. Am Ende entsteht – wie bei UWP-Apps – nur noch eine ausführbare Datei, die nur den tatsächlich verwendeten Programmcode enthält.

WPF- und Windows-Forms-Anwendungen müssen für .NET Core 3.0 neu kompiliert werden. Ob dazu Änderungen am Programmcode notwendig sein werden, hat Microsoft noch nicht klar dementiert. Aktuell gibt es noch keine öffentliche Previewversion von .NET Core 3.0. Eine fertige Version soll im ersten Quartal 2019 erscheinen. Man kann an diesem Zeitplan durchaus Zweifel haben. Microsoft will bei der Gelegenheit das klassische ADO.NET Entity Framework umschreiben (Abb. 2), sodass es auf .NET Core 3.0 auf Windows läuft, da viele Desktopanwendungen darauf basieren. Angekündigt ist auch eine Erweiterung des .NET-Standards im Rahmen von .NET Core 3.0.

Anfang August hat Microsoft den .NET Portability Analyzer aktualisiert, sodass dieser nun schon die für .NET Core 3.0 geplanten Klassen berücksichtigt. .NET-Entwickler können somit schon sehen, ob ihre WPF- und Windows-Forms-Anwendungen unter .NET Core 3.0 laufen werden. Microsoft kann mit dem Werkzeug sehen, welche Klassen bei Kunden im Einsatz sind und ggf. die Entscheidung für die Klassen in .NET Core 3.0 auf dieser Basis ändern. Anders als zunächst angekündigt, läuft .NET Portability Analyzer aber noch nicht auf einem Prototyp von .NET Core 3.0, sondern auf dem klassischen .NET Framework.

 

UWP-Inseln

Eine weitere Ankündigung in diesem Zusammenhang sind die UWP-XAML-Inseln. Damit können Entwickler UWP-Steuerelemente in WPF- und Windows-Forms-Fenster einbinden. Das setzt dann noch engere Grenzen, denn UWP gibt es nur auf Windows 10. Wer den Luxus hat, nur noch für Windows 10 entwickeln zu können, kann dann Steuerelemente wie das Edge Browser Control oder das UWP-Stifteingabesteuerelement in seine alten GUIs einbinden. Das soll einfach per Drag-and-drop aus der Visual-Studio-Werkzeugleiste gehen. Diese UWP-XAML-Inseln will Microsoft nicht nur in .NET Core 3.0, sondern in der kommenden Version 4.8 des klassischen .NET Framework erlauben.

 

Fazit

Es tut sich eine Menge bei .NET Core und das ist auch gut so. Es ist wichtig, dass Microsoft baldmöglichst allen Entwicklern ermöglicht, auf .NET Core zu setzen, damit man spätestens 2019 keine neuen Projekte mehr im klassischen .NET Framework beginnen muss.

Natürlich gab es nach den Ankündigungen von Windows Compability Pack, Windows Forms und WPF auf .NET Core nur für Windows sofort Skeptiker, die aufschrien, dass Microsoft die Plattformunabhängigkeit von .NET Core beerdige. Nein, das tun sie nicht.

ASP.NET Core und Konsolenanwendungen laufen auch weiterhin plattformunabhängig auf Windows, Linux und macOS. Microsoft dehnt nur die Einsatzszenarien für .NET Core auf die Entwickler aus, die weiterhin nur für Windows entwickeln können oder wollen. Und auch das ist – ich wiederhole mich – gut so!

 

 

The post .NET Core 2.1, 2.2 und 3.0 – neue Funktionen appeared first on BASTA!.

]]>
Interview | Cloud Native – Buzzword oder echte Innovation? https://basta.net/blog/cloud-native-buzzword-oder-echte-innovation/ Wed, 10 Oct 2018 09:52:51 +0000 https://basta.net/?p=27859 Cloud-Computing hat sich von einer Randerscheinung zu einem Fixpunkt in der IT-Strategie vieler Teams entwickelt. Da liegt es nahe, dass man Anwendungen speziell für diese Umgebung optimiert und sie damit "Cloud Native" macht.

The post Interview | Cloud Native – Buzzword oder echte Innovation? appeared first on BASTA!.

]]>

In seiner Keynote spricht Rainer Stropek darüber, was bei Cloud Native anders läuft. Als Ausgangspunkt verwendet er reale Anforderungen aus einem seiner Praxisprojekte. Darauf aufbauend zeigt er, wie eine typische Cloud-Native-Softwarearchitektur aussieht und welche Auswirkungen die Architektur- und Designentscheidungen auf die Projektorganisation und den Anwendungsbetrieb haben.

Rainer Stropek zeigt in seiner Keynote wichtige Tipps und Tricks, wie Entwickler aus der Cloud das Maximum herausholen, die Hürden, die es natürlich auch dort gibt, erfolgreich meistern und so neue Möglichkeiten praktisch nutzen können. Die Cloud bietet für Entwickler viele Services, APIs und ganze Softwarelösungen, die entweder den Arbeitsalltag praktisch unterstützen oder neuartige Produkte überhaupt erst möglich machen. Dazu muss man nicht immer gleich zu 100 Prozent in die Cloud und sie bei der zunehmenden Komplexität auch nicht vollständig verstehen. Es wird immer wichtiger, aus dem wachsenden Angebot genau das herauszufinden, was die eigene Arbeit effektiv unterstützt. Dabei können Zeit-, Kosten- oder Aufwandseinsparungen im Vordergrund stehen; wichtig ist es, den Weg in Cloud grundsätzlich in Betracht zu ziehen – für Sie selbst und Ihre Kunden.

Auch auf der BASTA! Spring 2019 zeigen zahlreiche Sessions, wie Sie als Entwickler die Cloud sinnvoll und praxisorientiert einsetzen können.

Lesen Sie mehr zu diesem Thema in unserem Blogartikel “Die Cloud-Perslektive: Warum Benutzer Software oft ganz anders sehen als wir Entwickler”.

The post Interview | Cloud Native – Buzzword oder echte Innovation? appeared first on BASTA!.

]]>
Die Cloud-Perspektive: Warum Benutzer Software oft ganz anders sehen als wir Entwickler https://basta.net/blog/warum-benutzer-software-oft-ganz-anders-sehen-als-wir-entwickler/ Sun, 30 Sep 2018 16:43:21 +0000 https://basta.net/?p=27845 Viele, die den Weg von On-Premises-Software zu Cloud-basierten SaaS-Lösungen gehen, müssen eine Lektion auf die harte Tour lernen: Benutzer sehen und beurteilen die von uns Technikerinnen und Technikern erstellte Software ganz anders als wir. Naturgemäß stehen für sie jene Aspekte im Mittelpunkt, die sie ständig brauchen.

The post Die Cloud-Perspektive: Warum Benutzer Software oft ganz anders sehen als wir Entwickler appeared first on BASTA!.

]]>

Dass im Hintergrund eine beachtliche Softwareinfrastruktur für Abonnementverwaltung, Rechnungslegung, Supportportal, Kreditkartenabrechnung, Monitoring, Automatisierung von Administrationsprozessen etc. notwendig ist, ist den Endbenutzern ziemlich egal. Strom kommt verlässlich aus der Steckdose und Software stabil aus der Cloud – das ist die Erwartungshaltung, und das sollte ein guter SaaS-Anbieter auch anstreben.

Hinzu kommt, dass viele Softwareprodukte gleichzeitig ganz unterschiedliche Typen von Benutzern adressieren. Ein Administrator im Backoffice, der vor zwei großen Monitoren sitzt und mit Maus und Tastatur arbeitet, hat ganz andere Bedürfnisse als eine Benutzerin, die in entlegenen Gegenden von Kunde zu Kunde fährt, um dort mithilfe mobiler Geräte und der SaaS-Softwarelösung ihren Job zu machen.

Blick auf das Gesamtsystem

Als Technikerinnen und Techniker, die an der Erstellung der SaaS-Lösung arbeiten, vergessen wir manchmal die Sichtweise unserer Endbenutzer. Wir haben ständig das Gesamtsystem im Kopf. Im Mittelpunkt unserer Aufmerksamkeit stehen beispielsweise Dinge wie die folgenden:

  • Einheitlichkeit: Wenn wir viele Problemstellungen auf einheitliche Weise lösen, können wir Softwareartefakte leichter wiederverwenden. Das wirkt sich positiv auf die Entwicklungs-produktivität aus.
  • Keine Ausnahmen: Kunden- oder benutzerspezifische Speziallösungen zerstören unsere eleganten, klaren Softwaredesigns.
  • Keine technischen Schulden: Wir wollen immer mit der neuesten Technologie arbeiten und die modernsten Softwaredesigns anwenden. Schließlich finden dort die echten Innovationen statt.
  • Integrierte Gesamtlösungen: In Schule und Universität wurde uns die Normalisierung von Datenbanken eingebläut. Am liebsten hätten wir eine einzelne, integrierte, transaktionssichere Datenbank für alles.
  • Konfigurierbarkeit: Wenn wir eins gelernt haben, dann dass sich Technologie und Anforderungen laufend verändern. Daher erstellen wir gerne konfigurierbare Software, in der sich der Benutzer selbst aussuchen kann, was er will.
  • Zentralisierung: Was wir auf unseren Servern in der Cloud haben, haben wir im Griff. Offlinefähigkeit, Synchronisation von Daten, Installation und Update von Software auf Clients machen eine Menge Arbeit und Stress.

 

Cloud Developer Day auf der BASTA! 2018

 

Die Benutzersicht

Benutzer nähern sich einer SaaS-Lösung anders: Für sie zählt, ob die Software ihre konkreten Probleme löst. Hier einige Beispiele:

  • Schön, dass die Software ein echter Alleskönner ist, aber ist sie auch wirklich gut bei den Dingen, die ich täglich brauche? Der Rest interessiert mich eigentlich nicht.
  • Beruhigend zu wissen, dass man die Software konfigurieren könnte, aber ist sie für mich von Haus aus hilfreich? Ich habe keine Lust auf lange und teure Anpassungsprojekte.
  • Beeindruckend, dass Kunden auf der ganzen Welt die Software nutzen, aber erhalte ich eine aussagekräftige Monatsrechnung, die für mein Land rechtlich einwandfrei ist?
  • Toll, dass es Apps für praktisch jede Smartphoneplattform gibt, aber lässt sie sich auf meinem Büroarbeitsplatz auf meinem 30 Zoll großen 4k-Monitor effizient bedienen?
  • Gut zu wissen, dass die Software gerade auf die neueste Angular-Version umgestellt worden ist, aber welche neuen Features hat die neue Softwareversion, die für mich in der täglichen Arbeit hilfreich sind?
  • Gut, dass die SaaS-Lösung sich um eine stabile Internetanbindung der Server, Back-ups und Ähnliches kümmert, aber wie hilft mir die Software, wenn ich während der Arbeit mit dem System selbst keine stabile und schnelle Internetverbindung habe?

 

Die Spitze des Eisbergs

Als SaaS-Anbieter sind wir stolz, wenn wir eine Plattform geschaffen haben, die technisch und vom Geschäftsmodell her richtig gut skaliert. Kunden wertschätzen unsere Investitionen aber nur bedingt. Sie sehen nur die Spitze des Eisbergs: die für sie in der täglichen Arbeit relevanten Funktionen. Sie vergleichen die SaaS-Lösung mit einem System, das ihr eigenes IT-Team für sie aufbauen könnte. Ist der Kunde mittelgroß und hat ein engagiertes, schlagkräftiges IT-Team, ist es als SaaS-Anbieter nicht immer einfach, mitzuhalten. Die lokale IT kann locker auf individuelle Anforderungen eingehen, sie braucht keine komplizierte Abonnementverwaltung, sie muss sich nicht mit Verträgen zur Auftragsdatenverarbeitung herumschlagen und so weiter.

Jeder SaaS-Anbieter möchte natürlich möglichst viele Kunden haben. Zehn- oder hunderttausende Abonnements zu verkaufen, klingt verlockend, hat aber massive Auswirkungen, was die initiale Investition in Softwareentwicklung und den organisatorischen Aufbau betrifft.

  • Bei ein paar Dutzend Kunden kann man die eine oder andere administrative Aufgabe (z. B. Cloud-Administration, Rechnungslegung, Mahnwesen etc.) noch manuell erledigen. Will man wirklich groß werden, muss alles automatisiert sein. Ansonsten fressen die laufenden Kosten die monatliche Marge auf.
  • Im kleinen Rahmen kennt das Supportteam die Kunden und umgekehrt. Man versteht die Situation des jeweils anderen und hat eine gemeinsame Vergangenheit. Wenn die Kundenbasis größer wird, wird alles unpersönlicher. Es braucht Management und professionelle, unterstützende Softwaresysteme beim SaaS-Anbieter.
  • Als kleiner SaaS-Anbieter kann man relativ leicht auf individuelle Bedürfnisse einzelner Kunden eingehen. Da eine etwas größere Datenbank, dort eine quartalsweise Abrechnung statt der sonst üblichen monatlichen – solche Extrawürste lassen sich im kleinen Rahmen ohne Weiteres machen. Will man diese Flexibilität auch im Großen bieten, muss auf Anbieterseite eine Menge in die SaaS-Plattform investiert werden.

 

Konsequente Orientierung an Personas

Meiner Ansicht nach ist es wichtig, dass es bei jedem SaaS-Anbieter Personen gibt, die sich laufend in die Rolle der Endbenutzer versetzen. Dabei muss beachtet werden, dass nicht alle Benutzer über einen Kamm geschoren werden dürfen. Die besten SaaS-Teams haben detaillierte Personas ausgearbeitet. Eine Persona stellt einen Prototyp für eine Gruppe von Nutzern dar, mit konkret ausgeprägten Eigenschaften und einem konkreten Nutzungsverhalten. Sie verwenden die Personas, um bei der Produktplanung sicherzustellen, dass laufend Verbesserungen für alle Benutzergruppen spürbar sind.

Manchmal kommt es bei konsequenter Orientierung an Personas zu anderen Lösungen, als sie Technikerinnen und Techniker sonst gestalten würden. Das Technikteam hat, wie oben erwähnt, immer die Gesamtlösung im Kopf und würde vielleicht dazu tendieren, ein einheitliches UI für alle Benutzergruppen zu erstellen, in dem man über Benutzerberechtigungen den Zugriff auf die jeweils relevanten Module steuern kann. Aus Benutzersicht ist diese Integration möglicherweise unnötig oder sogar kontraproduktiv. Während der eine Benutzer für seine Szenarien ein leichtgewichtiges, für das Smartphone optimiertes UI will, möchte ein anderer ein Desktop-UI, das die Leistungsfähigkeit seines lokalen Computers optimal ausschöpft. Für beide Personas sind die Funktionen der jeweils anderen Benutzergruppe größtenteils irrelevant. Der Technologiebruch ist ihnen egal. Sie haben ihre ganz individuelle Sichtweise auf die SaaS-Lösung. Nebenbei gesagt: Diese Erkenntnis passt übrigens wunderbar zum Architekturkonzept der Microservices.

 

Produktplanung in Balance

Die besten Produktplanungsteams sorgen außerdem bei Weiterentwicklungen für eine ausgewogene Balance aus Investition in die SaaS-Infrastruktur im Hintergrund (nur indirekt sichtbar für den einzelnen Kunden) und neue Funktionen mit konkreten Auswirkungen auf Endbenutzer.

Es ist gut, wenn die Personen, die sich bei SaaS-Anbietern um die Produktplanung kümmern, nicht zu tiefes IT-Know-how haben. Ein Grundverständnis für IT, SaaS und Cloud-Computing ist unverzichtbar, da ansonsten die technische Weiterentwicklung unterrepräsentiert ist und man binnen weniger Jahre auf einem veralteten Berg Software sitzt. Aus eigener, schmerzhafter Erfahrung weiß ich aber, dass zu techniklastiges Produktmanagement Gefahr läuft, sich in technischen Spielereien zu verlieren. Funktionale Anforderungen und organisatorische Rahmenbedingungen werden vernachlässigt. Am Ende hat man eine SaaS-Plattform, die zwar Unmengen an Kunden verkraften würde, aber das Marktpotenzial vertrieblich nicht ausschöpfen kann.

 

Cloud, Azure, Serverless Track auf der BASTA! 2018

 

Handwerkskunst vs. Massenware

Einen SaaS-Anbieter, der auf eine große Kundenbasis abzielt, kann man mit einem Hersteller von Massenware vergleichen. Vermarktet wird ein relativ stark standardisiertes Produkt, das dank Economy-of-Scale zu einem attraktiven Preis angeboten werden kann. Diese Tatsache sollte man nicht verschweigen, ganz im Gegenteil. Kunden, die sich für ein SaaS-Standardprodukt entscheiden, wissen aus anderen Lebensbereichen, worauf sie sich einlassen. Ihnen ist klar, dass ein BILLY-Regal von Ikea etwas anderes ist als ein individuell vom Tischler hergestelltes Bücherregal.

Erfolgreich sind SaaS-Angebote dann, wenn sie konsequent an den Bedürfnissen der Endbenutzer ausgerichtet werden und sich die Herstellerteams nicht ganz und gar in der Optimierung der Softwareinfrastruktur verlieren, die im Hintergrund arbeitet. Wenn das gelingt, erhalten die Kunden durch SaaS eine Softwarelösung, die in Sachen Preis-Leistungsverhältnis von keiner On-Premises-Software geschlagen werden kann.

 

Wie Entwickler die Cloud als neuen Entwicklungsraum erobern können präsentiert Rainer Stropek in der Eröffnungskeynote „Cloud Native – Buzzword oder echte Innovation?“. Und noch mehr Informationen finden Sie im neuen Cloud Developer Day am Mittwoch.


The post Die Cloud-Perspektive: Warum Benutzer Software oft ganz anders sehen als wir Entwickler appeared first on BASTA!.

]]>
DevOps und Microsoft – ein neuer Wind https://basta.net/blog/devops-und-microsoft/ Tue, 14 Aug 2018 15:20:30 +0000 https://basta.net/?p=27675 Vergleicht man das Microsoft von heute mit dem Microsoft von vor ein paar Jahren, mag man manchmal kaum glauben, dass es sich um die gleiche Firma handelt: Eine ganz neue Offenheit im Hinblick auf Technologien und Prozesse ist an die Stelle von Zurückgezogenheit und Abschottung getreten. Neno Loje stellt in seiner Keynote den Wandel in der Arbeitsweise und der Kultur bei Microsoft vor und gibt Tipps, wie Entwickler davon profitieren können.

The post DevOps und Microsoft – ein neuer Wind appeared first on BASTA!.

]]>

Bei Microsoft hat sich in den letzten Jahren auf Unternehmensebene ein ganz neues Selbstverständnis entwickelt und auch das Mindset der Mitarbeiter hat sich verändert. Heute arbeiten viele kleine interdisziplinäre Teams zusammen. In der Konsequenz führt das auch zu Produkten, die die neue Vision des Unternehmens widerspiegeln.

Neno Loje beschreibt am Beispiel von Microsoft die Reise eines Unternehmens zur Agilität, die Auseinandersetzung mit Themen wie Open Source, Git und DevOps, und wie diese konkret/praktisch Einzug in die Visual-Studio-Produktpalette und den Alltag von Entwicklern halten. Nicht zuletzt ist auch die geplante Übernahme von GitHub durch Microsoft ein deutliches Zeichen dafür, wohin der Weg für Entwickler im Hinblick auf bessere Zusammenarbeit und Open-Source-Entwicklung geht.

DevOps ist eine Kultur

Als Entwickler fragt man sich vielleicht: „Wie kommt das, was hier unter meinen Fingern entsteht, am schnellsten und besten zu meinen Kollegen und zu meinen Kunden?“ Die Antworten darauf haben sich in den letzten Jahren immer wieder geändert, aber aktuell ist ein Schlagwort dazu DevOps. Dahinter steckt zunächst die Idee, dass Developer und Operations eng zusammenarbeiten, um Produkte möglichst reibungslos auf den Markt zu bringen. Das Ziel ist wie immer, Produkte in der Qualität und in einer Geschwindigkeit zu erstellen, die am Ende alle zufriedenstellt. Wie immer müssen dabei viele Elemente unter einen Hut gebracht werden: Kompetenz, Tools, Methoden, Ziele und Menschen. Vor allem Letztere, denn am Ende sind es eben Menschen, die die Produkte herstellen und nutzen. Auf der Basis von Agilität mit ihren vielen Richtungen wie XP, Scrum oder Kanban lassen sich da schon viele Hürden nehmen; diesen Weg ist auch Microsoft gegangen. Aber wer wie Microsoft mehr will, spricht dann eben nicht mehr von ALM und Agilität, sondern geht einen Schritt weiter und macht DevOps. In der einfachen Variante heißt das, Developer und Operations werfen sich nicht wechselseitig Fehler vor, sondern setzen sich zusammen und versuchen in einem iterativen Verbesserungsprozess, ihre Software und ihre Arbeitsweise kontinuierlich weiterzuentwickeln. In einer erweiterten Variante sind auch der Vertrieb, die Geschäftsleitung, die Partner und die Kunden an diesem Kommunikationsprozess beteiligt. Womit aus einem Prozess, der zunächst nur die IT betrifft, ein Prozess wird, der Grenzen überschreitet und zu einem Kulturwandel für das Unternehmen und den Markt führt. Im Fall von Microsoft z. B. in der Form, dass Sie als Entwickler mit den Tools von Microsoft und in der Art und Weise, wie diese für Sie bereitgestellt werden, bzw. welche Möglichkeiten Sie Ihnen bieten, neue Wege finden, Ihre Ziele zu erreichen.

 

DevOps – kleine Änderung, große Wirkung

Dabei ist DevOps vielleicht nur ein kleiner, konzeptioneller Aspekt, aber die Neuerungen in Visual Studio, TFS oder Azure haben für Sie immense praktische Auswirkungen. Wie das im Einzelnen aussieht, zeigen zahlreiche Sessions der BASTA! wie auch diese vier Special Days, von denen jeder einzelne die Facetten von DevOps aus seiner Perspektive betrachtet und die Wichtigkeit des Themas unterstreicht: TFS & DEVOPS DAY, CLOUD DEVELOPER DAY, AGILE TESTING DAY, AGILE DAY.

Wenn Sie alles in einem Paket haben wollen, besuchen Sie am besten den Power Workshop von Neno Loje und Dr. Holger Schwichtenberg: DevOps live Workshop – Continuous Deployment und Continuous Delivery für .NET-Core- und Angular-Anwendungen.

 

The post DevOps und Microsoft – ein neuer Wind appeared first on BASTA!.

]]>
Docker-Grundlagen für .NET-Entwickler https://basta.net/blog/docker-grundlagen-fuer-dotnet-entwickler/ Tue, 31 Jul 2018 12:39:11 +0000 https://basta.net/?p=26754 Container und Docker sind in aller Munde. Doch die Einstiegshürde in die Docker-Welt ist nicht ganz niedrig. Aber auch für .NET-Entwickler lohnt es sich, zu fragen, was genau Docker eigentlich ist, wie es funktioniert, welche Probleme es löst und wie man es heute schon einsetzen kann.

The post Docker-Grundlagen für .NET-Entwickler appeared first on BASTA!.

]]>

In den vergangenen Jahren ist die Beliebtheit von Docker stetig gestiegen. Applikationen werden in einem Container ausgeführt und können somit auf beliebigen Umgebungen „angedockt“ werden. Das klingt ja ganz spannend. Aber warum sollte das einen .NET Entwickler interessieren, ist das denn überhaupt wichtig für den Entwicklungsalltag? Wieso ist Docker auch für uns eine solche Revolution?

 

Warum Docker?

Bei Docker geht es primär um das Verteilen von Anwendungen und Diensten, das sogenannte Deployment. Doch wie wurde das eigentlich früher gemacht? Nehmen wir an, ein Entwickler will seine frisch erstellte .NET-Webapplikation einer Kollegin zum lokalen Testen geben. Ganz früher war es noch so, dass die Kollegin zur Installation eine Anleitung mit Voraussetzungen und ggfs. auch manuelle Skripte bekam. Da stand dann z. B. in der Anleitung, dass Windows als Betriebssystem benötigt wird, dass das .NET Framework in einer bestimmten Version installiert sein muss, dass zur Ausführung eine bestimmte Datenbank benötigt wird usw. Es wird also für die Kollegin ein mühsames und aufwändiges Unterfangen, die Webapplikation lokal zum Laufen zu bekommen.

Im Laufe der Jahre hat sich das Ganze vereinfacht, indem man mit virtuellen Maschinen gearbeitet hat. Ja, viele Entwickler machen das natürlich auch heute noch. In der virtuellen Maschine wird die .NET-Webapplikation mit allen Abhängigkeiten installiert. Jetzt kann der Entwickler seiner Kollegin einfach die virtuelle Maschine geben, die Kollegin startet sie und hat somit eine lauffähige Applikation, die sie innerhalb der VM testen kann – ein deutlicher Fortschritt. Doch auch hier gibt es noch einen kleinen Haken: Die eigentliche Anwendung ist in der Praxis meist nur ein paar Megabytes groß, wenn überhaupt. Zur Größe der Anwendung kommen noch ein paar Abhängigkeiten, wie ein Webserver und eine Datenbank. Doch die virtuelle Maschine selbst beinhaltet ja noch das Gastbetriebssystem, und das nimmt üblicherweise gleich mal einige Gigabytes in Anspruch. Dass man der Kollegin die beispielsweise 100 GB große virtuelle Maschine für eine Anwendung geben muss, die mit ihren Abhängigkeiten nur einen Bruchteil dieser Größe hat, ist natürlich nicht ideal. Geht man sogar noch davon aus, dass man verschiedene Applikationen mit unterschiedlichen Abhängigkeiten hat, dann braucht es vielleicht sogar mehrere virtuelle Maschinen, um für jede Applikation eine zu haben. Wer kennt es nicht, dass die Festplattengröße des Entwicklungsrechners da oft ein Problem darstellt. Neben dem Datenvolumen einer VM ist auch das Startverhalten ein Kritikpunkt. Es dauert meist eine kleine Weile, bis das Betriebssystem hochgefahren ist. Doch warum brauche ich überhaupt ein zusätzliches Betriebssystem, wenn die Kollegin auf ihrem Rechner doch schon eins hat? Das ist eine sehr gute Frage, und das ist der Punkt, an dem Docker ins Spiel kommt.

 

Der Cloud, Azure, Serverless Track auf der BASTA! 2018

 

Über Images und Container

Mit Docker lassen sich alle Abhängigkeiten einer Anwendung in einem sogenannten Docker Image abbilden. Aus einem Image lässt sich dann eine Instanz erzeugen, die als Container bezeichnet wird. Um eine Applikation mit allen Abhängigkeiten an eine Kollegin zu geben, wird ein Docker Image bereitgestellt. Die Kollegin kann das Docker Image dann in Form eines Containers lokal auf ihrem Rechner ausführen. Doch was ist jetzt genau der Unterschied zwischen einem laufenden Docker-Container und einer laufenden virtuellen Maschine? Der große Unterschied ist, dass der Docker-Container im Gegensatz zur virtuellen Maschine kein eigenes Betriebssystem hat. Anstelle eines Gastsystems wie bei einer virtuellen Maschine, wird bei einem Docker-Container direkt das Betriebssystem des Hosts genutzt, das sogenannte Host-OS. Abbildung 1 verdeutlicht den Unterschied zwischen einer virtuellen Maschine und einem Docker-Container.

 

Abb. 1: Virtuelle Maschine vs. Docker Container 

 

Wie Abbildung 1 zeigt, benötigt eine virtuelle Maschine den sogenannten Hypervisor, der die Ressourcen des Hosts bereitstellt. Auch ein Docker-Container benötigt etwas ähnliches, die sogenannte Docker Engine. Die stellt den Zugriff auf den Kernel des Host-Betriebssystems sicher und ist in der Lage, Container zu erstellen, zu starten und zu stoppen. Aufgrund der Tatsache, dass ein Docker Image und somit auch ein daraus erstellter Docker-Container kein eigenes Betriebssystem mit sich bringt, sondern auf dem Host-Betriebssystem aufgesetzt wird, ist ein Docker Image sehr viel kleiner als eine virtuelle Maschine. Neben der Größe ist das schnelle Starten eines Docker-Containers ein weiterer großer Vorteil gegenüber einer virtuellen Maschine. Beim Starten eines Containers muss nicht ein ganzes Betriebssystem hochgefahren werden. Somit geht das Starten und Stoppen eines Containers ungewohnt schnell, es gleicht dem Starten und Stoppen eines Prozesses.

Um also Docker-Container ausführen zu können, wird die Docker Engine benötigt. Auf jedem Rechner, auf dem eine Docker Engine installiert ist, lassen sich Docker-Container ausführen. Es ist also an der Zeit, die Docker Engine auch auf dem eigenen Entwicklungsrechner zu installieren.

Mit Docker loslegen

Nach der Installation ist Docker auf der Kommandozeile verfügbar und es kann losgehen. Wie in Abbildung 2 zu sehen ist, läuft die Docker Engine auf Linux, das als Operating System (OS) angegeben ist.

 

Abb. 2: Die installierte Docker-Version

 

Beim Installieren von Docker stand die Option USE WINDOWS CONTAINERS INSTEAD OF LINUX CONTAINERS zur Verfügung. Docker wurde ursprünglich auf Linux aufbauend eingeführt, Windows-Container kamen erst später dazu. Da mit der Defaultinstallation Linux-Container verwendet werden, bedeutet das, dass Images und daraus instanziierte Container Linux-basiert sind und somit den Linux-Kernel des Hostbetriebssystems verwenden. Doch wo ist dieser Linux-Kernel unter Windows? Im Hintergrund hat die Docker-Installation eine virtuelle Maschine mit Linux installiert, damit sich auf einen Windows-Betriebssystem auch Linux-basierte Container starten lassen. Wird unter Windows der Hyper-V-Manager geöffnet, ist die von Docker installierte und genutzte Linux VM zu sehen, sie trägt den Namen MobyLinuxVM.

Im Icon Tray von Windows – das ist der Bereich rechts unten in der Taskbar links von der Uhr – ist nach einer erfolgreichen Installation und einem erfolgreichen Start der Docker Engine ein Docker-Icon zu sehen. Es repräsentiert die installierte Docker Engine und hat die Form des klassischen Walfischs mit Containern an Bord. Ein Rechtsklick auf dieses Icon zeigt ein Kontextmenü, das unter anderem einen Eintrag enthält, um von Linux-Containern auf Windows-Container umzusteigen.

Doch Linux-Container sind die übliche Variante. Ein Linux-Container kann zwar kein .NET Framework beinalten, da das nur unter Windows läuft. Aber .NET Core ist ein ideales Mittel, um .NET-Applikationen auch in einem Linux-Container auszuführen, da .NET Core ja eine modulare Cross-Platform-Implementierung von .NET ist, die unter Windows, Mac OS und Linux läuft. Jetzt dürfte auch klar sein, wieso die Cross-Platform-Fähigkeit von .NET Core so wichtig ist. Sie macht .NET Core zu einem wichtigen Framework für containerbasierte Applikationen, unabhängig davon, ob es sich um Linux- oder Windows-basierte Container handelt.

Docker in Visual Studio

Visual Studio enthält eine integrierte Unterstützung für Docker. Wird eine neue ASP.NET-Core-Webapplikation erstellt, gibt es dort im Dialog eine Checkbox, um die Docker-Unterstützung zu aktivieren. Wurde diese Auswahl beim Erstellen des Projekts nicht getroffen, lässt sie sich auch im Nachhinein noch aktivieren. Dazu genügt ein Rechtsklick auf das Projekt im Solution Explorer. Über das Kontextmenü und den Menüpunkt ADD | DOCKER SUPPORT wird die Docker-Unterstützung hinzugefügt (Abb. 3). Unmittelbar nach einem Klick auf diesen Menüpunkt erscheint ein Dialog, der eine Auswahl zwischen Windows und Linux erlaubt. Darüber wird definiert, was für eine Art von Container erstellt wird.

 

Abb. 3: Docker Support in Visual Studio

 

Fazit

Neben dem Deployment für produktive Applikationen ist Docker ideal, um Entwicklungs- und Testumgebungen aufzusetzen. Auf dem Docker Hub gibt es Datenbanken und andere Services, die das Entwicklerherz höherschlagen lassen. Haben Entwickler erst einmal mit Docker angefangen, sind vermutlich die Tage gezählt, an denen sie SQL Server direkt auf ihrem Entwicklungsrechner installiert haben. Stattdessen läuft das Ganze in einem Container. Unterschiedliche Versionen lassen sich somit problemlos parallel installieren, starten, stoppen und auch wieder entfernen.

Die Mächtigkeit von Docker macht virtuelle Maschinen weitestgehend überflüssig und bringt uns im 21. Jahrhundert endlich an den Punkt, an dem wir tatsächlich von echten Softwarekomponenten sprechen können. Es ist an der Zeit, sich mit dem Thema Docker auseinanderzusetzen, denn in Zukunft wird vermutlich kein Entwickler daran vorbeikommen.

Die vollständige Einführung in Docker für .NET-Entwickler von Thomas Claudius Huber bietet die aktuelle Ausgabe des Windows Developers „Docker für .NET-Entwickler“. Noch mehr Wissenswertes zum Thema finden Sie in den Artikeln zu Windows- oder Linux-basierten Containern von Dr. Holger Schwichtenberg und Serverless Containern in Azure von Rainer Stropek.

Noch weiter vertiefen können Sie Ihr Docker-Wissen in den praxisorientierten Sessions von Marc Müller und Thorsten Hans auf der BASTA!. Bis dahin behalten Sie mit dem Docker-Spickzettel von Dr. Holger Schwichtenberg alles Wichtige zu Docker im Blick.

 

The post Docker-Grundlagen für .NET-Entwickler appeared first on BASTA!.

]]>
Top 20 Social Influencers in .NET Development 2018: Wer die Community prägt https://basta.net/blog/top-20-social-influencers-in-net-development-2018/ Mon, 30 Jul 2018 12:41:06 +0000 https://basta.net/?p=26705 Who are the most influential .NET people in the Twittersphere? After analyzing thousands of accounts, we created a list of people that every .NET or Microsoft enthusiast should be following.

The post Top 20 Social Influencers in .NET Development 2018: Wer die Community prägt appeared first on BASTA!.

]]>
BASTA! 2018 – TOP 20 Influencers

The .NET, Windows & Open Innovation Conference BASTA! proudly presents the top 20 social influencers regarding the Microsoft and .NET World. All influential people have something in common: they can spread ideas faster and better than anyone else. We are aware that following those people has a handful of perks, including staying on top of the latest news and trends. Therefore, we decided to compile a list of Twitter accounts all Microsoft and .NET fans should follow.

This list was created by analysing thousands of twitter profiles extracting the MozRank and Kred scores which rank both quality and influence. 

If we missed anybody, it has nothing to do with the fact that we don’t think that they are just as important or shareworthy as any person on this list. We ourselves though do believe that such analytical lists do have the right to exist and furthermore we at BASTA! follow the listed speaker on their social channels and enjoy their quality content.

We congratulate everyone who made it on this list. If you click on the name or on the picture of each speaker you will be redirected to their twitter profile and check out the useful information they share every day.

Enjoy!

METHODOLOGY

We first generated a list of two thousand .NET-related Twitter accounts.
To score the account and rank them accordingly, we analyzed their social authority and reach using two key metrics: MozRank and Kred.

Moz Social Authority Score: Social Authority score is composed of:

  1. The retweet rate of users’ last few hundred tweets.
  2. The recency of those tweets.
  3. A retweet-based model trained on user profile data.

Visit this MOZ blog post for more in-depth information.

Kred Score: Kred Influence Measurement, or Kred, is a website created by PeopleBrowsr that attempts to measure online social influence. Read more in the Kred scoring guide.

Congratulations to all the influencers who have made it into our Top 20!


To visit the associated Twitter profiles, just click on the respective portraits. We hope you enjoy exploring new perspectives and find some inspiration.

Scott Hanselman @shanselman

Tech, Code, YouTube, Race, Linguistics, Web, Parenting, Black Hair, STEM, Inclusion, @OSCON chair, Phony. MSFT, but these are my opinions. @overheardathome

Kred score 98,1
Moz score 87
Total score 185,1

Addy Osmani @addyosmani

Eng. Manager at Google working on @GoogleChrome. TodoMVC, @Yeoman, Material Design Lite. Make the web fast

Kred score 96,2
Moz score 87
Total score 183,2

Jeff Atwood @codinghorror

Co-founder of http://stackoverflow.com and http://discourse.org. Abyss domain expert. Disclaimer: I have no idea what I’m talking about.

Kred score 98,1
Moz score 85
Total score 183,1

Troy Hunt @troyhunt

Pluralsight author. Microsoft Regional Director and MVP for Developer Security. Online security, technology and “The Cloud”. Creator of @haveibeenpwned.

Kred score 95,9
Moz score 84
Total score 179,9

Tom Warren @tomwarren

Senior Editor at The Verge. Primarily focused on Microsoft news and reviews. Tips: Telegram at tomwarren, or Twitter DM.

Kred score 98,5
Moz score 78
Total score 176,5

Miguel de Icaza @migueldeicaza

Started Xamarin, Mono, Gnome with great friends. Working at Microsoft on beautiful .NET Mobile tools.

Kred score 93,5
Moz score 80
Total score 173,5

Mary Jo Foley @maryjofoley

I am All About Microsoft on ZDNet (http://blogs.zdnet.com/microsoft ). Oh, and I am also sometimes (more often than not) all about craft beer.

Kred score 98,1
Moz score 75
Total score 173,1

Scott Guthrie @scottgu

Vice President of Microsoft Cloud and Enterprise Group.

Kred score 91,9
Moz score 79
Total score 170,9

Paul Thurrott @thurrott

Paul Thurrott is an award-winning technology journalist and blogger and the author of over 25 books. He is the majordomo at http://Thurrott.com .

Kred score 98,3
Moz score 72
Total score 170,3

Daniel Rubino @Daniel_Rubino

Editor-in-chief of Windows Central. Full-time curmudgeon, science geek and play-acting anarchist. HoloLens user. [email protected]

Kred score 94,4
Moz score 75
Total score 169,4

Robert Cecil Martin @unclebobmartin

Software Craftsman, Consultant/Speaker

Kred score 94,3
Moz score 74
Total score 168,3

Ed Bott @edbott

Tech journalist at ZDNet. Now with added blockchain. “With all due respect, I don’t get confused.”

Kred score 96,3
Moz score 72
Total score 168,3

Chris Heilmann @codepo8

Londoner, German, European. Developer Evangelist – all things open web, writing and helping. Works at Microsoft on Edge, opinions totally my own. #nofilter

Kred score 96,9
Moz score 71
Total score 167,9

Kelly Sommers @kellabyte

4x Windows Azure MVP & Former 2x DataStax MVP for Apache Cassandra, Backend brat, big data, distributed diva. Relentless learner. I void warrantie

Kred score 93,6
Moz score 73
Total score 166,6

Mark Russinovich @markrussinovich

CTO of Microsoft Azure, author of novels Rogue Code, Zero Day and Trojan Horse, Windows Internals, Sysinternals utilities.

Kred score 89,3
Moz score 75
Total score 164,3

Brad Sams @bdsams

Executive Editor of http://Petri.com & http://Thurrott.com. Writer, Speaker, Podcaster, Human.

Kredt score 89,7
Moz score 72
Total score 161,7

Ben Rudolph @BenThePCGuy

Managing Director, @Microsoft Modern Journalism. Co-founder, @pollwithstraw. Jiu-jitsu black belt. Krav Maga instructor. Aspiring photographer.

Kred score 96,6
Moz score 64
Total score 160,6

Peter Bright @DrPizza

Ars Technica writer. I do not speak for my employer. Retweets are endorsements unless ironic. Immigrant. Londoner, New Yorker, European. Kinda queer. He/Him.

Kred score 95,6
Moz score 65
Total score 160,6

Joe Belfiore @joebelfiore

Tweeting Microsoft Windows 10! My team creates the Windows experience & Microsoft Edge

Kred score 93,5
Moz score 67
Total score 160,5

Sarah Drasner @sarah_edo

Award-winning speaker. Sr. Developer @Microsoft. @vuejs Core Team, Writer @Real_CSS_Tricks, co-organizer@ConcatenateConf, work: http://codepen.io/sdras/

Kred score 79
Moz score 81
Total score 160

The post Top 20 Social Influencers in .NET Development 2018: Wer die Community prägt appeared first on BASTA!.

]]>
Moderne Anwendungen vom Backend bis zum Frontend https://basta.net/blog/moderne-anwendungen-vom-backend-bis-zum-frontend/ Fri, 13 Jul 2018 10:00:25 +0000 https://basta.net/?p=26670 Der Modern Business Application Day bietet auf der BASTA! seit 2012 einen Überblick durch die Entwicklung von Geschäftsanwendungen. Dabei zeigen die Sessions im Laufe des Tages den Weg vom Backend zum Frontend. Christian Weyer (Thinktecture) erzählt im Interview mit Mirko Schrempp, BASTA! Program Chair, warum die Idee des Thementages über die Jahre gleichgeblieben ist und wie sich die Technologien verändert haben.

The post Moderne Anwendungen vom Backend bis zum Frontend appeared first on BASTA!.

]]>

 

Die Erwartungen an typische Businessanwendungen verändern sich schon seit Jahren. Der einfache Windows-Client, entwickelt mit WPF oder vielleicht schon UWP, reicht in vielen Fällen nicht mehr aus. Zum einen müssen Anwendungen oftmals Mobile sein, zum anderen müssen sie auf anderen Plattformen laufen.

Aus Sicht klassischer .NET-Entwickler ist das klar eine Herausforderung, aber kein wirkliches Problem. Es gibt heute viele Wege, wie .NET-Entwickler in ihrer gewohnten Umgebung und mit ihrer Kompetenz mit dem Microsoft-Tooling- und -Sprachenkit von Native bis Cross-Plattform entwickeln können.

Auch für Businessanwendungen gibt es kaum Grenzen

So ist es möglich, auch geschäftlichen Anwendern den Komfort eines gesicherten Zugriffs auf Firmendaten von Desktops, Laptops, iOS- und Android-Geräten und aus dem Browser heraus zu bieten. Die Daten können sicher und hochverfügbar über Services bereitgestellt werden – lokal oder über die Cloud. Und gerade in diesem Bereich hat sich in den Köpfen der Entwickler wie auch beim Angebot von Microsoft viel getan.

Ein Punkt, den Christian Weyer im Interview besonders hervorhebt, sind die Vorteile von Progressive Web Apps (PWA). Sie sind gespickt mit nativen Features, egal ob für Mobile oder Desktop. Man kann sie als den letzten konsequenten Schritt ansehen, der noch gefehlt hat, um vom Backend mit .NET bis zum Frontend im Browser Cross-Plattform endlich zu verwirklichen. Andere neue Entwicklungen wie WebAssembly und Blazor zur Ausführung von .NET-Anwendungen im Browser verstärken den Trend in eine Zukunft, die vielleicht sogar Serverless und „Appstoreless“ sein wird.

Auf der BASTA! können Sie einen Blick in diese Zukunft werfen und sie ein Stück weit schon erfahren. Nicht nur im Modern Business Applications Day, sondern auch im Cross-Plattform Applications Day, dem Web Dev Day und zahlreichen weiteren Sessions.

 

The post Moderne Anwendungen vom Backend bis zum Frontend appeared first on BASTA!.

]]>
Ich weiß nicht, ob Sie es wussten: Aber Linux, das braucht jetzt jeder https://basta.net/blog/ich-weiss-nicht-ob-sie-es-wussten-aber-linux-das-braucht-jetzt-jeder/ Wed, 13 Jun 2018 12:07:10 +0000 https://basta.net/?p=23902 Das Interesse an Linux ist derzeit groß. Verständlich ist das, denn das Betriebssystem hat sich in beinahe jedem Bereich, in dem Computer eingesetzt werden, verbreitet. Auch für .NET-Entwickler ist es ratsam, sich ein wenig mit Linux auszukennen. Für viele ist der Schritt allerdings schwierig, da alles ganz anders ist als unter Windows.

The post Ich weiß nicht, ob Sie es wussten: Aber Linux, das braucht jetzt jeder appeared first on BASTA!.

]]>

Im Februar 2018 habe ich einige Recherchen angestellt, und daraus ergab sich, dass Linux 96 Prozent der populärsten Million Domains im Internet betreibt. Seit November 2017 laufen alle 500 schnellsten Supercomputer der Welt mit Linux, und aufgrund der Verbreitung von Android ist die Verbreitung bei Mobilgeräten mit etwa 70 Prozent angenommen. Das System unterstützt mehr als 30 Prozessorplattformen, und ein Kernelrelease kommt im Schnitt mit etwa 10 000 Patches daher, die von mehr als eintausend Personen zur Verfügung gestellt werden.

Sehr beeindruckende Zahlen! Aber nun zurück zu dem, was man über Linux wirklich wissen sollte. Zunächst ist da der Aufbau des Systems, mal ganz grob. Linux selbst ist eigentlich nur der Kernel (Kern), in dem z. B. Dateisystemstreiber und ähnliche Komponenten auf unterster Ebene enthalten sind. Na gut, eine Zahl noch: Der Kernel hat derzeit mehr als 20 Millionen Zeilen Code!

.NET-Famework & C# Track auf der BASTA! 2018

Verteilte Entwicklung

Zu einem typischen, auf Linux basierenden System braucht man natürlich noch vieles mehr, und das kommt alles aus dritter Hand, also aus anderen Open-Source-Projekten. Spezifische Treiber für Hardware, System- und Anwendungssoftware, Shells für den Umgang mit der Kommandozeile, grafische Oberflächen, Paketverwaltung und vieles mehr ist getrennt vom Kernel zu sehen. Trotzdem ist meistens, wenn der Begriff Linux verwendet wird, ein Gesamtsystem aus all diesen Elementen gemeint. Tatsächlich handelt es sich bei einem solchen Gesamtsystem gewöhnlich um eine Distribution, wie sie von verschiedenen Anbietern zusammengestellt wird. Diese Anbieter sorgen dafür, dass sämtliche enthaltene Software auf aktuellem Stand ist, dass Abhängigkeiten zwischen Software und Systembibliotheken korrekt aufgelöst werden können, dass es einen schönen bunten Installer für das System gibt sowie regelmäßige Updates.

Nun bin ich schon oft gefragt worden, warum ein .NET-Entwickler irgendwas zu Linux wissen sollte oder gar das System selbst im Alltag einsetzen. Nun, dazu gibts viele Antworten. Zum Beispiel ist Linux eine großartige Ausführungsplattform – schnell, performant, skalierbar und gratis! Natürlich ist das Adjektiv „gratis“ mit Vorsicht zu genießen: Die Pflege jedes Systems kostet schließlich Geld. Aber gerade in modernen Cloud-Umgebungen ist es letztlich so, dass Systeme einfach und ersetzbar sein sollen. Mit Linux fließen keine Lizenzgebühren an einen Hersteller, sodass eine Linux VM immer weniger kostet als eine mit Windows.

Total alltagstauglich

Ich selbst verwende Linux im Alltag, auf meinem Computer im Büro wie auch auf meinem Laptop. Ich bin Enthusiast und ich bin ein fortgeschrittener Anwender, mit mehr Bedürfnissen und Erwartungen an mein System der Wahl, dessen Flexibilität und Anpassbarkeit an meine Arbeitsweise. Mit Linux habe ich ein System gefunden, das ich überall einsetzen kann, in großen und kleinen Umgebungen, konsistent und flexibel, standardkonform und anpassbar. Kurz: Linux tut genau, was ich will.

Wenn Sie selbst Linux einmal ausprobieren wollen, haben Sie heute mehrere Möglichkeiten. Die beste ist, eine Distribution herunterzuladen (ich empfehle Ubuntu, für den allgemeinen Einsatz wie auch für den Einsteiger) und zu installieren. Das gelingt mit einem USB-Stick auf einem alten Laptop, oder auch einfach mit einem ISO-Image in einer VM. Bei der Installation passiert nichts, das einem geübten Computernutzer Sorgen bereiten sollte.

Alternativ können Sie auch eine virtuelle Maschine in der Cloud starten. Ob Azure, Amazon oder andere Anbieter, Linux-VMs sind in der Cloud einfach und schnell zu starten und kosten wenig. Mit einer solchen virtuellen Umgebung verbinden Sie sich im Anschluss mit SSH, der Secure Shell – dazu gleich etwas mehr.

Als letzte Variante zum Einstieg sei noch erwähnt, dass für Windows 10 und Windows Server auch das Windows Subsystem for Linux (WSL) verfügbar ist. Sie müssen dazu den Developer Mode auf diesen Systemen aktivieren und dann bash.exe ausführen, um etwa Ubuntu zu installieren. Alternativ können Sie auch andere Distributionen für WSL aus dem Microsoft Store herunterladen.

Schnittstellen aller Art

Ein wesentlicher Unterschied zwischen einer Installation auf eigener Hardware oder in einer lokalen VM und den Varianten in der Cloud oder mit WSL besteht in der Bedienoberfläche, mit der Sie konfrontiert werden. Es gibt eine Vielfalt grafischer Oberflächen für Linux, KDE und Gnome, Xfce, MATE, LXDE und viele mehr. Diese Umgebungen enthalten gewöhnlich einen Window Manager (die Komponente, die für den Umgang mit grafischen Fenstern verantwortlich ist) und basieren auf einer bestimmten Philosophie für das visuelle Erscheinungsbild und die Bedienung. Außerdem gibt es, für Entwickler interessant, bestimmte Libraries für Grafikausgabe und andere Systemfunktionalität (etwa GTK und Qt), die direkt mit den grafischen Oberflächen zusammenhängen.

Ich selbst verwende einen eigenständigen Window Manager namens i3, der sich besonders dadurch auszeichnet, dass der Anwender sich gar nicht mit der Maus um die Verwaltung der Fenster kümmern muss – meiner Meinung nach eine große Erleichterung. Darin besteht gerade der Spaß an Linux: Eigene Prioritäten können beliebig einbezogen werden. Anwendungssoftware ist übrigens von der Wahl einer grafischen Oberfläche unabhängig, sie lässt sich immer einsetzen.

Nun gibt es viele Gelegenheiten, zu denen Linux im praktischen Einsatz ohne grafische Oberfläche daherkommt. Das ist etwa bei der Cloud-VM so, aber natürlich auch bei anderen Hosting-Providern und sogar bei WSL. Es gibt sogar Anwender, die den Umgang mit einer Kommandozeile grundsätzlich vorziehen. Ich selbst zähle mich nicht zu Letzteren, aber ich muss sagen, dass unter den Fenstern, die ich im Alltag auf meinem System öffne, sehr viele Kommandozeilen zu finden sind. Tippen geht letztlich oft schneller als Klicken, besonders wenn man weiß, was man tut.

Shells und Kommandos

Die Kommandozeile auf einem Unix-System basiert auf der Shell. Die Shell zeigt einen Prompt an (gewöhnlich das aktuelle Verzeichnis im Dateisystem und ein paar andere Details) und erlaubt die Eingabe von Kommandos. Das klingt einfach, aber es gibt viele verschiedene Shells mit unterschiedlichen Prioritäten. Moderne Shells bieten automatische Vervollständigung von Kommandos mit spezifischem Verständnis für gewisse Kommandogruppen (z. B. git), Historie, Verwaltung von Hintergrundprozessen und vieles mehr, und sie können mit Shell-Skripten automatisiert werden, was besonders für Programmierer sehr nützlich ist. Meine persönliche Präferenz ist Zsh, zusammen mit den Plug-ins des Projekts oh-my-zsh.

Eine besondere Shell ist die bereits erwähnte Secure Shell (SSH). Diese baut eine gesicherte Verbindung zu einem anderen System auf (dazu dient ein Schlüssel, basierend auf einem Public-Key-Verfahren – bitte verwenden Sie nicht die einfache Passwortautorisierung!) und startet dort wiederum eine der eben beschriebenen normalen Shells. SSH hat viele weitergehende Mechanismen, die etwa das sichere Kopieren von Dateien ermöglichen oder das Tunneln beliebiger Datenverbindungen durch eine SSH-Verbindung. Für den interaktiven Umgang mit Systemen über SSH empfehle ich auch das Tool tmux (oder alternativ, etwas traditioneller, screen), das eine Art Fensterumgebung an der Kommandozeile ermöglicht, sodass mehrere Shells parallel verwendet werden können.

Das wichtigste Kommando für den Umgang mit einem Unix-System ist man für Manual, also Handbuch oder Anleitung. Mit diesem Kommando können Sie Informationen zur Verwendung anderer Kommandos anzeigen oder auch zu man selbst: man man. Ich empfehle, für alle im folgenden genannten Kommandos man auszuführen, um mehr Details zur Verwendung kennen zu lernen.

Darüber hinaus gibt es viele Standardkommandos, die gewöhnlich kurz benannt sind, damit man sie schneller eintippen kann, und viele mögliche Parameter haben, um Flexibilität zu erzeugen. Shell-Skripte zu schreiben bedeutet größtenteils, parametrisierte Kommandos aneinanderzureihen, bis etwas Nützliches passiert (Tabelle 1 und 2).

 

ls list: Liste von Dateien ausgeben
cp copy: Datei(en) kopieren
mv move: Datei(en) verschieben oder umbenennen
rm remove: Datei(en) löschen
mkdir make directory: Ordner anlegen
rmdir remove directory: Ordner löschen (muss leer sein!)
ln link: Verknüpfungen erzeugen
cat concatenate: Datei(en) ausgeben bzw. aneinander hängen
find Dateien suchen
grep In Dateien suchen
less Inhalte seitenweise anzeigen
ps processes: Prozesse anzeigen
Table 1: Wichtigste Kommandos

 

cd change directory: Ordner wechseln
alias Kommandoaliase erzeugen, ausgeben usw.
Table 2: In der Shell eingebaute Kommandos

 

Abgesehen von den Kommandos sollten Sie sich auch mit den Möglichkeiten vertraut machen, diese an der Kommondozeile zu kombinieren. Die erste Möglichkeit dafür ist die „Pipe“, dargestellt durch |, mit der die Ausgabe eines Kommandos (stdout) an ein anderes weitergeleitet wird (stdin). Zum Beispiel listet dieses Kommando alle Dateien im Systemverzeichnis /bin auf, zeilenweise in einer Langform, und leitet die Ausgabe dann weiter an less, um sie seitenweise anzuzeigen: ls -l /bin | less. Ein zweites Feature ist die Substitution, wobei die Ausgabe eines Kommandos in ein anderes eingebaut wird. Folgende Zeile verwendet which, um herauszufinden, wo das Kommando cat zu finden ist, und führt dann ls -l aus, um die ausführbare Datei von cat im Dateisystem anzuzeigen: ls -l `which cat`. Für Substitution kann auch diese etwas längere Form verwendet werden, falls der Umgang mit den richtigen Anführungszeichen schwierig ist: ls -l $(which cat)

Dateien und Rechte

Im Dateisystem gibt es keine Laufwerkbuchstaben. Stattdessen ist alles als Teil eines Baums organisiert, der bei / beginnt. Man nennt diese Wurzel passenderweise root. Es gibt viele Standardordner und -pfade wie /bin, /etc, /usr/lib und andere, und zusätzliche physikalische oder virtuelle Laufwerke werden in die Hierarchie eingehängt (gemountet). Dazu dient das Kommando mount bzw. die Konfigurationsdatei /etc/fstab.

Datei- und Ordnernamen unterscheiden zwischen Groß- und Kleinschreibung, und wenn sie mit einem . (Punkt) beginnen, werden sie nur von ls angezeigt, wenn die Option -a übergeben wird.

Natürlich sind im Dateisystem Zugriffsrechte wichtig. Wenn Sie sich die Ausgabe des Kommandos ls -l /bin/ls ansehen, finden Sie etwa an erster Stelle diesen kryptisch anmutenden String: -rwxr-xr-x. Unterteilt in drei Gruppen ist hier ersichtlich, welche Rechte der Besitzer der Datei hat (rwx: Read, Write, eXecute), sowie die besitzende Gruppe (r-x) und der Rest der Welt (r-x). Manchmal wird die Angabe auch numerisch dargestellt, sozusagen binär, mit dem Wert 4 für r, 2 für w und 1 für x. Die Rechte, die /bin/ls zugewiesen bekommen hat, können also als 755 geschrieben werden, ein Standardrecht, das besagt, dass jeder die Datei lesen und ausführen darf, aber nur der Besitzer Schreibrechte hat.

Um Berechtigungen zu konfigurieren, gibt es die Kommandos chown (change owner) und chgrp (change group), mit denen der Besitzer bzw. die besitzende Gruppe einer Datei oder eines Ordners gesetzt werden können. chmod ändert die Berechtigungen selbst, mithilfe verschiedener kreativer Parametersyntax. Zum Beispiel erteilt chmod u+x dem Besitzer (u: user) das Ausführungsrecht, chmod +w setzt Schreibrechte für alle drei Berechtigungsebenen, und chmod 644 erteilt explizit das Recht -rw-r–r–. Für Ordner ist es wichtig zu wissen, dass ein Benutzer das Recht x haben muss, damit er in den Ordner hinein navigieren kann (mit cd).

Zum Schluss ist für den Umgang mit Berechtigungen natürlich noch wichtig zu wissen, mit welchem Account Sie eigentlich im System unterwegs sind. Der allmächtige Superuser auf einem Unix-System heißt „root“ und hat die Benutzer-ID 0, aber mit diesem Konto sollten Sie aus Sicherheitsgründen praktisch niemals direkt arbeiten. whoami zeigt an, wie ihr aktueller Benutzer heißt, und Sie können mit adduser, addgroup und usermod andere Konten anlegen und modifizieren. Tatsächlich wird dies allerdings normalerweise fehlschlagen, da Sie als normaler Anwender nicht die Rechte haben, Benutzerkonten zu administrieren. Um temporär mit root-Berechtigungen zu arbeiten, stellen Sie einem Kommando sudo (super user do) voran. Damit können Sie alles machen im System, aber natürlich auch alles zerstören – also bitte Vorsicht!

Wenn dann was passiert

Das Kommando ps zeigt Prozesse an, die aktuell im System laufen. Alle Prozesse sehen Sie, wenn Sie Optionen angeben: ps aux. top oder htop stellen die laufenden Prozesse tabellarisch dar und bieten interaktive Features, um die Ausgabe zu sortieren oder weitere Details abzurufen.

Mit kill schicken Sie einem Prozess ein Signal, alle Signale können Sie mit kill -l abrufen. Ein Prozess kann generell auf bestimmte Signale reagieren. Oft tut er das allerdings, indem er sich beendet, womit sich der Name des Kommandos erklärt. Um einen hängenden Prozess zu beenden, verwenden sie kill -9. Sie müssen nach dem Signal selbst noch die PID (process ID) des Prozesses übergeben, wie sie in der Ausgabe von ps erscheint. Alternativ können Sie auch skill verwenden, das einen Prozess namentlich identifizieren kann.

Die Konfiguration eines Linux-Systems basiert zum größten Teil auf Textdateien, die im Ordner /etc (und dessen Unterordnern) zu finden sind. Wenn eine Konfigurationsdatei für einen einzelnen Benutzer angepasst werden kann (die man-Page sagt Ihnen das), gibt es oft eine entsprechende Datei mit einem Punkt vor dem Namen im Home Directory des Benutzers. Seit einiger Zeit hat sich auch die Verwendung des Ordners ~/.config verbreitet (also der Ordner .config im Home Directory des Benutzers). In das Home Directory gelangen Sie übrigens am einfachsten, indem Sie cd ohne Parameter ausführen. Gewöhnlich lautet der Pfad /home/, aber ~ (Tilde) ist ein Kürzel, das immer verwendbar ist.

Admin in spe

Wenn Sie Konfigurationsdateien bearbeiten, sollten Sie sehr vorsichtig vorgehen, insbesondere bei denen in /etc. Sie können damit sehr leicht zumindest einzelne Systemdienste beschädigen. Trotz dieser Gefahr ist es regelmäßig nötig, solche Dateien zu bearbeiten, und dafür brauchen Sie natürlich einen Editor. Erfahrene Unix-Benutzer unterteilen sich im Allgemeinen in zwei Gruppen: Anwender des Editors vi (oder vim, der moderneren Version davon), und Anwender von Emacs. Beide Editoren sind extrem mächtig, es gibt dazu unter Windows oder anderen Systemen nichts Vergleichbares – gleichzeitig sind sie aber nicht besonders einfach im praktischen Einsatz. Ich empfehle daher den Editor nano für Einsteiger, der gewöhnlich auch von den Distributionen als Standardpaket verfügbar gemacht wird. Der wichtigste Vorteil besteht darin, dass nano die verfügbaren Tastaturkommandos visuell anzeigt und so den Einstieg wesentlich einfacher macht.

Nun fehlen in diesem Rundumschlag für den werdenden Linux-Administrator noch zwei wichtige Informationen. Daher hier erstens ein paar Worte zur Paketverwaltung. Pakete sind die Bausteine jeder Distribution. Sie enthalten Dateien, etwa für ein bestimmtes Programm, und Skripte, die bei der Installation des Pakets und bei anderen Anlässen automatisch ausgeführt werden. Leider gibt es allerdings verschiedene Paketsysteme, je nach der Distribution, für die Sie sich entschieden haben. Für die verbreiteten Distributionen Debian und Ubuntu verwenden Sie das Kommando apt (oder in älteren Versionen apt-get), während bei Red Hat und Derivaten rpm das zentrale Kommando ist. Auf den Webseiten der Distribution sollten Sie die notwendigen Details recht einfach finden können, da die Paketverwaltung ein zentraler Bestandteil des Systems ist.

Der zweite und letzte ausstehende Punkt besteht in ein paar Details auf niedriger Ebene. Zum Beispiel müssen Sie das System eventuell einmal neu starten oder herunterfahren. sudo reboot startet ein Linux-System neu, und sudo shutdown now beendet alle Prozesse, fährt das System herunter und schaltet es aus. Im Verzeichnis /var/log finden Sie Systemprotokolle – dies sollte der erste Anlaufpunkt sein, wenn im System einmal etwas nicht funktioniert!

Und dann: .NET

Nun möchte ich diese Einleitung mit einer Beschreibung der Schritte, die zur Ausführung einer .NET-Core-MVC-Anwendung notwendig sind, abschließen. Wenn Sie dem Beispiel folgen mögen, haben Sie letztlich Ihren ersten eigenen Linux-Server aufgesetzt.

Zunächst benötigen Sie .NET Core selbst. Microsoft hat hierzu unter “Get started with .NET in 10 minutes” Anleitungen publiziert, die sich je nach Distribution aufgrund der angesprochenen Unterschiede in der Paketverwaltung unterscheiden. Gewöhnlich sind dort fünf oder sechs Kommandos zu finden, nach deren Ausführung Sie .NET Core vollständig installiert haben.

Wenn Sie auf dem System selbst gern entwickeln wollen und Ihnen einen grafische Oberfläche zur Verfügung steht, empfehle ich auch sehr die Installation von Visual Studio Code. Auch hierzu stellt Microsoft eine detaillierte Anleitung unter “Running VS Code on Linux” bereit.

Nun haben Sie womöglich bereits eine .NET-Core-MVC-Anwendung, doch Sie können mit folgenden Kommandos auch einfach eine erzeugen und ausführen:

 

mkdir demoapp
cd demoapp
dotnet new mvc
dotnet run

 

Um diese Anwendung nun als Systemdienst ausführbar zu machen, sind mehrere Schritte erforderlich oder zumindest ratsam. Zunächst sollten Sie die Anwendung im Dateisystem installieren, also nicht etwa direkt aus dem Entwicklungsverzeichnis heraus starten. Die folgenden Kommandos sind dafür erforderlich:

 

dotnet publish
sudo mkdir /usr/local/demoapp
sudo cp -r bin/Debug/netcoreapp2.0/publish/\* /usr/local/demoapp
sudo chown -R www-data.root /usr/local/demoapp

 

Damit die Anwendung beim Systemstart automatisch ausgeführt wird, müssen Sie nun einen Dienst einrichten. Dazu erzeugen Sie zunächst eine neue Datei in /etc/systemd/system:

 

sudo nano /etc/systemd/system/demoapp.service

 

In dieser Datei werden Details zur Ausführung der Anwendung eingetragen. Die Datei könnte folgendermaßen aussehen:

 

[Unit]
Description=Demo App

[Service]
WorkingDirectory=/usr/local/demoapp
ExecStart=/usr/bin/dotnet /usr/local/demoapp/demoapp.dll
Restart=always
RestartSec=10
SyslogIdentifier=demoapp
User=www-data
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

 

Nun können Sie den neu erzeugten Dienst aktivieren und sofort starten:

 

sudo systemctl enable demoapp
sudo systemctl start demoapp

 

Wenn Sie an dieser Stelle (wie zuvor nach dem Kommando dotnet run) den URL http://localhost:5000 im Browser ansteuern, sollten Sie die laufende Anwendung zu sehen bekommen. Allerdings ist es eine wichtige und dringliche Empfehlung von Microsoft, den Server Kestrel nicht für den Rest der Welt zugänglich zu machen. Stattdessen sollte hierfür ein Proxy-Server verwendet werden. Es bleibt also als letzter Schritt die Aufgabe, diesen ebenfalls einzurichten. Ich entscheide mich für diesen Zweck für nginx, das Sie zunächst (für Ubuntu) folgendermaßen installieren müssen:

 

sudo apt install nginx

 

Nun erzeugen Sie eine neue Konfigurationsdatei für demoapp:

 

cd /etc/nginx
sudo vi sites-available/demoapp

 

Die Datei sollte mindestens diesen Inhalt haben:

 

server {
  listen 80;
  location / {
    proxy_pass http://localhost:5000;
  }
}

 

Es ist ziemlich leicht ersichtlich, was hier passiert: Zugriffe auf Port 80 des Servers werden an den Kestrel-URL weitergeleitet. Microsoft empfiehlt, bei der Konfiguration von nginx noch einige weitere optionale Schritte zu machen, weitere Details können Sie unter “Host ASP.NET Core on Linux with Ngix” nachlesen.

Es ist möglich, dass das Standard-Set-up von nginx auf Ihrem System bereits einen Dienst auf Port 80 betreibt. Sie können diesen Dienst entfernen, falls notwendig:

 

sudo rm sites-enabled/default

 

Nun aktivieren Sie Ihren eigenen neuen Dienst und sorgen dafür, dass nginx seine Konfiguration neu lädt:

 

sudo ln -s sites-available/demoapp sites-enabled/
sudo nginx -s reload

 

Damit ist der Vorgang abgeschlossen. Sie können nun mit dem Browser auf Port 80 des Linux-Systems zugreifen und dort die laufende Anwendung sehen.

Wenn dieser Artikel Ihr Interesse an Linux geweckt oder neu entflammt hat, sind die Sessions „Linux 101 für .NET-Entwickler“ von Oliver Sturm und “Linux Sicherheit für .NET Entwickler” von Tobias Kopf genau die richtigen für Sie.

Alternativ gibt es von Oliver Sturm den Architektur Workshop am Montag oder den Architektur Day am Donnerstag, mit vielen Themen rund um die aktuelle Software-Entwicklung im Microsoft-Umfeld.

The post Ich weiß nicht, ob Sie es wussten: Aber Linux, das braucht jetzt jeder appeared first on BASTA!.

]]>
4 schlagkräftige Argumente für den BASTA!-Besuch https://basta.net/blog/4-schlagkraeftige-argumente-fuer-den-basta-besuch/ Mon, 28 May 2018 12:26:58 +0000 https://basta.net/?p=23236 Sie möchten gerne an der diesjährigen BASTA! in Mainz teilnehmen und wissen nicht, wie Sie ihren Boss von Ihrer Teilnahme überzeugen? Punkten Sie mit unseren Top-Argumenten!

The post 4 schlagkräftige Argumente für den BASTA!-Besuch appeared first on BASTA!.

]]>
„Weiterbildung ist zu teuer“, „Schon wieder ein Konferenzbesuch?“ – Mit solchen Argumenten werden Anfragen der Mitarbeiter für eine Weiterbildung vom Tisch gefegt. Ehe man den Vorgesetzten mit einem Weiterbildungswunsch konfrontiert, sollte der “Schlachtplan” stehen. Mit diesen Argumenten überzeugen Sie Ihren Chef von einem Besuch auf der BASTA! Am Ende finden Sie eine Vorlage für die E-Mail an Ihren Chef.

 

 


Und damit Sie sich nicht mit Formulierungen herumschlagen müssen, haben wir hier für Sie die ultimative Textvorlage für Ihren Boss.

Eine Vorlage für die E-Mail an Ihren Chef

Sehr geehrte/r Frau / Herr (…),

ich möchte Sie gerne um Erlaubnis bitten, an der BASTA!-Konferenz teilzunehmen, die vom 24. – 28. September in Mainz stattfinden wird. Die BASTA! ist seit mehr als 20 Jahren die führende unabhängige Konferenz für Microsoft-Technologien, JavaScript und Web Development.

Die radikale Transformation von Microsoft und seiner Produkte hat nicht nur Auswirkungen auf Microsoft selbst, sondern auf die IT-Industrie im Allgemeinen und auf unsere Unternehmensstrategie im Besonderen. Darüber hinaus ist es unabdingbar, auf dem Laufenden zu bleiben und im Prozess ständig wechselnder Trends und neuen Entwicklungen mitzuhalten. Und genau diese Konferenz öffnet für uns völlig neue Möglichkeiten, unsere Produkte zu entwickeln und auf den Markt zu bringen.

Die Highlights der BASTA!:

  • Hier werden in den 130 + praxisnahen Workshops und Sessions neueste Technologien zu C#, dem .NET-Framework, der Software-Architektur und der agilen Softwareentwicklung aufgegriffen.
  • Die Konferenz vermittelt unter anderem wertvolles Know-how zu Angular, node.js, Azure, Docker, DevOps, Cloud und vielem mehr.
  • Mehr als 80 internationale erfahrene Microsoft-Experten sprechen auf der BASTA.
  • Best-Practices und Live-Coding Beispiele, die ich garantiert in meinem Alltag anwenden kann.
  • Materialien und Aufzeichnungen der meisten Sessions sind im Nachgang zum Download verfügbar.
  • Die BASTA! bietet Möglichkeiten, Gleichgesinnte zu treffen, ähnliche Probleme mit ihnen zu teilen und Lösungen zu finden.

Alle Infos zur Konferenz und zu Frühbucherpreisen finden Sie hier.

Ich bin mir sicher, dass ich als Teilnehmer für mich, meine Kollegen und unser Unternehmen hier das Wissen bekomme, das wir für die digitale Transformation brauchen, denn die steckt hinter allen diesen einzelnen Aspekten.

Mit besten Grüßen

(Ihr Name)


The post 4 schlagkräftige Argumente für den BASTA!-Besuch appeared first on BASTA!.

]]>
Cross-Plattform mit PWA und Angular https://basta.net/blog/cross-plattform-mit-pwa-und-angular/ Wed, 16 May 2018 08:17:51 +0000 https://basta.net/?p=23103 Wer heute moderne Cross-Plattform-Anwendungen schreiben will, hat ein breites Set von Tools und Technologien zur Auswahl, dieses Ziel mehr oder weniger leicht und umfangreich zu erreichen. Gerade im letzten Jahr sind auch für .NET-Entwickler neben Xamarin vor allem Webtechnologien als Möglichkeiten hinzugekommen. Shmuela Jacobs stellt auch in diesem Jahr die Möglichkeiten von PWAs vor.

The post Cross-Plattform mit PWA und Angular appeared first on BASTA!.

]]>

Als Vorgeschmack auf die Session im September zeigt dieses Video, wie Webanwendungen und speziell Single Page Applications nicht nur Nutzern mit leistungsfähigen Computern und starker Wi-Fi-Verbindung ein schnelles, flüssiges Erlebnis bieten können. Denn ein großer Teil der Webaktivitäten wird auf mobilen Geräten ausgeführt, die unterschiedliche Funktionen erfordern, wie z.B. die Minimierung des Daten- und Stromverbrauchs. Native mobile Anwendungen bieten darüber hinaus zusätzliche Funktionen zur Einbindung der Benutzer, wie z.B. Benachrichtigungen. Aber ihnen fehlen einige der Funktionen, die Webanwendungen haben. Die heutigen Technologien und die schnelle Weiterentwicklung der Browser ermöglichen es uns, das Beste aus beiden Welten zu genießen. Wir können Webanwendungen mit einem nativen Look and Feel für mobile Anwendungen schreiben. Wir können diese Anwendungen auf Desktopbrowsern ausführen und die Funktionen der nativen mobilen Anwendungen nutzen. Shmuela Jacobs stellt die wunderbare Welt der Möglichkeiten mit Progressive Web Apps vor und wie Angular Ihnen hilft, diese Welt zu betreten.

 

 

Seit der BASTA! 2017 hat sich das Rad mit Blick auf PWAs weitergedreht, und seit der Veröffentlichung von Windows 10 April 2018 Update ist neben Googles Chrome und Mozillas Firefox nun auch der Windows-eigene Edge-Browser soweit, PWAs auf dem PC zu nutzen. Zudem hat Microsoft schon zahlreiche PWAs in den Store aufgenommen und wird die Angebote weiter ausbauen. Auf der BASTA! erfahren Sie mehr zu den Möglichkeiten und Entwicklungen auf dem Mobile Business Applications Day und Cross Plattform Day sowie in zahlreichen anderen Sessions.

Lesen Sie mehr zu diesem Thema in unserem Blogartikel “Progressive Web Apps – die Zukunft von morgen schon heute?”

The post Cross-Plattform mit PWA und Angular appeared first on BASTA!.

]]>
Blockchain – wieder nur ein Tech-Hype oder eher sinnvolle Zukunftstechnologie? https://basta.net/blog/blockchain-wieder-nur-ein-tech-hype-oder-eher-sinnvolle-zukunftstechnologie/ Mon, 23 Apr 2018 10:17:09 +0000 https://basta.net/?p=23066 Bitcoin, Ethereum oder Smart Contracts – diese drei Begriffe kommen den meisten in Sinn, wenn sie den Begriff „Blockchain“ hören. Doch ist das wirklich alles? Sicher nicht! Kryptowährungen wie Bitcoin, Ether oder Litecoin sind nur ein spezifischer Anwendungsfall öffentlicher Blockchains. Viel wertvoller, gerade im B2B-Bereich, sind private Blockchains. Vom Betrieb auf eigener Hardware mit Kontrolle über den Anschluss neuer Mitglieder an die Kette bis hin zur benutzerbezogenen Smart-Contract-Verwaltung lässt sich alles realisieren.

The post Blockchain – wieder nur ein Tech-Hype oder eher sinnvolle Zukunftstechnologie? appeared first on BASTA!.

]]>

Die Use Cases für private Blockchains sind umfangreich. Angefangen von der Prozesstransparenz für Kunden und Geschäftspartner über die Nachverfolgbarkeit von digitalen und physischen Gütern, über die Revisionssicherheit bis hin zur Möglichkeit für Read-only-Zugriffe von Auditoren kann eine Blockchain sinnvoll – und völlig ohne Hype – eingesetzt werden. Der wichtige Aspekt hierbei ist, dass dies alles ohne eine zentralisierte Stelle und damit vollständig dezentral funktioniert. Das Nachdenken darüber lohnt sich in jedem Fall.

 

Kleines Blockchain 1×1

Bitcoin und Ethereum gehören wohl mit zu den zwei bekanntesten öffentlichen Blockchains. Vom Entwickler bis hin zur Geschäftsführung ist der Begriff Bitcoin geläufig, der Hype in den Medien durch starken Kursanstieg und -abfall ist sehr hoch. Doch oftmals fehlt das Wissen, was genau eigentlich hinter Bitcoin, Ethereum und Co. steckt: die Blockchain als Basistechnologie.

Bitcoin und Ethereum haben einige Gemeinsamkeiten. Beide sind öffentliche Blockchains. Das bedeutet, dass jeder am Netzwerk partizipieren kann. Durch die Natur der Blockchain ist das Netzwerk vollständig dezentral aufgebaut. Das heißt, es gibt keinen Masterserver, zu dem sich alle verbinden, und es muss auch vorab kein Account angelegt werden. Die Teilnahme findet anonym statt. Über einen Peer-to-Peer-Prozess finden sich die Nodes untereinander selbstständig. Fällt der eine oder mehrere Nodes aus, finden sich die noch laufenden Nodes über den gleichen Prozess wieder.

Eine weitere Gemeinsamkeit ist, in welcher Art und Weise neue Blöcke zur Verkettung generiert werden. Die beiden Blockchains arbeiten nach dem sogenannten Proof-of-Work-Prinzip. Bei Ethereum wird im Schnitt alle 15 Sekunden ein neuer Block generiert, bei Bitcoin alle 10-15 Minuten. Innerhalb eines Blocks befinden sich sogenannte Transaktionen. Am Beispiel von Bitcoin sind dies – etwas vereinfacht – die Überweisungen der Währung: „Konto A sendet an Konto B 0,5 Bitcoin“.

Die eigentliche Krux an der Sache ist, wie der nächste Block generiert wird. Hier kommt der Begriff Mining ins Spiel. Dahinter verbirgt sich nicht, wie man vielleicht vermutet, die Extraktion einer Währung aus dem Netzwerk, sondern vielmehr die Lösung eines kryptografischen Rätsels, beispielsweise in Form der Findung eines Hashs nach bestimmten Regeln. Im Falle von Bitcoin wäre dies, dass der Hash mit einer bestimmten Anzahl an Nullen beginnt. Hier entsteht auch das bekannte Problem vom Mining: der sehr hohe Stromverbrauch, um dieses Rätsel zu lösen. Laut dem Bitcoin Energy Consumption Index werden dazu aktuell ca. 61 TWh eingesetzt. Zum Vergleich: Im Jahr 2014 lag der Endenergieverbrauch von Berlin bei rund 64 TWh.

 

Die private Blockchain im Businessumfeld

Ist das wirklich das, was wir im B2B-Umfeld wollen? Sicherlich nicht. Zum einen wollen wir in einem B2B-Umfeld in der Regel keine öffentliche Blockchain betreiben, an der jeder anonym teilnehmen kann. Zum anderen wollen wir kein Proof-of-Work Mining zur Blockgenerierung nutzen. Bei einem Netzwerk mit bekannten Teilnehmern können alternative Algorithmen eingesetzt werden. Für dieses Vorgehen eignen sich private Blockchains (Permissioned Blockchains), betrieben auf eigener Hardware (bspw. im eigenen Rechenzentrum oder in einer Cloud VM) mit vertraglich geregelten Teilnehmern.

Bei privaten Blockchains können wir nicht nur bei der Blockgenerierung bestimmen, wie diese stattfindet, sondern auch wie neue Teilnehmer in das Netzwerk integriert werden. Während bei öffentlichen Blockchains jeder anonymisiert teilnehmen kann, kann bei einer privaten Blockchain sehr genau bestimmt werden, wer die Teilnehmer eines Netzwerks sein dürfen und wie sie integriert werden können. Beispielsweise ist es möglich, dass ein bestimmter Node im Netzwerk neue Teilnehmer direkt aufnehmen kann. Ein Fall wäre hier, wenn ein Konsortium eine Blockchain aufbauen möchte. Jeder interessierte Marktteilnehmer muss einen Vertrag mit dem Konsortium abschließen, das dann wiederum den Teilnehmer direkt in das Blockchain-Netzwerk aufnehmen kann. Ein anderes Modell wäre, dass jeder Node einen neuen Teilnehmer vorschlagen und jeder bereits im Netzwerk bestehende Teilnehmer diesen Vorschlag annehmen oder ablehnen kann. Je nach Modell könnte z.B. eine zweidrittel- oder achtzigprozentige Mehrheit zur Aufnahme des neuen Teilnehmers ausreichen.

 

Microservices & APIs Track auf der BASTA! 2018

 

Und wo läuft der Code?

Bisher haben wir zwar von der Erzeugung von Blöcken und den darin enthaltenen Transaktionen gesprochen, aber noch nicht, wer diese Transaktionen verarbeitet – also Code basierend darauf ausführt. Code dieser Art wird in der Regel Smart Contract genannt oder je nach Blockchain-Technologie auch Chaincode. Eine generelle Aussage hierfür existiert nicht, da dies eine Implementierungssache der jeweiligen Blockchain-Technologie ist. Grundsätzlich ist ein Smart Contract eine Funktion, die auf der Blockchain aufgerufen werden kann. Jeder Smart Contract ist unter einer bestimmten Adresse (die je nach Blockchain-Technologie anders aufgebaut ist) erreichbar, kann Daten entgegennehmen, speichern und ein Ergebnis liefern.

Beim Beispiel Ethereum wird der Code selbst ebenfalls über eine Transaktion in der Blockchain hinterlegt. Der Code wird somit ein unveränderlicher Teil der Blockchain. Er wird in der Programmiersprache Solidity entwickelt und in der Ethereum VM ausgeführt. Hier muss man sich vor dem ersten Deployment des Codes Gedanken machen, nach welchem Modell er aktualisiert werden kann. Auch dieses Modell und dessen Änderungsregeln, die typischerweise aus mehreren verbundenen Smart Contracts bestehen, müssen im Code hinterlegt werden.

Anders funktioniert zum Beispiel die Blockchain-Technologie Tendermint. Sie bietet keine eigene VM, sondern eine Kommunikationsschnittstelle, bspw. über GRPC, an. Diese Schnittstelle kann von verschiedenen Programmiersprachen angesprochen werden, bspw. auch mit .NET-Code. Das bedeutet, dass wir in diesem Fall unsere Smart Contracts mit .NET (Core) implementieren können und er Side by Side zur eigentlichen Blockchain läuft, damit veränderbar und kein unveränderlicher Teil der Blockchain ist. Wichtig ist hier, dass zu jedem Zeitpunkt auf jedem Node der gleiche Code ausgeführt wird, da sonst unterschiedliche Hashs generiert werden würden, was zum Ablehnen eines Blocks führen kann.

Wir sehen also, dass wir mit unterschiedlichen Blockchain-Technologien viele Möglichkeiten haben, wie wir eine Blockchain für B2B Use Cases implementieren und benutzen können: weniger Technologiehype, mehr sinnvolle Zukunftstechnologie, bei einer sehr großen Bandbreite praktischer Einsatzmöglichkeiten.


Im Rahmen der BASTA! 2018 haben Sie die Möglichkeit, durch die Sessions und den Ganztagesworkshop von Ingo Rammer und Manuel Rauber weitere theoretische und technische Eindrücke zu erhalten, und zu lernen, wie Sie Ihr erstes privates Blockchain-Netzwerk konfigurieren und betreiben und Ihre ersten Smart Contracts implementieren können.

The post Blockchain – wieder nur ein Tech-Hype oder eher sinnvolle Zukunftstechnologie? appeared first on BASTA!.

]]>
Willkommen in der Post-JavaScript-Ära mit Blazor https://basta.net/blog/willkommen-in-der-post-javascript-aera-mit-blazor/ Tue, 27 Mar 2018 12:48:53 +0000 https://basta.net/?p=22922 Die erste Preview von ASP.NET Blazor bringt C# wieder in den Browser. Und damit steht .NET-Entwicklern dank WebAssembly die Webwelt mit ihren gewohnten Sprachen und Tools offen. Mit C#, HTML5 und CSS gebaute Anwendungen sind dann in jedem Browser und auf jeder Plattform ausführbar und das ganz ohne Plug-in oder eigene JavaScript-Anstrengungen. Rainer Stropek stellt kurz vor, wie die Zukunft mit Blazor aussehen kann.

The post Willkommen in der Post-JavaScript-Ära mit Blazor appeared first on BASTA!.

]]>

So wie viele andere Personen in der Softwareentwicklung auch, habe ich das Programmieren mit Sprachen wie C++, Java und vor allem C# gelernt. Ich mag daher typisierte Sprachen und mir sind die Vorteile einer vollwertigen IDE im Vergleich zu einem leichtgewichtigen Editor mit Konsole bewusst. Dementsprechend habe ich mich von Anfang an in JavaScript nicht ganz wohl gefühlt. Zugegeben, es ist eine spannende Programmiersprache, die ihre Mächtigkeit erst auf den zweiten Blick zeigt. JavaScript lädt zum Tüfteln ein. Aber JavaScript für große, professionelle Projekte – naja, nicht so mein Ding.

TypeScript hat meine Einstellung zu clientseitiger Webentwicklung geändert. Diese Sprache bietet mir das Beste aus beiden Welten. Am Ende des Tages läuft es aber genauso auf JavaScript hinaus. Um gut TypeScript zu programmieren, muss man solides JavaScript-Wissen haben. Ansonsten verzweifelt man recht schnell, wenn es gilt, die ersten Probleme in größeren Anwendungen zu beheben. Ohne JavaScript geht clientseitige Webentwicklung also nicht, oder?

Ich mochte Silverlight

Jetzt ist es raus. Ich fand einiges an Silverlight nicht schlecht. Vor allem C# im Browser übt großen Reiz auf mich aus. Es ist eine moderne, weit verbreitete Programmiersprache, die kontinuierlich weiterentwickelt wird, sie ist Open Source und auf praktisch allen Plattformen verfügbar, mit Visual Studio und Visual Studio Code stehen sowohl eine mächtige IDE als auch ein schlanker Editor zur Verfügung – das sind nicht zu vernachlässigende Vorteile. Dazu kommt, dass eine einheitliche Sprache und Base Class Library für Server und Client die Entwicklungsproduktivität enorm steigert. Stimmt schon, dass man das auch mit Node.js und TypeScript schafft. Ich habe Node.js in den letzten Jahren kennen und mögen gelernt. Auf die häufig fehlenden oder schlecht gewarteten TypeScript Type Definitions kann ich aber gut und gerne verzichten. Da ist mir mein serverseitiges ASP.NET Core bei großen Projekten schon lieber.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Die größte Schwäche von Silverlight war, dass es nicht auf einem offenen Browserstandard aufgebaut war, sondern ein Plug-in braucht. Dementsprechend ging Silverlight mit dem Verbannen von Plug-ins durch Browser- und Plattformhersteller unter. Neben der Notwendigkeit eines Plug-ins gab es noch andere, häufig kritisierte Punkte: Warum mit XAML eine eigene Markup-Sprache, es gibt doch HTML? Warum eine eigene Styling- und Templatelogik, man könnte doch CSS nehmen? Trotz guter Ansätze scheiterte Silverlight und das Monopol von JavaScript im Browser blieb aufrecht.

Auftritt WebAssembly

Im März 2017 tat sich etwas Fundamentales in der Webwelt: Das erste Minium Viable Product (MVP) von WebAssembly erschien. Damit ist der Startschuss für einen ernsthaften Konkurrenzkampf mit JavaScript gefallen. Was macht WebAssembly (kurz Wasm) aus?

  • Das Beste gleich zu Beginn: Wasm wird von der W3C vorangetrieben und wird ohne Plugin von allen führenden Browsern (Chrome, Edge, Firefox und WebKit) auf Desktop und Mobilplattformen unterstützt.
  • Es ist ein binäres Format für eine virtuelle Maschine. Sprachen wie C++ können in Wasm übersetzt werden.
  • Wasm wurde nicht ausschließlich für den Browser konzipiert, sondern kann generell als Standard für portablen Code genutzt werden. In diesem Artikel beschränken wir uns aber auf Wasm im Browser.
  • Im Browser kann Wasm JavaScript aufrufen und umgekehrt.
  • Wasm ist bei der Ausführung so schnell wie nativer Code.
  • Es gibt ein Textformat für Wasm, damit man mit dem Code leichter umgehen kann und Debuggingunterstützung hat.
  • Wasm wird in einer Sandbox ausgeführt, die speziell im Browser für die notwendige Sicherheit sorgt. Die Sandbox kann auch auf JavaScript basieren. Wer gar nicht anders kann, erreicht daher mit Wasm auch den Internet Explorer.

Mono mit Blazor auf Wasm

C++ kann wie erwähnt in Wasm kompiliert werden, der Großteil aller weit verbreiteten Programmiersprachen ist auf unterster Ebene in C++ geschrieben, der Sturm auf den Browser ist also eröffnet. C# ist dabei an vorderster Front dabei. Das Mono-Team hat bereits im August 2017 erste Prototypen veröffentlicht, die zeigen, wie C# und .NET auf Wasm laufen. Natürlich sind Programmiersprache und Basisbibliothek noch zu wenig, um in der Praxis größere Weblösungen zu bauen. Genau diese Lücke schließt das Blazor-Projekt.

 

.NET Framework & C# Track auf der BASTA!

 

Ich kenne eine Menge .NET-Entwicklungsteams, die schon lange darüber nachdenken, wie sie mit ihren weit verbreiteten WinForms-, WPF- und Xamarin-Anwendungen ins Web kommen. Hat man ein größeres Entwicklungsteam mit viel Erfahrung in diesen Technologien und eine Menge bestehenden Code, wechselt man nicht so mir nichts, dir nichts auf z. B. TypeScript mit Angular. Blazor ändert in solchen Situationen die Spielregeln.

  • HTML und CSS muss immer noch gelernt werden, das C#-, .NET- und Toolwissen ist aber weiter anwendbar.
  • Bestehender Code (z. B. Businesslogik, Formvalidierung, eventuell sogar Teile der View-Logik) kann im Browserclient wiederverwendet werden.
  • Die üblichen Mechanismen für Modularisierung (Projekte, DLLs, NuGet-Pakete) funktionieren wie gewohnt.

Neugierig? Dann lassen Sie uns kurz einen Blick darauf werfen, wie Blazor das Kunststück schafft, C#, .NET und den Browser zu vereinen.

Blazor = Browser + Razor

ASP.NET Blazor verbindet Wasm, die Mono-Plattform und Razor, die Markup-Sprache für dynamisches HTML aus ASP.NET Core. Code schreibt man in C#. Für das UI gibt es im Gegensatz zu Silverlight keine eigene Sprache, sondern es werden HTML und CSS verwendet. Für die Leserinnen und Leser, die noch nie mit Razor gearbeitet haben, habe ich in Listing 1 ein einfaches Codebeispiel zusammengestellt. Ich werde in diesem Artikel nicht auf Details eingehen, der Code soll nur einen Eindruck über das prinzipielle Vorgehen vermitteln. Im Windows Developer Magazin 7.18 und auf der kommenden BASTA! 2018 folgen ausführliche Einführungen in das Programmieren mit Blazor.

@page "/"
@using RestApi.Shared
@inject HttpClient Http

<h1>Customer List</h1>

<button @onclick(async () => await FillWithDemoData())>Fill with demo data</button>

@if (Customers == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <table class='table'>
    <thead>
      <tr>
        <th>ID</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Department</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var customer in Customers)
      {
        <tr>
          <td>@customer.ID</td>
          <td>@customer.FirstName</td>
          <td>@customer.LastName</td>
          <td>@customer.Department</td>
        </tr>
      }
    </tbody>
  </table>
}
@functions {
  private Customer[] Customers { get; set; }

  protected override async Task OnInitAsync() =>
    await RefreshCustomerList();

  private async Task RefreshCustomerList()
  {
    Customers = await Http.GetJsonAsync<Customer[]>("/api/Customer");
    // Trigger UI refresh
    StateHasChanged();
  }

  private async Task FillWithDemoData()
  {
    for (var i = 0; i < 10; i++)
    {
      await Http.SendJsonAsync(HttpMethod.Post, "/api/Customer", new Customer
      {
        FirstName = "Tom",
        LastName = $"Customer {i}",
        Department = i % 2 == 0 ? "Sales" : "Research"
      });
    }

    await RefreshCustomerList();
  }
}

Da Wasm im Moment noch nicht direkt auf die Browser- und DOM-APIs zugreifen kann, besteht Blazor aus einem .NET- und einem JavaScript-Teil. Wann immer Blazor mit einem Browser-API interagieren muss (z. B. das DOM zur Laufzeit verändern, einen HTTP Request abschicken), übergibt der Wasm-Teil von Blazor an den JavaScript-Teil.

In Abbildung 1 sieht man die Struktur von Blazor. Das Bild zeigt das Netzwerklog beim Laden einer Blazor-Beispielanwendung. Als erstes werden die JavaScript-Teile von Blazor und Mono geladen. Sie beginnen mit dem Bootstrap-Prozess von Blazor. Als nächstes lädt der Browser die Mono-Wasm-Laufzeitumgebung. Sie holt danach die notwendigen .NET-DLLs (eigene Anwendung und benötigte Framework-DLLs) vom Webserver. Die DLLs sind ganz normale .NET-Standard-DLLs wie man sie aus jeder anderen .NET-Core-Anwendung kennt. Die Mono Wasm Runtime interpretiert die .NET DLLs im Moment. Sie werden in der vorliegenden, ersten Preview von Blazor nicht in Wasm kompiliert. Das Blazor-Team hat auf GitHub erwähnt, dass bereits daran gearbeitet wird, sowohl Interpretation als auch Ahead-of-Time-Kompilieren (AoT) zuzulassen.

 

Abb. 1: Laden einer Blazor-Beispielanwendung 

 

Abbildung 2 zeigt, wie JavaScript, Wasm und .NET beim Behandeln eines Button-Click-Ereignisses zusammenspielen. Code im JavaScript-Teil von Blazor (BrowserRenderer.ts) reagiert auf den DOM-Event. Er übergibt den Event an die Mono Wasm Runtime, die den entsprechenden .NET-Code ausführt. Das Ergebnis, der sogenannte Render Batch, wird von Mono Wasm an JavaScript zurückgegeben. JavaScript ist für das Durchführen der notwendigen DOM-Änderungen, also für das Aktualisieren der Benutzerschnittstelle, verantwortlich.

 

Abb. 2: Ablauf eines Button-Clicks in Blazor 

 

Wo steht Blazor?

Wer an dieser Stelle begeistert ist und plant, sofort die Entwicklungsstrategie in Richtung Blazor zu verschieben, den möchte ich bremsen. Blazor ist ein experimentelles Projekt und es gibt gerade einmal eine erste Public Preview. Man muss im Moment das System sogar noch selbst kompilieren und kann keine fertige Visual-Studio-Erweiterung herunterladen.

Die Webwelt mit ihrer rasanten Entwicklungsgeschwindigkeit und unsere Erfahrungen mit Silverlight haben uns gelehrt, entspannt an neue Entwicklungen heranzugehen und nicht zu schnell das Ruder herumzureißen. Jetzt ist die richtige Zeit, um mit Blazor zu experimentieren, Feedback zu geben, mitzuarbeiten (Blazor ist Open Source, das Team akzeptiert gerne Pull Requests und GitHub Issues) und die Auswirkungen auf die zukünftige Architektur der eigenen Projekte abzuschätzen.

Da die Dokumentation zu Blazor im Moment nur aus ein paar versprengten Blogartikeln besteht, habe ich die kostenlose Communitywebseite begonnen, die beim Einstieg in Blazor helfen soll.

 

Web Development Track auf der BASTA!

 

Wasm als die Zukunft der Webentwicklung?

Meiner Meinung nach ist WebAssembly eine wertvolle Ergänzung in der Webentwicklung. Macht es JavaScript obsolet? Keineswegs. Wie oben gezeigt arbeiten Wasm und JavaScript Hand in Hand. Das Monopol von JavaScript als Programmiersprache im Browser bricht Wasm aber auf jeden Fall auf. Als Entwickler werden wir die Sprachen und Plattformen weiter nutzen können, in denen wir viel Erfahrung und bestehenden Code mitbringen.

Wie sehr sich die Browserentwicklung von JavaScript weg hin zu Wasm verschieben wird, ist schwer abzuschätzen. Das hängt nicht zuletzt davon ab, wie die neue Technologie von uns Entwicklerinnen und Entwicklern angenommen wird. Microsoft hat auf jeden Fall Gefallen an Wasm gefunden. Blazor ist nicht das einzige Wasm-bezogene Projekt, das der Konzern aus Redmond vorantreibt. Auch die Xamarin-Community zeigt schon Prototypen von Xamarin Forms mit Wasm im Browser. Eine spannende Zeit mit ganz neuen Möglichkeiten kommt auf uns zu!


The post Willkommen in der Post-JavaScript-Ära mit Blazor appeared first on BASTA!.

]]>
Keynote | Warum Progressive Web Apps immer wichtiger werden https://basta.net/blog/warum-progressive-web-apps-immer-wichtiger-werden/ Mon, 05 Mar 2018 13:40:47 +0000 https://basta.net/?p=22678 Apps ohne App-Store installieren? Progressive-Web-App-Technologie macht es möglich: das Web nativ auf dem Smartphone, eine Anwendung für alles. Ein heißes und topaktuelles Thema in der Webentwicklung, mit dem sich Manfred Steyer in der Eröffnungskeynote der BASTA! Spring 2018 beschäftigt hat.

The post Keynote | Warum Progressive Web Apps immer wichtiger werden appeared first on BASTA!.

]]>
Das Web wird nativer

Wie wäre es, wenn wir die Vorteile der nativen Anwendungen mit denen von Web-Apps verbinden könnten? Dann bekommen wir Progressive Web Apps. Sie sind eine Fusion der Vorzüge aus beiden Welten. Wenig Aufwand für den Entwickler und gleichzeitig viel Nutzen für den Anwender. Somit stellen PWAs eine ernst zu nehmende Alternative zu klassischen Anwendungen dar und eigenen sich hervorragend für die Cross-Plattform-Entwicklung. PWAs funktionieren ohne Installation und können ganz einfach über den Server deployt und gewartet werden. Sie sind gleichzeitig aber auch offlinefähig, können langsame Datenverbindungen überbrücken und bieten Push-Benachrichtigungen und Gerätezugriff. Eine Kerntechnologie dafür sind Service Workers, die als Hintergrundprozesse im Browser laufen und sich ums Caching von Programmdateien, aber auch abgefragten Daten kümmern.
In der nun online bereitstehenden Keynote vom 20. Februar 2018 erfahren Sie, was sich hinter dem Begriff Progressive Web Apps verbirgt, und warum jede Anwendung eine Progressive Web App sein sollte.

 

 

Lesen Sie mehr zu diesem Thema in unserem Blogartikel “Progressive Web Apps – die Zukunft von morgen schon heute?”

The post Keynote | Warum Progressive Web Apps immer wichtiger werden appeared first on BASTA!.

]]>
Schneller Einstieg in TypeScript https://basta.net/blog/einstieg-in-typescript/ Tue, 30 Jan 2018 15:09:07 +0000 https://basta.net/?p=22462 Die von Microsoft entwickelte Sprache TypeScript ist eine Obermenge von JavaScript und erfreut sich nicht nur unter .NET-Entwicklern großer Beliebtheit. Beispielsweise nutzt Google zum Programmieren seines Frameworks Angular ebenfalls TypeScript. Doch was macht das vom C#-Erfinder Anders Hejlsberg erdachte TypeScript so interessant? Was sind die Vorteile gegenüber klassischem JavaScript, und wie lässt sich bestehender JavaScript-Code zu TypeScript-Code migrieren?

The post Schneller Einstieg in TypeScript appeared first on BASTA!.

]]>

Thomas Claudius Huber beantwortet diese und viele anderen Fragen in seinem “Einstieg in TypeScript” und vermittelt in sieben Kapiteln die Grundlagen für Entwickler, die sich mit JavaScript beschäftigen möchten und dabei nicht auf die vielen Vorzüge, die sie z. B. als C#-Programmierer gewohnt sind, verzichten wollen.

 

JavaScript für Unternehmensanwendungen

Dieser Vorzug von TypeScript macht sich doppelt bemerkbar, wenn man mit JavaScript auch größere Unternehmensanwendungen schreiben will. Zwar war die Idee, als JavaScript 1995 entwickelte wurde, zunächst nur Code auf Webseiten ausführen zu können, um Inhalte dynamisch anzupassen. Wobei man wahrscheinlich von 100 bis 1000 Zeilen Code, die auf so einer Webseite ausgeführt werden, ausgegangen ist. Aber dass damit auch Unternehmensanwendungen mit 100 000 oder mehr Zeilen Code umgesetzt werden können, hat sich vermutlich niemand vorgestellt. Doch das ist heute der Fall.

 

Der JavaScript & HTML5 Track auf der BASTA! Spring 2018

 

Statische Typisierung für echtes JavaScript

Die namensgebende Stärke von TypeScript ist die Einführung der statischen Typisierung für das Variablensystem von JavaScript. Diese statische Typisierung bringt neben Compile-Fehlern weitere Vorteile: Tools wie Visual Studio Code können deutlich bessere Unterstützung als bei reinem JavaScript bieten, weil der Typ zur Entwicklungszeit schon feststeht. Somit gibt es Funktionen wie IntelliSense, Go to Definition und vieles mehr, die das nutzen können. Was die Arbeit am Code erheblich erleichtert.
Neben der statischen Typisierung hat TypeScript ein weiteres sehr wichtiges Merkmal: TypeScript kompiliert zu reinem JavaScript-Code. Das heißt, dass TypeScript lediglich zur Entwicklungszeit wichtig ist. Zur Laufzeit benutzt der Browser klassisches JavaScript, das aus TypeScript kompiliert wurde, und dabei lässt sich die gewünschte Zielversion selbst bestimmen.

 

Mehr zum Thema

Im Videointerview erklärt Thomas Claudius Huber, warum er inzwischen so weit ist, dass er alles, was heute JavaScript ist, mit TypeScript schreiben würde. Neben den Vorzügen der Typisierung und der Möglichkeit, wie gewohnt mit Klassen arbeiten zu können, lässt auch das Tooling rund um TypeScript für ihn kaum Wünsche offen.

Auf der BASTA! finden Sie zahlreiche Sessions, in denen Sie den praktischen Einsatz von TypeScript sehen. Thomas Claudius Huber bietet am Freitag, 23.02. den Workshop “TypeScript-Workshop: Einführung von 0 auf 100 in einem Tag” dazu an. Zudem gibt es von ihm ein entwickler.tutorials-Video zum Thema.

 

The post Schneller Einstieg in TypeScript appeared first on BASTA!.

]]>
SQL Server auf Linux – Der größte Teil bleibt gleich https://basta.net/blog/sql-server-auf-linux-der-groesste-teil-bleibt-gleich/ Wed, 10 Jan 2018 10:07:04 +0000 https://basta.net/?p=22374 SQL Server und C# sind in der aktuell sich stark veränderten Microsoft-Welt feste Größen für .NET-Entwickler. Aber auch hier gibt es Entwicklungen, die den Zeichen der Zeit folgen. SQL Server läuft inzwischen auf Linux, und durch .NET Core stehen neben Windows auch macOS und Linux als Entwicklungs- und Zielplattformen für C# zur Verfügung.

The post SQL Server auf Linux – Der größte Teil bleibt gleich appeared first on BASTA!.

]]>
 

Thorsten Kansy, dotnetconsulting.eu, ist seit vielen Jahren Sprecher und Advisor auf der BASTA!. Seine Schwerpunkte sind SQL Server und C#. Im Interview erklärt er, was sich für Entwickler durch die neue Ausrichtung von Microsoft ändert und wie sich das an SQL Server 2017 zeigt. Dabei bleibt er mit beiden Beinen auf dem Boden und sieht ganz klar, dass sich im Prinzip für das eigentliche Entwickeln nicht viel ändert, sie können auch weiterhin so arbeiten, wie sie es kennen. Aber sie haben mehr Möglichkeiten bekommen, und Microsoft geht auf die Tatsache ein, dass Linux am Servermarkt eine Größe ist und auch Azure eine zunehmend wichtige Plattform wird. Beides spiegelt sich auch in der Arbeitsrealität der Entwickler wider. Auch gilt, dass SQL Server und C# sich weiterentwickeln werden, aber im Kern können Entwickler ihr Know-how nahtlos weiternutzen, die Änderungen liegen im Detail.

 

 

Neu und Alt Hand in Hand

Es muss halt nicht immer gleich TypeScript, CosmosDB oder Node.js sein, auch wenn diese neuen Spieler im Markt viele Vorteile bringen und gute mit bewährten Technologien kombinierbar sind. Die Sessions von Thorsten Kansy und anderen Sprechern der BASTA! zeigen immer wieder, wie sich quasi “traditionelle” und “neue” Technologien und Sprachen verbinden lassen, um die Kontinuität der eigenen Arbeit und Entwicklung aufrecht zu halten. Aber gerade an den traditionellen und beständigen Technologien zeigen sich Veränderungen und Fortschritt am deutlichsten. Vor allem dann, wenn sie Hand in Hand mit schnellen Entwicklungen auf Azure oder im Mobile-Bereich gehen.

Sessions zu C# finden Sie im C# Day und dem .NET Framework und C# Track. Zum Thema Datenbanken finden Sie die Sessions „Datenbank Babylon“ von Thorsten Kansy. Und am Montag findet der C#-Workshop von Rainer Stropek statt, der auf die aktuellen Entwicklungen und Möglichkeiten von C# eingeht.

The post SQL Server auf Linux – Der größte Teil bleibt gleich appeared first on BASTA!.

]]>
Progressive Web Apps – die Zukunft von morgen schon heute? https://basta.net/blog/progressive-web-apps-die-zukunft-von-morgen-schon-heute/ Wed, 06 Dec 2017 13:45:22 +0000 https://basta.net/?p=22217 Eine Zukunft ohne App Stores? Kaum vorstellbar, oder? Wie soll die Anwendung installiert werden? Was ist mit Zugriff auf native Features wie Kamera oder Push-Nachrichten? Fragen über Fragen – die Antwort? Progressive Web Apps! Ein seit 2015 durch Google geprägter Begriff, um das Web Stück für Stück nativer zu machen, um mehr und mehr Anwendungen ins Web zu bekommen und das Nutzererlebnis zu verbessern. Hinzu kommen Themen wie Offlinefähigkeit und ein zweiter JavaScript-Thread.

The post Progressive Web Apps – die Zukunft von morgen schon heute? appeared first on BASTA!.

]]>

In diesem Artikel wollen wir eine bestehende kleine Cross-Plattform Angular-Anwendung verwenden und um die Vorteile von Progressive Web Apps anreichern. Die Beispielanwendung nutzt die Star-Wars- und die Poké-APIs, um beispielhaft Master-Detail-Daten anzuzeigen (Abb. 1). Als Buildsystem wird die aktuelle Release Candidate Version 1.6.0-RC.1 von Angular CLI eingesetzt, um Vorteile wie die Ahead-of-Time Compilation oder Tree Shaking zu nutzen. Beides hilft uns, eine in Bezug auf die Dateigröße möglichst kleine und performante Anwendung zu entwickeln. Die bestehende Anwendung inkludiert sowohl das Tool Cordova, sodass sie auch als native Anwendung verpackt und auf mobilen Geräten mit Android oder iOS als echte Smartphone-App deployt werden kann. Zusätzlich hilft Cordova beim Benutzen echter Plattform-APIs, wie z.B. dem Teilen-API, um Inhalte mit unseren Freunden via Facebook, Twitter, WhatsApp und allen anderen Apps, die diese Funktion unterstützen, teilen zu können. Auch inkludiert die bestehende Anwendung das Tool Electron, um eine echte native Desktopanwendung, sprich .exe, .App und Co., zu generieren und auch hier native Funktionen zum Teilen des Inhalts anzusprechen.

https://basta.net/wp-content/uploads/2017/12/rauber_1.png

 

Doch wie können wir zukünftig Apps entwickeln, die ohne weiteres Tooling, wie Cordova oder Electron, auskommen und dennoch auf native Funktionen zugreifen können? Hier kommen die Progressive Web Apps (PWA) ins Spiel. Die vollständige Beispielanwendung für diesen Artikel liegt auf GitHub. Es empfiehlt sich, den Code zum Artikel zu öffnen, da im Artikel nur Ausschnitte gezeigt werden können. Progressive Web App ist ein Begriff, den man in der Webwelt aktuell oft hört und auf den bereits große Firmen wie beispielsweise Twitter, Telegram oder die NASA aufgesprungen sind. Doch um was genau handelt es sich überhaupt?

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Der Blick in die Zukunft

 

Progressive Web Apps beschreibt eine Sammlung von Technologien, Möglichkeiten und Eigenschaften zukünftiger Webanwendungen. PWAs selbst sind keine eigene Technologie, sondern eine seit 2015 von Google getriebene Initiative, die das Web nativer machen soll und dabei natürlich auch Zugriff auf native Gerätefunktionen wie die Kamera gewährt. Im Kern sind PWAs ganz normale Webanwendungen, wie auch unsere Beispielanwendung eine ist – aber auf Steroiden. Die bestehende Webanwendung wird mit weiteren nativen Funktionen angereichert. Um das zu erreichen, legt Google einige Charakteristika fest, die eine Anwendung haben sollte, um als PWA zu gelten. Schauen wir uns im Folgenden die einzelnen Charakteristika genauer an.

  • Progressive bedeutet, dass eine bestehende Webanwendung um weitere Funktionen angereichert, also progressiv verbessert wird. Das bedeutet, dass Benutzer mit modernen Browsern die Webanwendung mit den verbesserten Funktionen nutzen können. Benutzer mit älteren Browsern oder Browsern, die gewisse Standards noch nicht implementiert haben, können die Webanwendung ohne Zusatzfunktionen weiterhin benutzen.
  • Responsive bedeutet, dass die Anwendung sich an jeden Formfaktor, also vom Smartphone über Tablets bis hin zum Desktop, anpasst und dem Benutzer eine für den Formfaktor angepasste User Experience und ein entsprechend angepasstes User Interface bieten kann. Unsere Cross-Plattform-Beispielanwendung ist dank Flexbox bereits vollständig Responsive; daher haben wir diese Charakteristik bereits erfüllt.
  • Connectivity Indepenent bedeutet, dass die Webanwendung auch ohne oder mit schlechter Internetverbindung genutzt werden kann, also offlinefähig ist. Um das zu erreichen, werden wir im Artikel den Service Worker kennenlernen – einen zweiten JavaScript-Thread.
  • Die Charakteristik App-like beschreibt, dass eine Anwendung sich wie eine native Anwendung anfühlen und verhalten soll. Single-Page Applications (SPA) unterstützen diese Charakteristik sehr gut, da eine SPA auf dem Endgerät ausgeführt wird. Das hilft bei der Navigation, da nur bestimmte Seiteninhalte ausgetauscht werden und dies animiert werden kann – wie eben in der nativen Anwendung. Da unsere Beispielanwendung eine SPA ist und ein clientseitiges Routing durchführt, erfüllen wir diese Charakteristik ebenfalls.
  • Fresh beschreibt, dass eine Anwendung immer aktuell ist. Das spiegelt sich zum einen in den Daten der Anwendung wider. Zum anderen ist auch die Anwendung selbst gemeint; sie wird beispielsweise durch ein Hintergrundanwendungsupdate aktualisiert, das über den bereits angesprochenen Service Worker erledigt werden kann.
  • Safe beschreibt, dass eine Anwendung nur über HTTPS – mit Ausnahme von localhost zur Entwicklung – zugänglich ist. Das soll gewährleisten, dass niemand die Daten auf Transportebene abgreifen oder gar verändern kann.
  • Unter der Charakteristik Discoverable versteht man das Auffinden von PWAs via Suchmaschinen, beispielsweise Google oder Bing. Das wiederum bedeutet, dass PWAs nicht extra in einen App Store veröffentlicht werden müssen, sondern eine Standardsuchmaschine die Anwendungen finden und indizieren kann.
  • Die Charakteristik Re-engagable beschreibt das aktive Zurückholen des Benutzers zur Anwendung. Typisches Beispiel ist hier die Nutzung von Push-Notifikationen, wie man sie von Facebook, WhatsApp oder Twitter kennt, sodass der Benutzer sich erneut mit der Anwendung beschäftigt.
  • Installable beschreibt, dass Benutzer für sie interessante Anwendungen ohne einen App Store installieren können. Das bedeutet für den Benutzer, dass er die Webanwendung, die er gerade benutzt, direkt aus dem Web heraus installieren kann, ohne dass sie erneut im App Store gesucht werden muss.
  • PWAs sollen Linkable sein, also einfach via URL geteilt werden können, ohne dass eine Installation der Anwendung erforderlich ist. Da unsere Beispielanwendung vollständig verlinkbar ist, haben wir diese Charakteristik erfüllt.

 

Wie man sieht, beschreiben die Charakteristika hauptsächlich nichtfunktionale Anforderungen, die durch technische Hilfsmittel umgesetzt werden können. Da wir unsere Beispielanwendung bereits als moderne Webapplikation erstellt haben, haben wir einige Charakteristika ohne weiteres Zutun bereits erfüllt. Prinzipiell benötigt man kein Framework wie Angular oder React, um eine PWA zu erstellen. Allerdings unterstützen SPA-Frameworks generell die Entwicklung von großen Webanwendungen, die um die Charakteristika von PWAs ergänzt werden können. Bevor wir uns im Folgenden mit den weiteren Charakteristika speziell in Bezug auf unsere Angular-Anwendung beschäftigen, werfen wir einen Blick auf den zuvor angesprochenen Service Worker.

 

Der Rechenknecht im Hintergrund

 

Der Service Worker erweitert unsere Anwendung um einen zweiten, im Hintergrund laufenden JavaScript-Thread, mit dem eine bidirektionale Kommunikation möglich ist. Im Hintergrund laufend bedeutet, dass der Service Worker auch aktiv ist, wenn unsere Anwendung nicht läuft. Facebook ist hier ein gutes Beispiel: Auch wenn in keinem Tab Facebook geöffnet ist, werden uns Notifikationen angezeigt. Hier werkelt ein Service Worker im Hintergrund. Es nutzen bereits erstaunlich viele Websites einen Service Worker, um dem Benutzer einen gewissen Komfort zu ermöglichen.

 

Der JavaScript & HTML5 Track auf der BASTA!

 

Generell sind PWAs und die dahinterstehenden Technologien sehr modern, sodass viele Browser noch in der Konzeptions- bzw. Implementierungsphase der Features sind. Allen voran Google Chrome und damit auch Android. Sie haben bisher die weitreichendste Unterstützung der Technologien für PWAs. Ganz hinten steht der neue Internet Explorer: Safari. Safari bietet leider kaum die für PWAs nötigen Technologien an. Einige Features sind im Fünfjahresplan angedacht, aber es existieren noch keine konkreten Implementierungspläne.

Doch welche Aufgaben kann der Service Worker für uns erledigen? Der Service Worker ist speziell darauf ausgelegt, asynchrone Arbeiten durchzuführen. Daher hat er auf synchrone (DOM-)APIs wie z. B. der localStorage keinen Zugriff. Jedoch eignet er sich besonders für Arbeiten wie das Synchronisieren von Daten in einer IndexedDB, das Cachen von Dateien und Daten zur Offlineverwendung, als Request-Proxy zwischen der Anwendung und dem Internet oder – für das Re-engagement – zum Anzeigen von Desktopnachrichten nach Erhalt von Push-Nachrichten. Schauen wir uns einmal an, was das fleißige Kerlchen für unsere Anwendung tun kann.

 

Let’s go: Offline

 

Wir werden im Folgenden unsere Beispielanwendung um zwei Dinge erweitern: Offlinefähigkeit und Push-Benachrichtigungen. Zum Testen der Implementierung empfiehlt sich die Installation von Google Chrome Canary, da dieser, wie eingangs beschrieben, die beste Unterstützung der nötigen Technologien für PWAs bietet. Zudem schauen wir uns die PWAs mit einem starken Fokus auf Angular an. Doch aufgepasst! Wie anfangs erwähnt müssen wir auf aktuelle Betas und Release Candidates von Angular zurückgreifen, um Unterstützung für den Service Worker zu erhalten. Die Funktionen sind daher noch sehr neu und die Unterstützung für uns Entwickler teilweise noch sehr rudimentär. Diese Dinge werden sich in der Zukunft verändern, verbessern und vereinfachen. Cutting Edge eben.

Durch existierendes Tooling werden wir uns eher an der Nutzung der PWA-Technologien orientieren und weniger auf Low-Level-Konzepte eingehen. Hier lohnt es sich, einen Blick in das Mozilla Developer Network zu werfen. Die Links hierfür sind am Ende des Artikels zu finden.

Da das Thema Offline selbst einen weiteren Artikel füllen würde, wollen wir es auf recht einfachem Wege implementieren: Wir werden die gesamte Anwendung sowie die Antworten von API-Anfragen im Cache ablegen. Damit erreichen wir, dass unsere Anwendung im Browser auch ohne Internetverbindung funktioniert und ein kleiner Teil an Daten zur Verfügung steht. Den Service Worker werden wir hierbei als Proxy benutzen, der alle HTTPS-Anfragen der Anwendung entgegennimmt und aus dem Browser Cache beantwortet, sofern die Daten dort bereits vorhanden sind. Falls nicht, wird er die Daten aus dem Internet laden und deren Antwort im Cache ablegen. Dann mal los! Zu Beginn müssen wir ein neues Paket installieren, um Angular mit dem Service Worker bekanntzumachen: npm i @angular/service-worker@next.
Zusätzlich müssen wir die Datei .angular-cli.json editieren und dem Abschnitt apps folgende Eigenschaft hinzufügen:

"apps": {
  // ...
  "serviceWorker": true
}

Die Eigenschaft serviceWorker schaltet die Service-Worker-Unterstützung des Angular-CLIs an, das uns eine Menge Arbeit abnehmen wird. Welche genau, werden wir gleich im Anschluss sehen. Im Ordner src legen wir eine Datei mit dem Namen manifest.json an. Der Inhalt ist Listing 1 zu entnehmen.

Listing 1: Inhalt der Datei src/manifest.json

{
  "short_name": "WinDev App",
  "name": "Windows Developer PWA Sample",
  "icons": [
    {
      "src": "assets/launcher-icon-3x.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "assets/icon-1024px-square.png",
      "sizes": "1024x1024",
      "type": "image/png"
    }
  ],
  "theme_color": "#ff584f",
  "background_color": "#ffffff",
  "start_url": "/index.html",
  "display": "standalone"
}

 

Generell benötigt jede PWA eine manifest.json-Datei. In ihr sind Metadaten über die Anwendung enthalten. Schauen wir uns einmal genauer an, welche Inhalte in dieser Datei zu finden sind:

  • short_name: Kurzname der Anwendung; sollte nicht länger als zwölf Zeichen sein, um ein Abschneiden des Anwendungsnamens auf dem Homescreen des Smartphones zu vermeiden.
  • name: Langname der Anwendung.
  • icons: Array von beliebig vielen Icons, bestehend aus Quelldatei, Größe und Typ.
  • theme_color: Hauptfarbe der Anwendung. Bestimmt bei manchen Betriebssystemen, wie die Anwendung dargestellt wird. Bspw. wird hierdurch die Farbe im Android Task Switcher geändert.
  • background_color: Anweisung für den Browser, mit welcher Hintergrundfarbe die App gezeichnet werden soll, bevor etwaige CSS-Dateien geladen wurden.
  • start_url: Startseite der Anwendung, wenn sie auf einem Gerät (d. h. in installierter Variante) gestartet wird.
  • display: Gibt an, wie die Anwendung gestartet werden soll. Mögliche Werte sind fullscreen, standalone, minimal-ui und browser. Von links nach rechts werden mehr und mehr Elemente vom Betriebssystem/Browser bei der laufenden Applikation angezeigt.

 

Neben der manifest.json-Datei, die jede PWA benötigt, wird speziell für Angular noch eine weitere Datei zur Instrumentierung des Angular Service Workers benötigt. Diese Datei legen wir ebenfalls im Ordner src mit dem Namen ngsw-config.json an. Der Inhalt der Datei ist in Listing 2 zu finden. Während des Build-Prozesses wird diese Datei in eine Datei mit dem Namen ngsw.json überführt und im Build-Ordner abgelegt. Diese dient dann zur eigentlichen Instrumentierung des Angular Service Workers.

 

Listing 2: Inhalt der Datei src/ngsw-config.json

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html"
        ],
        "versionedFiles": [
          "/*.bundle.css",
          "/*.bundle.js",
          "/*.chunk.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**"
        ]
      }
    }
  ],
  "dataGroups": [
    {
      "name": "pokemon",
      "urls": [
        "https://pokemon.co/*"
      ],
      "cacheConfig": {
        "strategy": "performance"
      }
    }
  ]
}

 

Schauen wir uns das Cache-API des Browsers genauer an, da wir es zur Implementierung der Offlinefähigkeit benutzen werden. Generell kann Angular bzw. der Angular Service Worker den Cache auf zwei Arten füllen: Mit statischem und dynamischem Inhalt.
Der statische Inhalt kann über die assetGroups (siehe Listing 2) bestimmt werden. Die Eigenschaft assetGroups ist ein Array aus Konfigurationsobjekten, welche wir uns im Folgenden genauer anschauen wollen:

  • name: Freiwählbarer Name für die aktuelle Konfiguration. Praktisch zum Gruppieren verschieden gecachter Inhalte der Anwendung. Der Name ist auch Teil des Cachenamens im Browser, was das Debugging vereinfacht.
  • installMode: Gibt an, ob der Cache bei der Installation des Service Workers direkt befüllt werden soll (Modus prefetch) oder erst bei Zugriff auf die jeweilige Datei (Modus lazy). Es empfiehlt sich alle Dateien, die zum Start der Anwendung benötigt werden, via prefetch zu laden, sodass nach dem ersten Laden die Anwendung offline zur Verfügung steht.
  • updateMode: Gibt an, wie der Cache aktualisiert werden soll, wenn für eine Datei eine neue Version zur Verfügung steht. Auch hier sind die Werte prefetch und lazy möglich. prefetch gibt an, dass immer die neueste Version der Datei in den Cache geladen wird, sobald diese zur Verfügung steht. Bei URL-basierten Cacheeinträgen (siehe nächste Konfiguration resources) erfolgt immer ein Update. Bei Hash-basierten Dateien erfolgt ein Update bei Änderung des Dateihashes. Bei lazy wird die Prüfung auf eine neue Version erst dann gemacht, wenn auch ein Zugriff auf die Datei im Cache erfolgt. Der eigentliche Update-Zyklus ist dann wie im Modus prefetch.
  • resources: Diese Konfiguration gibt explizit an, welche Dateien in den Cache geladen werden soll. Es stellt ein Objekt mit drei verschiedenen Eigenschaften dar:
    • files: Eine Liste von Dateien, die in den Cache geladen werden soll. Diese Dateien müssen im Build-Verzeichnis liegen (also dort, wo auch später die ngsw.json abgelegt wird). Für alle Dateien innerhalb dieser Liste wird ein Dateihash gebildet, der für ein einfaches Erkennen sorgt, wenn die Datei auf eine neue Version geprüft werden soll. Die Einträge innerhalb dieser Liste können auch via Globs angegeben werden.
    • versionedFiles: Eine Liste von Dateien, die bereits einen Hash im Dateinamen haben, wie beispielsweise alle durch das Angular CLI erstellten. Diese müssen nicht zusätzlich gehasht werden, da bei einer Änderung sich der Name der Datei ebenfalls ändert.
    • urls: Liste von externen URLs, die zusätzlich gecacht werden sollen. Bspw. Google-Fonts-Dateien. Für diese kann kein Dateihash berechnet werden, daher werden diese Dateien immer aktualisiert, sobald die Konfiguration sich ändert. Daher sind die anderen zwei Möglichkeiten zu bevorzugen.

 

Je nach Einstellung wird der Service Worker beim Start der Anwendung oder wenn der Cache nicht gefüllt ist, selbstständig alle definierten Dateien vom Server laden und im Cache ablegen. Ab diesem Moment ist die Anwendung – ohne Daten – bereits offline verfügbar. Über die Dev Tools von Chrome kann im Tab Network der Browser auf offline geschaltet werden. Wird die Anwendung per F5 aktualisiert, wird die Anwendung dennoch geladen, da sie vollständig im Cache des Browsers liegt.

Möchten wir die Antworten unserer API-Anfragen an das Star Wars- oder Pokémon-API im Cache ablegen, müssen wir einen Blick auf das Array dataGroups werfen. Die Konfiguration unterscheidet sich etwas zu den assetGroups:

  • name: Freiwählbarer Name, wie den den assetGroups
  • urls: Eine Liste von URLs, deren Antwort gecacht werden soll. Globs werden unterstützt.
  • version: Möglichkeit, die Daten zu versionieren, was bei Migration oder Anwendungsupdates nützlich sein kann.
  • cacheConfig: Objekt zum Bestimmen des Cachingsverhalten einer einzelnen Konfiguration.
    • maxSize: Anzahl, wie viele Requests innerhalb der Gruppe gecacht werden soll (bspw. bei Wildcards in der URL)
    • maxAge: Angabe, wie lange die Antwort im Cache als gültig angesehen wird. Mögliche Werte sind string-basierte Zeitangaben wie 1m, 4h, 10d (1 Minute, 4 Stunden, 10 Tage).
    • strategy: Für welche Art der Verwendung der Cache optimiert werden soll. Es kann aktuell zwischen freshness und performance gewählt werden. Bei freshness wird immer versucht, die Daten vom Server zu laden und bei erfolgreichem Fall im Cache abzulegen. Nur im Offlinefall wird direkt auf den Cache zugegriffen. freshness implementiert also ein Network First-Verhalten. performance implementiert ein Cache First-Verhalten und lädt die Dateien immer aus dem Cache und nur dann vom Server, wenn sie noch nicht im Cache liegen.
    • timeout: Wird nur genutzt, wenn die strategy auf freshness gestellt ist und bestimmt, nach welcher Zeit der Request in ein Timeout läuft und auf den Cache zurückgegriffen werden soll.

 

Zum Zeitpunkt des Schreibens des Artikels existiert für dieses Feature noch keine schriftliche Dokumentation, daher lohnt sich ein Blick in das GitHub-Repository. Innerhalb unseres Beispiels cachen wir nur das Pokémon-API. Als kleine Übung kann der Cache für das Star-Wars-API eingefügt werden.

Als vorletzter Schritt muss in der index.html-Datei im <head> folgender Eintrag hinzugefügt werden, sodass der Browser weiß, unter welchem Namen er das PWA-Manifest findet: <link rel=”manifest” href=”manifest.json”>. Zu guter Letzt müssen wir in unserer module.ts noch das Module ServiceWorkerModule den imports hinzufügen und zwar mit folgendem Ausdruck:

 

environment.production ? ServiceWorkerModule.register('ngsw-worker.js') : [],

 

Der Service Worker kann aktuell nur in Angulars Produktionsmodus gestartet werden, da nur hier die angegebene Datei ngsw-worker.js generiert wird. Das erschwert aktuell das Debugging, wird aber mit der Weiterentwicklung des Toolings besser werden. Zudem muss die Anwendung zwingend gebaut und nicht via ng serve gestartet werden. Das heißt, die Anwendung muss via ng build –prod gebaut und über einen Webserver (z.B. node-static oder http-server) gestartet werden. Nach dem Start kann etwas durch die Anwendung geklickt werden. Über die Chrome Dev Tools kann der Browser in den Offlinemodus (Tab Networking) geschaltet werden. Auch nach einem Reload des Browsers wird die Anwendung angezeigt. Zuvor angezeigte Daten sind auch im Offlinemodus verfügbar. So haben wir bereits die Möglichkeit, unsere Anwendung in Teilen offline zunehmen.

Hinweis: Das Angular-CLI bzw. der Angular Service Worker abstrahieren sehr viele Low-Level-APIs. Was so einfach aussah, abstrahiert Themen wie den Service Worker Lifecycle, das Service-Worker-API, das Cache-API, Offlinestrategien oder das Fetch-API. Es gibt also noch viel zu entdecken!

 

Ich hab’ da was für dich: Push-Nachrichten

Moderne Anwendungen kommen kaum noch ohne aus, und der Benutzer verlässt sich darauf, bei neuen Ereignissen in seiner Anwendung eine Notification zu erhalten. Auch wir wollen die Möglichkeit haben, Push-Nachrichten zu empfangen und eine Notification anzuzeigen. Hier ist auch wichtig, dass eine Push-Nachricht nicht automatisch etwas mit einer dem Benutzer angezeigten Notification zu tun hat. Push-Nachrichten sind generell die Möglichkeit, einer Anwendung eine Nachricht zukommen zu lassen. Die Anwendung hat daraufhin die Möglichkeit, diese Push-Nachricht als Notification dem Benutzer anzuzeigen. Prinzipiell könnte stattdessen auch nur ein Datensatz aktualisiert oder ein interner Prozess angestoßen werden. Was genau gemacht werden soll, wird durch die Payload einer Notification bestimmt.

Zur Umsetzung von Push-Nachrichten mit Notification werden wir uns zweier APIs bedienen: des Push-API und des Notification-API. Beide APIs werden durch den Angular Service Worker abstrahiert, sodass wir nicht direkt mit ihnen in Berührung kommen, sie aber grundlegend für die Funktion verantwortlich sind. Um Push-Nachrichten anzubieten, werden zwei Dinge benötigt:

  1. Implementierung eines App-Servers zum Verwalten von Push-Subskriptionen und Versenden von Push-Nachrichten.
  2. Implementierung innerhalb der Clientanwendung.

Erste Implementierungen von Push in Google Chrome haben einen Dienst von Google Cloud Messaging (GCM) bzw. Firebase Cloud Messaging (FCM) benötigt. Leider hat es sich hierbei um ein propritäres Protokoll gehandelt, welches nur für Chrome-basierte Browser zur Verfügung stand. Wollte man Push-Nachrichten bspw. für Firefox implementieren, musste auf den Mozilla Push Service (MPS) zurückgegriffen werden. Für uns Entwickler bedeutet dies, dass wir für beide Services unsere Anwendung registrieren mussten, um Push-Nachrichten zu versenden.
Mittlerweile existiert ein neues, standardisiertes, Protokoll mit dem Namen Web Push, welches von Firefox und Chrome implementiert, von FCM und MPS unterstützt werden und der zukünftige Weg ist, Push-Nachrichten an die Browser zu schicken. Safari und Edge hinken hier leider noch etwas hinterher.

Durch das Web Push Protokoll müssen wir uns nicht mehr für jeden Dienst selbst registrieren, sondern können dies über den neuen Standard VAPID (Voluntary Application Server Identification) erledigen. Für VAPID wird auf unserem App-Server, der die Push-Subskriptionen verwaltet und die Push-Nachrichten verschicken kann, ein Schlüsselpaar bestehend aus Public- und Private-Key erzeugt. Möchte ein Browser sich für Push registrieren, benötigt er den Public-Key des App-Servers. Durch den korrekten Public-Key kann der App-Server eine Verbindung mit FCM oder MPS aufbauen, die nach wie vor für den eigentlichen Versand der Push-Nachricht benötigt werden. Durch VAPID identifizieren wir unseren App-Server bei FCM und MPS, weswegen die Registrierung bei den Diensten entfällt.

Da die Implementierung des App-Servers den Rahmen des Artikels verlässt, liegt eine Beispielimplementierung eines App-Servers im Ordner srcApi bereit, der über npm run push-server gestartet werden kann. Das benötigte Schlüsselpaar liegt exemplarisch bei, kann aber bspw. über eine Website wie den Push Companion oder das Node.js-Modul crypto selbst erzeugt werden.

Der App-Server stellt nach dem Start zwei Web-APIs bereit. Das erste API dient zur Registrierung von Clientanwendungen, sodass der App-Server weiß, an welche Clients er Push-Nachrichten übermitteln kann. Das zweite API ist ein kleines Debug-API, mit dem wir den Versand von Push-Nachrichten an alle subskribierten Clients anstoßen können.

Der dritte Schritt ist das Implementieren von Push-Nachrichten in unserer Anwendung selbst. Dazu ändern wir den Code der root.ts ab, wie in Listing 3 gezeigt.

Listing 3: Ergänzung der Root-Komponente

export class RootComponent implements OnInit {
  constructor(@Optional() private _swPush: SwPush, private _http: HttpClient) {}

  public ngOnInit(): void {
    this.register();
  }

  public register(): void {
    if (!this._swPush) {
      return;
    }

    const push = this._swPush.requestSubscription({serverPublicKey:''});
    Observable.fromPromise(push)
      .pipe(
        switchMap(subscription =>
          this._http.post('http://localhost:9090/push/register', subscription))
        )
        .subscribe();
  }
}

Zum einen lassen wir uns eine Instanz von SwPush und HttpClient geben. SwPush ist ein Angular Service, der uns Zugriff auf die Push-Funktionalität des Angular Service Workers gewährt. Zum anderen implementieren wir die Methode register(), um via SwPush die Methode requestSubscription() aufzurufen, um den Service Worker am Browser für Push-Nachrichten zu registrieren. Hier wird der zur für VAPID erzeugte Public-Key benötigt. Ist die Registrierung am Browser erfolgreich, erhalten wir Informationen über die Subskription subscription, die wir via http.post() an unser eigenes API schicken. Wie dem Beispiel zu entnehmen ist, läuft das API auf localhost:9090, d. h. dass nur Anwendungen mit Zugriff auf localhost sich für den Empfang von Push-Nachrichten registrieren können. Für ein echtes Szenario muss der App-Server online gehostet werden, sodass er durch die mobilen Apps erreicht werden kann. Und natürlich sollte der Beispielcode in einen schönen, eigenen Angular Service abstrahiert werden.

Ein kleines Manko hat der Angular Service allerdings noch: Die Anwendung hat noch keine Möglichkeit, auf die Push Notifications selbst zu reagieren. Die dahinterliegenden Low-Level-APIs bieten bereits die Möglichkeit, auf den Klick einer Notification zu reagieren oder Daten aus Notifications auszulesen. Diese Möglichkeit wird in einer künftigen Version des Angular Service zur Verfügung stehen. Benötigt man diese Funktionen, muss man selbst die Low-Level-APIs implementieren.

Nach dem Start des App-Servers kann die Anwendung gestartet werden. Via Postman kann ein HTTP POST auf localhost:9090/push/notifyAll abgesetzt werden, was dazu führt, dass Chrome eine Desktop-Notification anzeigen wird. Mit diesen wenigen Schritten haben wir Push Notifications implementiert!

 

Der Wegweiser: Lighthouse

Google bietet das Tool Lighthouse an, das unsere Anwendung unter anderem auf die eingangs genannten Eigenschaften von PWAs prüft und auch Vorschläge gibt, wie sie verbessert werden können. Das Tool kann als Chrome Extension installiert werden und wird per einfachem Klick auf das Lighthouse-Icon gestartet. Die Prüfung ist vollautomatisch und dauert wenige Sekunden. Lighthouse generiert einen Report, der in Abbildung 2 gezeigt wird. Das Scoring von 100/100 Punkten für PWAs zeigt, dass wir sehr gute Arbeit geleistet haben. Einen Teil der Arbeit hatten wir im Vorfeld bereits erledigt, wie Responsive, und einen weiteren Teil der Arbeit mit dem Artikel durch die Integration des Service Workers und das Anbieten einer Manifest-Datei.

https://basta.net/wp-content/uploads/2017/12/rauber_2.png

 

Fazit

Mit Hilfe des Artikels haben wir eine bestehende Angular-Cross-Plattform-Anwendung um weitere Technologien ergänzt, um die Anwendung in Richtung Progressive Web App weiterzuentwickeln. Die Single-Codebase stellt hierbei nicht nur eine Webanwendung bereit, sondern kann, wie eingangs angesprochen, in echte native mobile Apps und echte native Desktopanwendungen verpackt werden, die jeweils auf native APIs zugreifen können, um dem Benutzer eine bestmögliche Integration zu bieten. Mit ein paar wenigen Zeilen konnten wir in diesem Artikel unsere Anwendung um PWA-Technologien erweitern. Via Service Worker haben wir eine einfache Möglichkeit, um die Anwendung mit einfacher Offlinefunktionalität auszustatten und auf Push-Nachrichten reagieren zu können. Dank der Manifest-Datei ist unsere App auch installierbar geworden, was auf Android-Geräten durch ein kleines Banner angezeigt wird (Abb. 3), und das ganz ohne einen App Store! Lighthouse hat uns gezeigt, dass wir ein sehr gutes PWA-Scoring erhalten, somit steht unserer modernen Anwendung kaum noch was im Wege. Unter der Haube bieten die Low-Level-APIs noch viel mehr, schließlich haben wir mit diesem Artikel nur an der Oberfläche gekratzt.

https://basta.net/wp-content/uploads/2017/12/rauber_3-1.png

Angular und PWAs auf der BASTA!:

Passend zum Thema dieses Artikels finden Sie auf der BASTA! z. B. die Session „Progressive Web Apps mit Angular: das Web wird nativ(er)“ von Christian Liebel und viele weiter im HTML5 & JavaScript Track sowie den Power Workshops am Montag und Freitag.

 

The post Progressive Web Apps – die Zukunft von morgen schon heute? appeared first on BASTA!.

]]>
Unser BASTA! Gewinnspiel zum Nikolaustag! https://basta.net/blog/nikolaus-gewinnspiel/ Wed, 06 Dec 2017 06:58:59 +0000 https://basta.net/?p=22192 Heute schon in den Stiefel geschaut? Nichts gefunden? Dann könnte es sich für Sie heute noch lohnen. Denn der BASTA! Nikolaus hat großartige Bücher zur Verlosung bereitgelegt!

The post Unser BASTA! Gewinnspiel zum Nikolaustag! appeared first on BASTA!.

]]>
 

 

Passend zum heutigen Tag verlosen wir 3 Buchexemplare „TFS Jumpstart – Per Express zum Application Lifecycle Management“.

 

Sie möchten sich in den Bereich ALM mit dem Team Foundation Server einarbeiten, bisher aber noch keine oder nur wenig Erfahrung mit Quellcodeverwaltung und Co.? Dann haben Sie jetzt die Gelegenheit, ein Exemplar noch heute zu gewinnen. Das Buch soll eine einfache Starthilfe auf dem Weg zum gelebten Application Lifecycle Management sein.

Bereits seit Anfang 2013 kann man auf verschiedenen Wegen an eine kostenlose Ausgabe von Microsofts ALM-Plattform, dem Team Foundation Server (TFS), gelangen: von der On-Premise-Variante TFS Express bis hin zur Nutzung der Visual Studio Team Services in der Cloud. In den letzten Jahren hat sich die Welt rund um den TFS weiterentwickelt, und viele dieser Entwicklungen sind auch am TFS nicht spurlos vorübergegangen. Ein namhaftes Beispiel hierfür ist eine vollständige Integration von Git sowie ein komplett überarbeitetes Build-System, das sowohl per PowerShell-Skript als auch unter Node.js lauffähig ist. Der Trend zu mehr Offenheit bei Microsoft macht also auch vor deren ALM-Plattform zum Glück nicht halt. Es ist heute also einfacher denn je, sich mit dem Team Foundation Server zu befassen und die Mächtigkeit dieser ALM-Plattform für sich zu nutzen.

 

Bitte tragen Sie Ihre Email-Adresse ein, an die Sie den BASTA!-Newsletter erhalten möchten, und gewinnen Sie mit etwas Glück eins von drei Buchexemplaren „TFS Jumpstart“.

 

 

P.S. Auch treue BASTA-Newsletterabonnenten kommen in den Lostopf, wenn sie sich hier nochmal eintragen!

The post Unser BASTA! Gewinnspiel zum Nikolaustag! appeared first on BASTA!.

]]>
Keynote | Microservices – zu klein und zu gut, um nur ein Hype zu sein https://basta.net/blog/microservices-zu-klein-und-zu-gut-um-nur-ein-hype-zu-sein/ Wed, 01 Nov 2017 07:15:41 +0000 https://basta.net/?p=21944 Microservices gehören im IT-Buzzword-Bingo zu den Neuzugängen der letzten Jahre, und sie beweisen dabei eine ihrer Qualitäten: sie stehen nicht für eins, sondern vieles, manchmal schon fast für alles. Microservices sind ein fundamentaler Wandel. Sie haben eine strategische Komponente, die auf die Architektur, den Programmierstil, die Arbeitskultur, das Geschäftsmodell und die Technologie Auswirkungen hat.

The post Keynote | Microservices – zu klein und zu gut, um nur ein Hype zu sein appeared first on BASTA!.

]]>
 

In der Keynote zur Eröffnung der BASTA! 2017 haben Oliver Sturm, DevExpress, Rainer Stropek, software architects, Christian Weyer, Thinktecture AG und Mirko Schrempp, BASTA! Program Chair, diese vielen Facetten von Microservices diskutiert, um den Blick für die Möglichkeiten und Chancen zu öffnen.

 

 

Microservices sind ein Buzzword, denn DevOps, Container, Cloud, Plattform- und Sprachunabhängigkeit, Resilienz, Austauschbarkeit, Agilität, Serverless, bimodale IT, Continuous Delivery, APIs fallen gleich mit aus der Bingotrommel. Der Titel der Keynote “Microservices – zu klein und zu gut, um nur ein Hype zu sein” deutet es schon an, denn das Fragezeichen fehlt aus gutem Grund: Microservices sind kein Hype! Die Keynote räumt mit den üblichen Missverständnissen rund um Microservices auf, skizziert typische Architekturpatterns, Technologieoptionen, Auswirkungen auf Organisationen und den Businessbezug.

Der Microservices & APIs Track auf der BASTA! Spring 2018

 

Neue Strukturen auf allen Ebenen

 

Microservices treiben den Trend zur Modularisierung und Flexibilität auf die Spitze, denn für Microservices kauft man keinen kompletten Technologiestack, den man auspackt, auf den Server setzt und dann läuft alles. Microservices bilden ihre Form und Struktur über kleinteilige Dienste, die austauschbar sein sollten und in den Sprachen geschrieben werden können, die einem Team zur Verfügung stehen. Darin waren sich alle Keynote-Sprecher einig, ob nun C#, Java, Python, JavaScript oder irgendeine andere Sprache, jede ist dafür geeignet für einen Microservice genutzt zu werden. Rainer Stropek ist dabei der Auffassung, dass dies nicht nur ein technischer Vorteil ist, sondern angesichts des Arbeitsmarkts in der IT auch ein wirtschaftlicher. Wer für ein Projekt keine C#-Entwickler auf dem Markt findet, kann dann eben mit einem Python-Team das Ziel erreichen. Spätestens hier sieht man, dass Microservices nicht nur auf die Struktur von Softwarelösungen und die Technologiewahl Einfluss haben, sondern auch auf die Organisation von Teams und sogar Unternehmen. Teams werden in die Lage versetzt autonome Entscheidungen darüber zu fällen, welche Technologie eingesetzt wird, um das gewünschte Ziel zu erreichen. Dazu muss allerdings das Management Teile seiner Kompetenz abgeben und auf das Team übertragen.

 

Der Monolith ist nicht am Ende

 

Damit einzelne oder unzählige Microservices zusammenspielen können, ist von zentraler Bedeutung, dass Architekturüberlegungen immer mitspielen. Und das muss auf allen Eben passieren, denn für sich alleine hat ein Microservice nur begrenzten Wert. Erst im Zusammenspiel werden komplexere Geschäftsprobleme gelöst. Allerdings gibt es dabei keinen Zentralismus, denn dieser ist nicht schnell genug, das haben spätestens alle SOA-Experimente gezeigt. Microservices erlauben durch ihre dezentrale Struktur, unterschiedliche Innovationsgeschwindigkeiten in einer Technologiewelt, die sich immer schneller dreht. Sie sind damit aber nicht automatisch das Ende der Softwaremonolithen. Im Gegenteil, diese werden für die Zukunft anschlussfähig, und spätestens hier kommt die Cloud ins Spiel.

 

Moderne Cloud-Lösungen sind keine Monolithen mehr, in denen zwar alles harmonisch zusammenpasst, die man aber nur ganz oder gar nicht weiterentwickeln kann. Was, wenn sich ein Teil einer Softwarelösung schneller verändert als andere Teile? Wie soll ein Team rascher auf .NET Core umsteigen können, wenn die anderen aus irgendeinem Grund noch für einige Zeit im .NET Framework festhängen? Damit solche Probleme gelöst werden können, braucht man kleiner strukturierte Softwarearchitekturen. Microservices sind das Architekturmuster dafür. Viele relativ kleine Logikbausteine (z. B. Rechenlogiken, Workflows, Services für Datenzugriff etc.) werden von autonomen Teams entwickelt und betrieben. Die Microservices tauschen Daten und Events über plattformunabhängige RESTful Web-APIs, WebHooks oder über eine Messaging-Infrastruktur aus. Genau hier sieht Christian Weyer die Chance, für ISVs ihre Zukunft zu sichern, vor allem, wenn sie mit Start-ups in Konkurrenz stehen, die genau eine Sache gut oder besser können, die für den ISV wichtig ist. Sie können ihre Monolithen fachlich aufteilen, ggf. erst über APIs, und dann sauber aufspalten und in kleinere Bereiche einteilen und zur Verfügung stellen.

Dabei ist dann nicht wichtig, ob mit AWS, Azure, Docker oder sogar Serverless gearbeitet wird. Wichtiger ist, dass die gekapselten und isolierten Dienste sauber und sicher kommunizieren. Was das heißt, zeigt das Serverless-Konzept sehr deutlich: Die Idee von Serverless Computing ist es, Dienste anzubieten, ohne einen einzigen Gedanken an Maschinen, Container, Betriebssysteme, Softwareversionen oder Skalierung der Instanzen – horizontal wie vertikal – zu verschwenden. Und das ist auch das Versprechen der Plattformbetreiber: „100% managed and operated“. Serverless Computing bietet also die Umgebung, um sich nahezu ausschließlich auf die Implementierung von Businesslogik konzentrieren zu können. Dabei muss diese in sinnvolle kleine und autonome Teile zerlegt werden. Jeder Teil wird als eine separate Funktion zur Verfügung gestellt, ausgeführt und skaliert.

 

Das Große im Kleinen

 

Microservices ist also ein großes Wort, auch wenn es so klein klingt. Wie klein die sein sollen, ist eine Frage, die auch oft und gern gestellt wird und auf die es keine pauschale Antwort gibt. Microservices setzen auf Unabhängigkeit zwischen den Modulen eines Softwaresystems. Die Aufgaben eines Diensts sollen kurz und bündig definierbar sein; meistens denken wir dabei an eine einzelne Funktion mit einem API. Daraus ergeben sich weitreichende Freiheiten: Plattform, Programmiersprache und Hilfs-Libraries können womöglich pro Dienst neu definiert werden. Microservices stellen ein mächtiges und vielschichtiges Pattern dar, von dem beinahe jedes Anwendungssystem profitieren kann. Damit stellen sich der IT und den Unternehmen neue Aufgaben. Der vertraute Monolith wird anschlussfähig an die Zukunft, aber man sollte den Monolithen nicht mehr denken, wenn er nicht nötig ist. Architektur, Organisation und Fachlichkeit greifen Hand in Hand bzw. Dienst in Dienst und führen nicht mehr zu einem mächtigen Produkt aus einem Guss, sondern einem mächtigeren WIE aus einem Guss. Die Keynote zur Eröffnung der BASTA! 2017 zeigt dazu das große Bild, viele Sessions und Workshops der BASTA!-Konferenzen in den nächsten Jahren werden die Details dazu vorstellen.

 

Spannende und Informative Sessions zum Thema finden Sie im Microservices & APIs Track der BASTA!

 

The post Keynote | Microservices – zu klein und zu gut, um nur ein Hype zu sein appeared first on BASTA!.

]]>
Von .NET zu .NET Core 2.0 – Kurze Erinnerung https://basta.net/blog/von-net-zu-net-core-2-0-kurze-erinnerung/ Thu, 14 Sep 2017 11:01:59 +0000 https://basta.net/?p=22033 Mit .NET Core 2.0 bringt Microsoft das .NET Framework auf andere Plattformen, wie z. B. Linux und Mac. Doch es ist ein anderes .NET als jenes, das wir vom Windows-Desktop kennen. .NET Standard 2.0 ist Microsofts jüngster Versuch, API-Konvergenz zu erreichen. Was aber bedeutet das, und welche Auswirkungen hat das auf uns Entwickler?

The post Von .NET zu .NET Core 2.0 – Kurze Erinnerung appeared first on BASTA!.

]]>
Zuerst hieß es .NET Framework 5. Doch dann überraschte uns die Marketingabteilung von Microsoft und gab der neuen quelloffenen und plattformunabhängigen Version den Namen .NET Core. Das macht insofern Sinn, da das neue Framework ein anderes ist als das, was wir seit Anfang der 2000er vom Windows-Desktop her kennen. Unsere klassischen .NET-Anwendungen (WinForms, WPF, ASP.NET etc.) laufen hier nicht. Der Softwarehersteller aus Redmond verpasst dem Framework damit einen Neustart.

Dann ist da noch die Firma Xamarin, die von Microsoft übernommen wurde. Damit befindet sich nun auch Mono in der Obhut von Microsoft. Mono ist eine quelloffene und plattformunabhängige Implementierung des .NET Frameworks, basierend auf den ECMA-Standards für C# und der Common Language Runtime [1]. Gleiches gilt für das Produkt Xamarin. Mit Xamarin lassen sich native Apps für verschiedene Plattformen (Android, iOS, Windows Universal Platform) in C# entwickeln. Unter der Haube setzt Xamarin dabei auf Mono.

Apropos „nativ“: Im Rahmen der Windows-Store-Apps veröffentlichte Microsoft .NET Native [2]. Im Wesentlichen stellt .NET Native einen Ahead-of-Time-(AoT-)Compiler zur Verfügung, der eine .NET-Anwendung (Windows-Store-App) in nativen Code übersetzt. Der Windows Store liefert dann den für die Zielplattform spezifischen nativen Code an den Benutzer aus.

Und als wäre das noch nicht genug, setzte Microsoft noch eins drauf und kündigte 2016 .NET Standard [3] an (damals noch .NET Platform Standard genannt). Nach sieben Versionen steht man hier nun bei der Version 2.0, die kürzlich veröffentlicht wurde.

Es lässt sich nicht abstreiten, dass sich rund um .NET viel tut. Dass man da als Entwickler schnell den Überblick verlieren kann oder sich in einem Zustand der Verwirrung und Orientierungslosigkeit wiederfindet, ist mehr als verständlich.

 

Abb. 1 In der Geschichte von .NET entstanden viele Ausprägungen (Verticals), meist aus einem Fork des großen .NET Frameworks

 

Der .NET Framework & C# Track auf der BASTA! Konferenz

 

Moderne Anforderungen

Schauen wir einmal auf Abb. 1. Wenn wir bisher von Portabilität geredet haben, dann betraf das nur die Microsoft-Windows-Plattformen. Plattformunabhängigkeit ist das noch lange nicht. Im Zeitalter der Cloud stellt sich die Frage, wie es mit anderen Plattformen aussieht: Linux, Mac und Containertechnologien lauten hier die Schlagwörter. Lange hat Amazon mit den Amazon Web Services (AWS) eine Cloud-Plattform etabliert, auf der das .NET Framework keine Rolle spielt. Und lässt man einmal Mono außer Acht, laufen herkömmliche .NET-Anwendungen auch nicht auf Linux-Distributionen oder auf dem Mac. Möchte Microsoft seine Kunden also auf derartige Plattformen bekommen, so muss eine andere, neue Lösung her. Und diese Lösung muss leichtgewichtig sein. Mal ebenso mehrere hundert oder tausend Container innerhalb von ein paar Sekunden hochziehen? Mit einem Windows-OS, vielleicht noch einer MSSQL-Datenbank und dem .NET Framework fühlt es sich an, als würde man versuchen, einen voll geladenen Jumbojet mit eigener Puste in den Himmel zu heben.

 

Dass .NET Core die Zukunft des .NET Frameworks sein muss, liegt aus technischer Sicht auf der Hand.

 

In diesem Kontext spielt auch die Modularität eine entscheidende Rolle. Wurde doch das .NET Framework als ein monolithisches Stück Software konzipiert, das auf dem Zielsystem als Ganzes installiert werden muss. Mittlerweile hat das Framework mehrere hundert Megabyte im Gepäck. Damit ergibt sich ebenfalls eine systemweite Abhängigkeit von Anwendungen zur installierten Version des .NET Frameworks. Erfordert eine Anwendung eines anderen Herstellers eine neuere Version des Frameworks, so kann sich dies auf bereits installierte Applikationen auswirken. Dafür möchte am Ende kein Systemadministrator verantwortlich sein. Wünschenswert wäre es, wenn diese systemweite Abhängigkeit von einer Anwendung zum Framework keine Rolle spielt. Das würde funktionieren, wenn Applikationen alles mitbringen könnten, was sie benötigen (inklusive der Laufzeitumgebung), und wenn diese Abhängigkeiten pro Anwendung installiert werden könnten. Dann wären die Versionen der Laufzeitumgebung und abhängigen Bibliotheken egal. Zudem würde das den Speicherbedarf reduzieren. Denn Anwendungen bringen nur das mit, was sie wirklich benötigen. Dazu gehört mit Sicherheit nicht das mehrere hundert Megabyte große .NET Framework. Wir sprechen hier von einem applikationslokalen Framework. Ein Monolith als maschinenweites Framework erfüllt diese Anforderung natürlich nicht.

Mit einem Windows-OS, vielleicht noch einer MSSQL-Datenbank und dem .NET Framework fühlt es sich an, als würde man versuchen, einen voll geladenen Jumbojet mit eigener Puste in den Himmel zu heben.

 

.NET Core im richtigen Licht

Der kritische Leser mag nun behaupten, dass .NET Core auch nichts anderes sei, als ein weiteres Vertical – ein weiterer .NET Stack. Zu Recht, denn genau das ist .NET Core. Dennoch: Laut Microsoft ist .NET Core ein Neustart des .NET Frameworks. Von Grund auf unter anderen Voraussetzungen als das ursprüngliche, monolithische .NET Framework konzipiert – basierend auf Anforderungen moderner Softwareanwendungen. Für Microsoft ist .NET Core der .NET-Stack der Zukunft und soll als Fundament für alle kommenden Plattformen und Hardwarearchitekturen dienen. Funktioniert das auf irgendeine Art und Weise zukünftig in der Praxis nicht, dann ist es wirklich nur ein weiterer .NET-Stack, den wir ggf. mit unseren Anwendungen bedienen müssen. Das ist aber eher unwahrscheinlich angesichts dessen, was gerade rund um .NET Core geschieht. Nicht ohne Grund presst Microsoft APIs mit Nachdruck in die neuen Versionen von .NET Core und forciert damit die API-Konvergenz – zum großen .NET Framework, aber auch zu anderen Verticals. Doch dazu später mehr.

Dass .NET Core die Zukunft des .NET Frameworks sein muss, liegt aus technischer Sicht auf der Hand: es löst die besagten Probleme der Vergangenheit und erfüllt damit die Anforderungen, die moderne Anwendungen für Web, Cloud, IoT, mobile Geräte etc., an ein solches Framework stellen.

The post Von .NET zu .NET Core 2.0 – Kurze Erinnerung appeared first on BASTA!.

]]>
Die serverlose (R)evolution https://basta.net/blog/die-serverlose-revolution/ Thu, 31 Aug 2017 08:50:22 +0000 https://basta.net/?p=22004 Cloud Computing ist in aller Munde und hat sich für die meisten Leute, die sich in der IT-Branche bewegen, bereits zu einem stehenden Begriff etabliert. Doch was genau verbirgt sich eigentlich hinter dem so schön klingenden Terminus? Was macht die Wolke aus und was kann sie leisten? Ist die Frage nach dem Unterschied von IaaS, PaaS und FaaS eigentlich schon geklärt, und was hat das alles mit Serverless Computing zu tun?

The post Die serverlose (R)evolution appeared first on BASTA!.

]]>
Die Anfangszeit oder „Prä-Cloud Hosting“

Alles begann Anfang der 90er Jahre mit der Entwicklung des Internets. Spätestens 1993 war der kommerzielle Durchbruch geschafft, was größtenteils mit der Veröffentlichung des Webbrowsers „Mosaik“ einherging, der die Inhalte des WWW erstmals grafisch darstellen konnte. Ab sofort war eine Präsenz im Web für das eigene Geschäft in der beginnenden digitalen Welt ein Muss. Nach und nach wurde das Angebot an Webdiensten und Onlineshops zahlreicher und immer vielfältiger. Das Hosting dieser Dienste passierte jedoch auf den eigenen Maschinen, die je nach Größe und nötiger Verfügbarkeit der Dienste entweder unter dem Tisch des Entwicklers im Büro standen oder im klimatisierten Keller als kleine Rechnerfarm lebten. Wartung, Instandhaltung, Deployment, Backupstrategie und Ausfallschutz – all das lag im Aufgabenbereich der Administratoren. Die Risiken und auch die Kosten waren nicht unerheblich, da nebst den Materialkosten auch erweiterte Personalkosten für z. B. Bereitschaftsdienst aufgewendet werden mussten (Abb. 1).

 

 

target=

Abb.1 Die (R)evolution der Cloud

 

 

IaaS oder „Der Startschuss in die Cloud“

Die Schmerzen, die durch das Hosting der eigenen Dienste verursacht wurden, waren rund um das Jahr 2000 endgültig verstanden und erhört worden. Nach und nach kamen Anbieter auf den Markt, die sich um die eigenen infrastrukturellen Herausforderungen kümmern wollten und somit das Hosting übernahmen. IaaS war geboren. Nun war es möglich, Hardware für die eigenen Dienste zu mieten, um so die physische Wartung und Instandhaltung der Maschinen vom Betreiber erledigen zu lassen. Man musste also „nur“ noch die Maschinen entsprechend aufsetzen und konfigurieren, wofür erstmals Virtualisierungsmechanismen zum Einsatz kamen  – dann die eigentlichen Dienste bzw. Services installieren und verfügbar machen  – fertig! Es war also nach wie vor komplexe Arbeit nötig, aber der Aufwand auf Maschinenebene fiel gänzlich weg.

IaaS: Infrastructure as a Service leitete die „Metal-Transformation“ ein und machte es erstmals möglich, die Hardwareebene zu abstrahieren.

 

Das war also der erste Schritt in die neue Welt. In den folgenden Jahren wurde IaaS breit angenommen und das Angebot stetig erweitert. Auch die Virtualisierungen waren erwachsen geworden und wurden industriell wie kommerziell als Standardwerkzeug eingesetzt. So war der Nährboden für den nächsten (R)Evolutionsschritt bereitet.

 

 

PaaS oder „Software as a Baukasten“

Was IaaS mit der Hardwareebene anstellte und somit Administratoren das Leben einfacher machte, wollte PaaS auf Softwareebene für die Entwickler tun. Und das mit enormem Erfolg.

PaaS: Platform as a Service ist der Wegbereiter für die „Systemtransformation“, die die Abstraktion des Betriebssystems zur Folge hatte.

 

Die Plattform bietet vorkonfigurierte Dienste mit definiertem Funktionsumfang an, die miteinander verknüpfbar sind und sich auch in bestehende Systemlandschaften integrieren lassen. Benötigt eine Anwendung beispielsweise zur Persistierung (objekt)relationaler Daten eine Datenbank, bietet die Plattform einen Baustein in einer Grundkonfiguration, der sofort zur Verfügung steht. Bei Bedarf kann die Konfiguration natürlich noch justiert werden.

So stehen in dieser Form Bausteine für die verschiedensten Anforderungen zur Verfügung (Application Services, Storage, Event Queues, API Gateways, Media Services, Machine-Learning-Instanzen und zahllose mehr), die im Zusammenspiel eine Softwarelösung bereitstellen oder als einzelnes Feature in einer Anwendung zum Einsatz kommen.

Aber Vorsicht! Mit diesen neuen Möglichkeiten der Anwendungsentwicklung verändern sich auch die darunterliegenden Mechanismen. Neue Softwareentwicklungsmuster entstehen und alte werden in Rente geschickt. Konnte man mit IaaS noch ein „Lift & Shift“ (Die Migration von Applikationen, bei der man die bisherige Ausprägung ohne Anpassungen direkt in der Cloud-Plattform abbildet) von Anwendungen in die Cloud betreiben, steht man bei PaaS oft vor einer Neustrukturierung der Anwendungslogik, um das Potenzial der Plattform auch nutzen zu können.

Nun muss man sich also nicht mehr um Hardware kümmern, dank der Mühen von IaaS. Und PaaS macht es möglich, die Dienste klug und nach Bedarf zu kombinieren, um damit die gewünschte Anwendung zu formen bzw. bestehende zu erweitern. Sind wir nun bereits am Ende der Komfortskala angekommen, was die moderne und Cloud-basierte Anwendungsentwicklung angeht? Man mag es erahnen: noch lange nicht!

Neue Softwareentwicklungsmuster entstehen und alte werden in Rente geschickt.

 

 

FaaS oder „Kleinvieh macht auch Mist“

Seit etwa Mitte des Jahres 2014 ist eine neue Begrifflichkeit auf den Plan getreten und verspricht – vor allem den Entwicklern unter uns – den nächsten Entwicklungsschritt in der Cloud. Funktionen sollen als flexibler Klebstoff die PaaS-Welt dynamisch nutzbar gestalten.

FaaS: Function as a Service kann auch als „Anwendungstransformation“ verstanden werden. Anwendungen werden nach ihren Verantwortlichkeiten in Module oder Services geteilt und diese wiederum separat zur Verfügung gestellt.

 

Was ist an diesen Funktionen nun so neuartig ? Nutzt doch jeder Entwickler Funktionen in der Programmiersprache seiner Wahl täglich, um Businesslogik in Code zu gießen. Die Funktionen, um die es sich hier handelt, sind allerdings andere.

Sie sind Kleinstdienste, haben klar abgetrennte Aufgabenbereiche und arbeiten autonom und statuslos. Man kann sie wiederum auf schlaue Weise zusammenschließen und erhält am Ende eine lose gekoppelte Anwendungslogik, die auch in bzw. an Bestandssysteme ein- oder angehängt werden kann.

Vielleicht drängt sich nun unweigerlich der Gedanke „Microservices!“ auf, womit man keineswegs falsch liegt. Funktionen kann man als „Single-Purpose Services“ oder auch gerne als „Nanoservices“ beschreiben.

Nur Vorsicht vor Vermischung von Begrifflichkeiten. Die Architektur einer mit Funktionen realisierten Anwendung kann dem Microservices-Pattern folgen, aber die Idee geht darüber hinaus. Microservices beschreiben einen bestimmten Bauplan für eine Anwendung, wohingegen sich die Funktionen als das neue, mächtige – aber sehr kleinteilige – Werkzeug für den eigentlichen Bau verstehen.

Funktionen sollen als flexibler Klebstoff die PaaS-Welt dynamisch nutzbar gestalten.

Die Stärke der Funktionen liegt allerdings nicht in ihrem Formfaktor, sondern in dem Versprechen, dass die Betreiber der anbietenden Cloud-Plattform geben. Und hier kommt auch der Name ins Spiel, unter dem FaaS eher bekannt geworden ist: „Serverless Computing“ oder kurz „Serverless“.

 

 

Serverless oder „Weg mit den Maschinen“

Die Idee von Serverless Computing ist es, Dienste anzubieten, ohne einen einzigen Gedanken an Maschinen, Container, Betriebssysteme, Softwareversionen oder Skalierung der Instanzen – horizontal wie vertikal – zu verschwenden. Das ist natürlich etwas überspitzt formuliert, dennoch soll der „Serverless“-Weg genau zu diesem Ziel führen.

Und das ist auch das Versprechen der Plattformbetreiber: „100 % managed and operated“. Serverless Computing bietet also die Umgebung, um sich nahezu ausschließlich auf die Implementierung von Businesslogik konzentrieren zu können. Dabei muss diese in sinnvolle kleine und autonome Teile zerlegt werden. Jeder Teil wird als eine separate Funktion zur Verfügung gestellt, ausgeführt und skaliert.

Die Vorteile von „Serverless Computing“ wie Wartbarkeit, Skalierbarkeit und kurze Deploymentzyklen liegen auf der Hand und können auf diese Weise bereits auf Funktionsebene optimiert werden, was bislang nur auf Applikationsebene möglich war.

 

 

Aus großer Kraft folgt …

… große Verantwortung. Deshalb ein leises „Aber“ bzw. eine Ermahnung zur Vorsicht!

So verlockend es auch ist, Anwendungslogik frei nach dem Vorsatz „Separation of Concerns“ in kleinste Teile zu zerlegen, muss man stets mit Bedacht vorgehen und jedes Szenario für sich beleuchten. Denn sowohl die einst gefeierten Monolithen als auch die neuerdings gehuldigten Microservices haben uns beide noch nicht den heiligen Gral der glorreichen Systemarchitektur gebracht. Somit gilt auch hier der berühmte, immer richtige Satz der IT: „Es kommt drauf an“.

 

Spannende und Informative Sessions zum Thema finden Sie im Microservices & APIs Track der BASTA!

 

The post Die serverlose (R)evolution appeared first on BASTA!.

]]>
“Angular ist keine Eintagsfliege” https://basta.net/blog/angular-ist-keine-eintagsfliege/ Wed, 16 Aug 2017 07:27:32 +0000 https://basta.net/?p=21466 Die Frage nach dem "richtigen" JavaScript-Framework sollte man sich in jedem Projekt neu stellen – und dabei auch auf die eigenen Vorlieben achten. Manfred Steyer hat sich für Angular entschieden und erzählt im Interview, warum er das gemacht hat, welche Kriterien wichtig für die Entscheidung sind – und warum React auch gut sein kann.

The post “Angular ist keine Eintagsfliege” appeared first on BASTA!.

]]>
Die Qual der Wahl

Man könnte den Eindruck gewinnen, dass es so viele JavaScript-Frameworks gibt wie Sand am Meer. Gemeinsam ist ihnen allen, dass sie den Entwicklern dabei helfen wollen, die Arbeit leichter zu erledigen. Natürlich kann man einen einfachen Editor nehmen und alles direkt in nacktem JavaScript schreiben. Sinnvoller ist es, eine IDE zu verwenden, eventuell TypeScript zu nutzen und sich dann ein Framework zu suchen, das für den einen Arbeitsstil und das anvisierte Projekt passend ist. Und genau hier kann es schwierig werden.

Vor dem Projektstart sollte man sich im Klaren darüber sein, warum man was mit JavaScript machen möchte. Ist es nur eine kleine Anwendung für einen kurzen oder experimentellen Einsatz, geht es nur um eine Webseite oder soll eine richtige App daraus werden? Soll das Ganze auf Enterprise-Niveau oder für einen kleinen Zirkel von Fachanwendern funktionieren? Und zu guter Letzt natürlich die Frage, welches der zahlreichen Frameworks zum Einsatz kommen soll. Denn die Frameworks lösen Probleme auf unterschiedliche Weise oder sind genau zur Lösung eines Problems geschrieben worden.

Darüber hinaus ist nach Manfred Steyer darauf zu achten, wie das Ökosystem eines Frameworks aussieht, welche Partner beteiligt sind und wie die Community arbeitet – im Bereich der Open-Source-Software sind das selbstverständliche Fragen, in der Microsoft-Welt muss man sich als Entwickler unter Umständen noch an solche Überlegungen gewöhnen. Zwar stehen hinter Frameworks wie Angular und React Unternehmen wie Google und Facebook, aber es gibt auch viele gute Lösungen, die von kleinen Unternehmen, Gruppen oder sogar einzelnen Entwickler angeboten werden und auch wunderbar funktionieren.

Angular als erste Wahl

Aus der klassischen ASP.NET-Welt heraus ist Manfred über zahlreiche Projekte immer stärker in die JavaScript-Welt hineingewachsen und dabei natürlich an den Punkt gekommen, all die genannten Fragen und Überlegung zu überdenken. Er hat sich für Angular entschieden und ist darin inzwischen zum erfahrenen Experten aufgestiegen. Ihm ist wichtig, dass man sich vor dem Projektstart der konzeptionellen Unterschiede der einzelnen Frameworks bewusst ist, und auch weiß, wie das eigene Team tickt – was das im Detail und für Einzelne bedeutet, erklärt er anschaulich im Interview.

 

 

Selbstverständlich ist er auch als Sprecher auf der BASTA! und stellt in seinem Workshop “Angular-Senkrechtstarter-Workshop: Web-Frontends, die begeistern” und z.B. in der Session “Durch die Schallmauer: Hoch performante Anwendungen mit Angular” den praktischen Einsatz von Angular vor.

The post “Angular ist keine Eintagsfliege” appeared first on BASTA!.

]]>
SQL Server 2017 im Überblick: Alles, was Sie wissen müssen https://basta.net/blog/ueberblick-zum-sql-server-2017/ Wed, 26 Jul 2017 06:57:24 +0000 https://basta.net/?p=21371 SQL Server 2017 bedeutet nicht nur Unterstützung von Linux. Vielmehr bietet die Version einige neue Features, die durchaus interessant sind. Dieser Artikel stellt diese interessanten Neuerungen der Database Engine vor und soll Interesse an ihnen wecken.

The post SQL Server 2017 im Überblick: Alles, was Sie wissen müssen appeared first on BASTA!.

]]>
Graph Data Processing

Eine größere Neuerung ist der Einbau einer neuen Art von Tabellen: „Graph Tables“ (Graphtabellen). Mit diesen lassen sich Beziehungen zwischen Tabellen definieren. Zu diesem Zweck gibt es Knotentabellen (Nodes), die Benutzerdaten speichern und Kantentabellen (Edges), die den Beziehungen zwischen diesen Platz bieten. Insgesamt ist dies ein alternativer Ansatz zu relationalen Beziehungen zwischen Tabellen, um Daten mit Beziehungen zu speichern und abzufragen. Der wohl größte Unterschied dabei dürfte sein, dass eine Beziehung (Eintrag in Edge-Tabelle) zwischen jeder existierenden Node Table definiert werden kann; anders als relationale Beziehungen (mit Fremdschlüsseln), die fest zwischen zwei Tabellen angelegt werden.
Wenn also Beziehungen zwischen Bieren, Brauereien, Städten und Ländern abgebildet werden sollen (Abb. 1), dann sieht das T-SQL wie in Listing 1 aus.

Abb. 1: Biere, Brauereien, Städte und Länder

 

Listing 1
/*------------------
Knotentabellen
-------------------*/

-- Biere
CREATE TABLE [dbo].[Biere]
(
  Id INT PRIMARY KEY,
  [Name] VARCHAR(50),
) AS NODE;
CREATE NONCLUSTERED INDEX idx_Biername ON [dbo].[Biere] ([name]);

-- Brauereien
CREATE TABLE [dbo].[Brauereien]
(
  Id INT PRIMARY KEY,
  [Name] VARCHAR(50),
) AS NODE;
CREATE NONCLUSTERED INDEX idx_Brauereiname ON [dbo].[Brauereien] ([name]);

-- Städte
create table [dbo].[Staedte]
(
  Id INT PRIMARY KEY,
  [Name] VARCHAR(50),
) AS NODE;
CREATE NONCLUSTERED INDEX idx_Stadtname ON [dbo].[Staedte]([name]);

-- Länder
CREATE TABLE [dbo].[Laender]
(
  Id INT PRIMARY KEY,
  [Name] VARCHAR(50),
) AS NODE;
CREATE NONCLUSTERED INDEX idx_Laendername ON [dbo].[Laender]([name]);

/*-------------
Kanten Tabellen
---------------*/

-- Gebraut von
CREATE TABLE [dbo].[GebrautVon] AS EDGE;
CREATE UNIQUE NONCLUSTERED INDEX idx_GebrautVon ON [dbo].[GebrautVon] ($from_id, $to_id);

-- Brauereistandort
CREATE TABLE [dbo].[Brauereistandort] AS EDGE;
CREATE UNIQUE NONCLUSTERED INDEX idx_Brauereistandort ON [dbo].[Brauereistandort] ($from_id, $to_id);

-- Im Land
CREATE TABLE [dbo].[LiegtInLand] AS EDGE;
CREATE UNIQUE NONCLUSTERED INDEX idx_Herkunftsland ON [dbo].[LiegtInLand] ($from_id, $to_id);

Die T-SQL-Zusätze AS NODE beziehungsweise AS EDGE sind beide neu und legen fest, welche Funktion eine Tabelle einnehmen soll. Für die Graph Tables wird dadurch eine Reihe zusätzlicher Spalten angelegt (Abb. 2), die für interne Zwecke benötigt werden.

Abb. 2: Node- und Edge-Tabellen im SQL Server Management Studio

Beim Befüllen (und auch beim Ändern) der Tabellen mit Inhalten muss zwischen den beiden Arten von Tabellen unterschieden werden. Node-Tabellen werden befüllt, indem die beiden internen Spalten am Anfang ignoriert werden können; um ehrlich zu sein, kann auf die graph_id…-Spalte überhaupt nicht zugegriffen werden, während die $node_id…-Spalte ein internes JSON enthält.

Bei Edge-Tabellen muss wiederum angegeben werden, von welcher Entität (aus welcher Node-Tabelle) zu welcher Entität (aus welcher anderen Node-Tabelle) die Verbindung existieren soll. Listing 2 zeigt dafür einige Beispiele.

Listing 3
-- Welche Biere wurden von Brauerei #1 gebraut?
SELECT [Biere].[Name] AS 'Bier'
FROM [dbo].[Biere], [dbo].[GebrautVon], [dbo].[Brauereien] 
WHERE MATCH (Biere-(GebrautVon)->Brauereien) AND [Brauereien].[name] = 'Brauerei #1';

-- Welche Biere kommen aus Stadt #7?
SELECT [Biere].[Name] as 'Bier',
       [Brauereien].[Name] as 'Brauerei',
       [Staedte].[Name] AS 'Stadt'
FROM [dbo].[Biere], [dbo].[GebrautVon], [dbo].[Brauereien],[dbo].[Brauereistandort],[dbo].[Staedte]
WHERE MATCH (Biere-(GebrautVon)->Brauereien-(Brauereistandort)->Staedte) AND [Staedte].[Name] = 'Stadt #7';


Die erste Version ist jedoch mit Einschränkungen versehen. Bestehende Tabellen können weder in Node- noch in Edge-Tabellen umgewandelt werden. In-Memory OLTP und Polybase sind ebenso tabu. Abfragen mittels MATCH können weder rekursiv noch polymorph sein. All dies soll in späteren Versionen oder Service-Packs nachgereicht werden.

 

Neue T-SQL-Funktionen

Aber es gibt noch einige weitere T-SQL-Funktionen, die kleinere Verbesserungen bringen sollen. Zunächst einmal (Trommelwirbel) gibt es endlich eine TRIM()-Funktion, die RTRIM() und LTRIM() zusammenfasst und Whitespaces am Anfang und Ende einer Zeichenkette entfernt. Des Weiteren gibt es nun eine CONCAT_WS(), die ähnlich der CONCAT()-Funktion beliebige Werte zu einer Zeichenkette zusammenfügt. Der Unterschied: CONCAT_WS() akzeptiert als Erstes einen Parameter, der als Separator dient und zwischen allen weiteren Parametern eingefügt wird. So liefert CONCAT_WS(‘;’, @@SERVERNAME, GETDATE(), SUSER_NAME()) beispielsweise SQLServer2017;Jun 29 2017  6:07PM;sa. Alle Werte werden mit einem Semikolon getrennt, ohne dass dieses Trennzeichen wiederholt werden muss.

Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]

Ebenfalls eine Erleichterung soll die TRANSLATE()-Funktion mit ihren drei Parametern bieten. Sie durchsucht den ersten Parameter nach jedem Zeichen des zweiten Parameters und setzt das Zeichen des dritten Parameters ein, das sie an derselben Position findet. Klingt kompliziert? Ist es aber nicht. Ein Beispiel: TRANSLATE(‘2*[3+4]/{7-2}’, ‘[]{}’, ‘()()’) sorgt dafür, dass alle eckigen Klammern (Stelle 1 und 2) durch runde Klammern ersetzt werden (auch an Stelle 1 und 2). Das Gleiche geschieht mit geschweiften Klammern (Stelle 3 und 4), die ebenfalls durch runde Klammern (auch an Stelle 3 und 4) ersetzt werden. Das Ergebnis ist somit 2*(3+4)/(7-2) und ersetzt damit diesen, etwas unübersichtlichen Ausdruck REPLACE(REPLACE(REPLACE(REPLACE(‘2*[3+4]/{7-2}’,'[‘,'(‘), ‘]’, ‘)’), ‘{‘, ‘(‘), ‘}’, ‘)’).

Mit SQL Server 2016 hat die Funktion STRING_SPLIT() Einzug in den Funktionsumfang gehalten. Mit ihr kann man Zeichenketten anhand eines Trennzeichens (Delimiter) auftrennen und somit in eine Liste verwandeln. In Version 2017 hat es nun auch das Pendant geschafft. Mittels STRING_AGG() können Werte aus mehreren Zeilen zu einem Wert aggregiert werden; der zweite Parameter ist dabei das gewünschte Trennzeichen. Auf Wunsch kann die Aggregation auch unabhängig einer Zeilensortierung erfolgen.

-- Alle Datenbanken ohne bestimmte Sortierung
SELECT STRING_AGG([name], '; ') AS Datenbanken FROM sys.databases;
-- Ebenfalls alle Datenbanken, diesmal sortiert nach Namen
SELECT STRING_AGG([name], '; ') WITHIN GROUP (ORDER BY [name])
 AS Datenbanken FROM sys.databases;


Abb. 3: „STRING_AGG()“ in Action

 

Der Access & Storage Track auf der BASTA!

 

Automatic Tuning

Mit SQL Server 2016 wurde der Query Store eingeführt, der verdichtete Perfomancedaten für die spätere Analyse durch einen Administrator bereitstellte. SQL Server 2017 geht einen Schritt weiter, indem laufend Ausführungsdaten gesammelt und analysiert werden. SQL Server „lernt“ also quasi, welcher Ausführungsplan für welche Abfrage die beste Performance mit den geringsten Ressourcenkosten ergeben hat. Genauer: Sie erlauben die Erkennung von sogenannten „Plan regressions“-Problemen, was bedeutet, dass Ausführungspläne (die ansonsten gut funktionieren) bei bestimmen Parametern nicht optimal sind. SQL Server kann nun für eine solche Kombination einen anderen Ausführungsplan verwenden und die Abfrage damit tunen. Somit wird also ein Vorgehen automatisiert, das zuvor nur aufwendig und fehlerbehaftet manuell durchgeführt werden konnte. Die dafür zuständige Stored Procedure heißt sp_force_plan und wird exemplarisch so aufgerufen: EXEC sp_force_plan @query_id = 4711, @plan_id = 1812;. Wohl dem, der @query_id und @plan_id bei einer großen Menge an Abfragen schnell ermitteln kann.

Die Aktivierung des neuen Automatismus geschieht auf Datenbankebene ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON );. Um SQL Server bei seinen Entscheidungen dabei in die Karten zu schauen, gibt es eine neue DMV (Database Management View) mit dem Namen sys.dm_db_tuning_recommendations. Ein Teil der zurückgelieferten Daten sind im JSON-Format, sodass eine brauchbare Abfrage in Listing 5 entsteht.

Listing 5
SELECT [name], [reason], [score],
  JSON_VALUE(details, '$.implementationDetails.script') as script,
  details.* 
FROM sys.dm_db_tuning_recommendations
  CROSS APPLY OPENJSON(details, '$.planForceDetails')
    WITH (  query_id int '$.queryId',
      regressed_plan_id int '$.regressedPlanId',
      last_good_plan_id int '$.forcedPlanId') as details
WHERE JSON_VALUE(state, '$.currentValue') = 'Active';

Das gesamte Vorgehen unterscheidet sich übrigens grundlegend vom dem des schon länger vorhandenen Database Engine Tuning Advisors, der die Daten aus einem Zeitraum nutzt, um Indizes vorzuschlagen.

 

System-versioned/Temporal Tables

Mit SQL Server wurden System-versioned/Temporal Tables eingeführt, die eine automatische serverseitige Historisierung ermöglichen. Nachteil war bis dato, dass die historischen Daten ohne manuelles Einschreiten niemals entsorgt wurden. Das ist nun anders. Bei der Aktivierung der Historisierung kann bestimmt werden, wie alt historische Daten maximal werden dürfen. Ältere Daten werden bei Abfragen nicht mehr berücksichtigt und automatisch aus der Datenbank entfernt. Als Einheiten stehen DAYS, WEEKS, MONTHS und YEARS zur Auswahl. Wird HISTORY_RETENTION_PERIOD nicht angegeben, findet kein automatisches Entfernen satt. Außerdem muss das neue Feature auf Datenbankebene eingeschaltet werden:

— Aktivierung auf Datenbankebene
ALTER DATABASE dotnetconsultingDb SET TEMPORAL_HISTORY_RETENTION ON;

— Historische Daten auf maximal 9 Monate beschränken
ALTER TABLE dbo.WebsiteUserInfo
SET (SYSTEM_VERSIONING = ON (HISTORY_RETENTION_PERIOD = 9 MONTHS));

 

CLR-Integration

Bis SQL Server 2016 wurden die Sicherheitsstufen, die festlegten, über welches API eine Assembly aufgerufen durfte, mittels Code Access Security (CAS) festgelegt. Da CAS nicht mehr als Sicherheitsgrenze unterstützt wird, wurde der Konfigurationswert clr strict security, dessen Wert angibt, ob SQL Server pauschal alle Assemblies wie mit der ehemaligem Sicherheitsstufe unsafe behandeln soll. Damit müssen faktisch alle Assemblies, die auf dem SQL Server zum Einsatz kommen sollen, signiert sein. Für die Entwicklung reicht es allerdings nach wie vor, die TRUSTWORTHY-Datenbankeigenschaft zu setzen.

 

SQL-Server-Machine-Learning-Services-Python-Unterstützung

Unter diesem Schlagwort wurde die beliebte Sprache Python in SQL Server eingebaut. Damit lassen sich schneller größere Datenmengen verarbeiten und auswerten, schließlich müssen keine Daten von SQL Server wegbefördert werden, da die Verarbeitung intern stattfindet. Damit stehen die Leistungsfähigkeit und die Erweiterungsmöglichkeiten Data Scientists für Auswertungen zur Verfügung. Administratoren können dabei mittels Richtlinien festlegen, welche Ressourcen die Python Runtime verwenden darf.

 

SQL auf der BASTA!

Noch viel mehr zu den Neuerungen erfahren Sie im Data Access und Storage Track.

 

 

The post SQL Server 2017 im Überblick: Alles, was Sie wissen müssen appeared first on BASTA!.

]]>
TypeScript ist der Türöffner für C#-Entwickler https://basta.net/blog/typescript-ist-der-tueroeffner-fuer-c-entwickler/ Wed, 19 Jul 2017 07:21:54 +0000 https://basta.net/?p=21291 Die Beliebtheit von JavaScript steigt seit einigen Jahren rasant an. Inzwischen gibt es zahlreiche populäre Frameworks und Bibliotheken wie Angular, React oder Aurelia. Mit Node.js lässt sich JavaScript sogar serverseitig verwenden. Ob Web, Desktop oder Server, mit JavaScript kann man dadurch überall arbeiten. Mit TypeScript hat Microsoft den klassischen C#-Entwicklern einen Königsweg nach JavaScript gebahnt.

The post TypeScript ist der Türöffner für C#-Entwickler appeared first on BASTA!.

]]>

Der JavaScript & HTML5 Track auf der BASTA! 2017

 

Von C# über TypeScript nach JavaScript

TypeScript ist für klassische .NET- und C#-Entwickler der Türöffner nach JavaScript. Es stellt eine Obermenge von JavaScript dar und erlaubt es, typisierten Code so zu schreiben, dass er in reines JavaScript übersetzt werden kann. Dabei werden auch unterschiedliche Versionen von JavaScript unterstützt, auch wenn die Entwickler rund um Anders Hejlsberg sich immer am aktuellsten Standard orientieren.

Da JavaScript eine dynamische Sprache ist, gibt es keine statische Typisierung von Variablen. Die statische Typisierung, die von Sprachen wie C# oder Java bekannt ist, führt bei falschen Zuweisungen schon zu Compile-Fehlern, und nicht zu Fehlern, die erst zur Laufzeit auftreten. Das erleichtert die Arbeit am Code und ist eines der vielen Probleme, die durch TypeScript gelöst werden. JavaScript wird dadurch (wieder) zu einer modernen Programmiersprache. Inzwischen werden sogar große Unternehmenslösungen damit umgesetzt.

Ich würde heute alles mit TypeScript machen

Im Interview erklärt Thomas Claudius Huber, Trivadis AG, warum er inzwischen so weit ist, dass er alles, was heute JavaScript ist, mit TypeScript schreiben würde. Neben den Vorzügen der Typisierung und der Möglichkeit, seit der ECMAScript-Version 2015 wie gewohnt mit Klassen arbeiten zu können, lässt auch das Tooling rund um TypeScript kaum Wünsche offen. In jedem gängigen Editor vom große Visual Studio, dem schlanken Visual Studio Code von Microsoft oder auch WebStorm von JetBrains. In jedem dieser Tools finden Entwickler eine stetig verbesserte Unterstützung beim Schreiben von TypeScript-Code. So kann man passend zu den jeweiligen Projekten, eigenen Vorlieben und Erfahrung ein effizientes Toolset zusammenstellen.

Darüber hinaus gibt es inzwischen zahlreiche Frameworks, wie Angular, Electron, Node.js und andere, mit denen man spezifische Probleme und Aufgaben lösen kann. Huber meint, dass es mit der Auswahl des Frameworks dabei eigentlich ganz ähnlich ist wie bei der Vorliebe für eine Automarke: Es gibt also objektive Kriterien, die passen müssen, aber es ist auch immer eine subjektive Entscheidung. Wie auch immer diese ausfällt, nach seiner Ansicht müssen moderne C#-Entwickler beide Welten beherrschen. Dann können sie je nach Projektanforderung client- oder serverseitig mit C# oder TypeScript oder einer Kombination aus beidem arbeiten. Dabei müssen sie sich aber auch daran gewöhnen, dass einige Sachen in TypeScript ähnlich sind wie in C#, aber doch auch anders – da muss man dann aufpassen.

Worauf man genau achten muss, erfahren Sie in zahlreichen Sessions und Workshops der BASTA! 2017. Eine Übersicht finden Sie auf dem JavaScript-Track.

Den umfassenden Workshop von Thomas Claudius Huber TypeScript-Workshop: Einführung von 0 auf 100 in einem Tag können Sie am 29.9.2017 besuchen.

 

 

 

 

The post TypeScript ist der Türöffner für C#-Entwickler appeared first on BASTA!.

]]>
Kolumne: Karrieretipps https://basta.net/blog/kolumne-karrieretipps/ Mon, 26 Jun 2017 09:26:39 +0000 https://basta.net/?p=21071 Die digitale Revolution hat uns fest im Griff. Wir sind mittendrin. Und wieder mal fragt man sich: ist das wieder so ein IT-Trend, wie SOA, Big Data oder Cloud Computing? Muss man sich als Entwickler zum wiederholten Male angeblich völlig neu erfinden und alles auf „Reset“ stellen? Was bedeutet die Digitalisierung für die IT-Berufswelt? Einerseits liest man in einer Studie der Wirtschaftswoche: „Computer können Jobs von 4,4 Millionen Deutschen übernehmen“, andererseits sagt eine Studie der Boston Consulting Group von 2015: „Digitalisierung schafft Hunderttausende neue Jobs!“ Aber wie sieht die Karriere 4.0 für Entwickler tatsächlich aus?

The post Kolumne: Karrieretipps appeared first on BASTA!.

]]>
von Yasmine Limberger

 

Sicher ist, mit der Digitalisierung entstehen unzählige neue Geschäftsmodelle, und über eines sind sich alle Branchen einig: Die Digitalisierung steht auf der CIO-Agenda ganz weit oben. Während viele Unternehmen noch damit hadern, alte Strukturen zu durchbrechen und mit einer neuen digitalen Strategie den Markt zu erobern, befinden sich andere bereits in der nächsten Stufe der Digitalisierung. Ob Lieferdrohnen, digitale Umkleidekabinen oder selbstfahrende Autos – die digitale Welt bietet für Entwickler einen völlig neuen riesigen Spielplatz mit unzähligen Möglichkeiten.

Neben der Technik kommt aber im Rahmen der Digitalisierung ein zusätzlicher Aspekt ins Spiel, dem in der analogen Welt ebenfalls eine essenzielle Aufgabe zugesprochen wurde, heute aber fest mit der Technologie einhergeht: das digitale Marketing! Schon der Begriff „Marketing“ löst bei manch einem ITler Unbehagen aus. Werbebotschaften und wissenschaftlich nicht immer untermauerte Versprechen waren für Entwickler bisher unbeachtete Randerscheinungen, um die sie sich nicht weiter kümmerten. Nun aber gewinnt der Kunde im digitalen Zeitalter eine so große Bedeutung, dass seine persönlichen Daten, seine Gewohnheiten, sein Verhalten und seine individuellen Bedürfnisse auf direktem Wege in die IT-Lösungen mit einfließen. Mehr noch, seine Daten liefern die Basis der digitalen Produkte. Neben Datenschutz und ethischen Überlegungen kommen bei der Entwicklung digitaler Lösungen somit individuell auf den Kunden zugeschnittene Marketinganforderungen mit ins Spiel. Auch das ist eigentlich nicht neu, erreicht aber heute eine neue Dimension. Bisherige Trends wie Big Data und Cloud Computing fügen sich nahtlos in den Trend zur Digitalisierung ein. Somit sind Experten in diesem Gebieten nach wie vor sehr gefragt. Zusätzlich entstehen aber neue Berufsbilder in der IT, die die strategische Wichtigkeit der Digitalisierung untermauern. In Jobportalen finden sich heute neue, fantasievoll klingende digitale Jobtitel, von denen man mehr oder weniger die Aufgaben ableiten kann (Quelle: www.jobs.de):

 

  • Digital Marketing Analyst
  • Leiter Digital Customer Experience
  • Chief Digital Officer
  • Consultant Cards & Digital Payment
  • Digital Strategist
  • Digital Verification Expert
  • Digital Project Manager
  • IT-Consultant Digital Banking
  • Digital Factories Security Management Support
  • Digital Insurance Consultant
  • Project Manager Digital Analytics
  • Senior Business Architect Digital Transformation
  • Digital Marketing Manager

 

Deutlich wird mit den neuen Jobtiteln vor allem eines: „Digitale IT-Experten“ werden in allen Branchen benötigt. Vor allem die Finanzbranche mit ihren Banken und Versicherungen signalisiert dabei einen großen Bedarf, aber auch alle anderen Branchen wie der Handel, die Industrie und der öffentliche Bereich suchen nach digitalen Experten. Die Aufgaben der digitalen IT-Spezialisten lassen sich dabei wie folgt zusammenfassen:

 

  • Allgemeine Projektaufgaben: Hierzu zählen die Konzeption, Entwicklung und Implementierung von digitalen Businessservices. Die Projektmanager fungieren dabei als Schnittstelle zwischen Kunden und Technologie und sind in der Lage, Kosten- und Wettbewerbsvorteile zu skizzieren.
  • Spezielle Projektaufgaben: Dabei geht es vor allem darum, die digitalen Businessservices konsequent entlang der Wertschöpfungskette zu integrieren. Das ist mit umfangreichem Analysieren von Geschäftsprozessen sowie der Durchführung von Big-Data-Analysen verbunden. Die Projektmanager agieren hier als Schnittstelle zwischen den Prozessverantwortlichen und den Entwicklerteams.
  • Entwicklungsaufgaben: Hier geht es um das Konzipieren, Entwickeln und die Implementierung digitaler Lösungen als App bzw. in unterschiedlichen Bereichen. Noch stärker als in der Vergangenheit spielen hier die grafische Benutzeroberfläche, die Navigation und das User Experience Design eine Rolle.
  • Führungsaufgaben: Diese finden sich vor allem im Produktmanagement sowie bei der strategischen Produktentwicklung und Vermarktung, aber auch im Management und der Entwicklung von strategischen Partnerschaften und Kundenbeziehungen. Auch die Visions- und Strategieentwicklung der digitalen Ausrichtung des Unternehmens setzt eine erfahrene Führungspersönlichkeit voraus.

 

Wie geht es weiter mit der IT-Karriere 4.0?

Auch wenn im Zuge der Digitalisierung klassische Aufgaben in unterschiedlichen Funktionen immer stärker durch intelligente Digital Services übernommen werden, die Digitalisierung bringt für die IT-Berufswelt jede Menge neues Entwicklungspotenzial. Für Entwickler heißt das jede Menge neue Herausforderungen, neue Technologien und noch mehr Schnittstellenvernetzung und Datenverarbeitung. Wie auch in der Vergangenheit, wird von Entwicklern der Weitblick für das Gesamtbild erwartet und eine starke Kundenausrichtung. Die Herausforderungen dabei nehmen jedoch stetig zu. Datensicherheit und Datenschutz stehen bei allen digitalen Entwicklungen im Fokus, sodass vor allem auch IT-Securityexperten weiterhin sehr gute Jobaussichten verzeichnen können. Zu den Anforderungen an digitale IT-Experten zählen die folgenden:

 

  • Businesskompetenzen: Betriebswirtschaft, Marketing, Rechnungswesen, Logistik sowie HR-Management und Organisation
  • IT-Kompetenzen: Internettechnologien, Softwareentwicklung, Digital Business Services im Front- und Back-Office
  • Methodenkompetenzen: Projektmanagement, Geschäftsprozessmanagement
  • Soziale Kompetenzen: Teamplayer in multikulturellen, interdisziplinären Teams, Kommunikation, Führungskompetenzen

 

Neben den technischen Anforderungen kommen künftig also immer mehr Marketingaspekte und auch juristische sowie ethische Komponenten dazu. Somit sind auch diesen Berufsgruppe im digitalen Zeitalter neue Jobperspektiven sicher – vorausgesetzt, man kann sich mit der hochkomplexen IT-Welt anfreunden.

Wie schnell es mit der Digitalisierung vorangeht, entscheidet sich vor allem daran, wie schnell die Unternehmen mit den damit verbunden Anforderungen zurechtkommen. Neben den technischen Voraussetzungen ist dabei auch ein konsequentes Change Management in der Arbeitskultur notwendig. Mobile Arbeitsmodelle sind in der IT schon Alltag, in traditionellen Branchen jedoch zum Teil noch Neuland. Oft fehlt es hier an Vertrauen in diese Art der virtuellen Arbeitskultur. Aber die Anforderungen an den digitalen IT-Arbeitsplatz sind klar:

 

  • Mobiles Arbeiten: jederzeit, überall
  • Einbinden unterschiedlicher Schnittstellen (Kunden, Lieferanten, Produktion, Marketing …)
  • Akzeptanz der Vielfalt in Lebens- und Karriereentwürfen
  • Stellenwert der menschlichen Arbeitskraft erkennen und effizient einsetzen
  • gemeinsame Arbeitskultur auch in virtuellen Arbeitsumgebungen
  • Schnelle und datensichere Arbeitstechnologien

 

Kein Grund also zur Sorge: Die Digitalisierung bedeutet nicht das Ende der klassischen IT-Karriere, sondern ist der Beginn neuer, spannender Herausforderungen. Gleichzeitig stehen wir mit der Digitalisierung aber auch am Übergang zu Innovationen und neuen Lösungen, denn letztlich ist auch die Digitalisierung wieder ein Trend, der in absehbarer Zeit von einem neuen Megatrend abgelöst wird.

 

 

The post Kolumne: Karrieretipps appeared first on BASTA!.

]]>
TOP 20 SOCIAL INFLUENCERS IN .NET DEVELOPMENT https://basta.net/blog/top-20-social-influencers-net-development/ Wed, 14 Jun 2017 14:56:51 +0000 https://basta.net/?p=20916 For over 20 years BASTA! is the leading and independent conference for Microsoft-Technologies and JavaScript. BASTA! enables, with his competent offers about C#, .NET-Framework and web technologies, developers and software-engineers to keep up with recent developments. In line with this, BASTA! presents to you the Top 20 Social Influencer in these areas.

The post TOP 20 SOCIAL INFLUENCERS IN .NET DEVELOPMENT appeared first on BASTA!.

]]>
BASTA! 2017 – TOP 20 Influencers

The .NET, Windows & Open Innovation Conference BASTA! proudly presents the top 20 social influencers regarding the Microsoft and .NET World.

This list was created by analysing thousands of twitter profiles extracting the MozRank and Klout scores which rank both quality and influence. 

If we missed anybody, it has nothing to do with the fact that we don’t think that they are just as important or shareworthy as any person on this list. We ourselves though do believe that such analytical lists do have the right to exist and furthermore we at BASTA! follow the listed speaker on their social channels and enjoy their quality content.

We congratulate everyone who made it on this list. If you click on the name or on the picture of each speaker you will be redirected to their twitter profile and check out the useful information they share every day.

Enjoy!

METHODOLOGY

We first generated a list of one thousand .NET-related Twitter accounts.
To score the account and rank them accordingly, we analyzed their social authority and reach using two key metrics: MozRank and Klout.

Moz Social Authority Score: Social Authority score is composed of:

  1. The retweet rate of users’ last few hundred tweets.
  2. The recency of those tweets.
  3. A retweet-based model trained on user profile data.

Visit this MOZ blog post for more in-depth information.

Klout Score: Klout uses more than 400 signals from eight different networks to update the Klout Score daily. It’s mainly based on the ratio of reactions a user generates compared to the amount of content he shares. Read more at the Klout score blog.

Congratulations to all the influencers who have made it into our Top 20!


To visit the associated Twitter profiles, just click on the respective portraits. We hope you enjoy exploring new perspectives and find some inspiration.

Scott Hanselman @shanselman

Tech, Code, YouTube, Race, Linguistics, Web, Parenting, Black Hair, STEM, Inclusion, @OSCON chair, Phony. MSFT, but these are my opinions. @overheardathome

Total score: 164 = Klout score: 79 + Social Authority score: 86

Klout score
Moz score
Total score

Miguel de Icaza @migueldeicaza

Started Xamarin, Mono, Gnome with great friends. Working at Microsoft on beautiful .NET Mobile tools.

Total score: 159 = Klout score: 83 + Social Authority score: 76

Klout score
Moz score
Total score

Troy Hunt @troyhunt

Pluralsight author. Microsoft Regional Director and MVP for Developer Security. Online security, technology and “The Cloud”. Creator of @haveibeenpwned.

Total score: 157 = Klout score: 79 + Social Authority score: 78

Klout score
Moz score
Total score

Daniel Rubino @Daniel_Rubino

Editor-in-chief of Windows Central. Full-time curmudgeon, science geek and play-acting anarchist. HoloLens user. [email protected]

Total score: 156 = Klout score: 81 + Social Authority score: 75

Klout score
Moz score
Total score

Mark Russinovich @markrussinovich

CTO of Microsoft Azure, author of novels Rogue Code, Zero Day and Trojan Horse, Windows Internals, Sysinternals utilities.

Total score: 156 = Klout score: 82 + Social Authority score: 74

Klout score
Moz score
Total score

Mary Jo Foley @maryjofoley

I am All About Microsoft on ZDNet (http://blogs.zdnet.com/microsoft ). Oh, and I am also sometimes (more often than not) all about craft beer.

Total score: 146 = Klout score: 68 + Social Authority score: 78

Klout score
Moz score
Total score

Chris Heilmann @codepo8

Londoner, German, European. Developer Evangelist – all things open web, writing and helping. Works at Microsoft on Edge, opinions totally my own. #nofilter

Total score: 142 = Klout score: 70 + Social Authority score: 72

Klout score
Moz score
Total score

Paul Thurrott @thurrott

Paul Thurrott is an award-winning technology journalist and blogger and the author of over 25 books. He is the majordomo at http://Thurrott.com .

Total score: 142 = Klout score: 70 + Social Authority score: 72

Klout score
Moz score
Total score

Tom Warren @tomwarren

Senior Editor at The Verge. Primarily focused on Microsoft news and reviews. Tips: Telegram at tomwarren, or Twitter DM.

Total score: 141 = Klout score: 66 + Social Authority score: 75

Klout score
Moz score
Total score

Scott Guthrie @scottgu

Vice President of Microsoft Cloud and Enterprise Group.

Total score: 140 = Klout score: 65 + Social Authority score: 75

Klout score
Moz score
Total score

Dan Wahlin @DanWahlin

Founder of Wahlin Consulting. Consulting & training on Angular, Node.js, ASP_NET, C#, Docker. Author for Pluralsight and Udemy. https://about.me/danwahlin

Total score: 136 = Klout score: 68 + Social Authority score: 68

Klout score
Moz score
Total score

John Papa @John_Papa

Husband, father, and Catholic enjoying every minute with my family. Disney fanatic, evangelist, HTML/CSS/JavaScript dev, speaker, and Pluralsight author.

Total score: 134 = Klout score: 64 + Social Authority score: 70

Klout score
Moz score
Total score

Jon Galloway @jongalloway

ASP.NET & Azure Dev, I like computers and think someday others will, too. I am part of @HerdingCode podcast. I work for Microsoft but speak for myself.

Total score: 134 = Klout score: 65 + Social Authority score: 69

Klout score
Moz score
Total score

Immo Landwerth @terrajobst

Program manager on .NET at Microsoft. Opinions are my own. I tweet in GIFs.

Total score: 132 = Klout score: 66 + Social Authority score: 66

Klout score
Moz score
Total score

Clemens Vasters @clemensv

Software Architect, moving stuff from A to B, Microsoft @Azure #ServiceBus #EventHubs #Relay. OPCUA, OASIS, IoT. [email protected]

Total score: 128 = Klout score: 66 + Social Authority score: 62

Klout score
Moz score
Total score

Robert Cecil Martin @unclebobmartin

Software Craftsman, Consultant/Speaker

Total score: 125 = Klout score: 60 + Social Authority score: 65

Klout score
Moz score
Total score

K. Scott Allen @OdeToCode

Developer, Author @pluralsight & Visual Studio MVP

Total score: 111 = Klout score: 54 + Social Authority score: 57

Klout score
Moz score
Total score

Steven Guggenheimer @StevenGuggs

Microsoft’s Chief Evangelist of Developer Experience (DX) – part geek, part businessman – all mobile, all cloud, all tech…all the time.

Total score: 110 = Klout score: 55 + Social Authority score: 55

Klout score
Moz score
Total score

Anders Hejlsberg @ahejlsberg

Lead Architect of C# & TypeScript @Microsoft

Total score: 108 = Klout score: 50 + Social Authority score: 58

Klout score
Moz score
Total score

Erich Gamma @ErichGamma

Lead Developer @VisualStudioCode…and skier

Total score: 104 = Klout score: 46 + Social Authority score: 58

Klout score
Moz score
Total score

The post TOP 20 SOCIAL INFLUENCERS IN .NET DEVELOPMENT appeared first on BASTA!.

]]>
Meine künstliche Intelligenz https://basta.net/blog/meine-kuenstliche-intelligenz/ Thu, 01 Jun 2017 09:09:05 +0000 https://basta.net/?p=20359 Letztes Jahr wurden die Cognitive Services im Rahmen der Build-Konferenz vorgestellt: verschiedenste APIs, die künstliche Intelligenz und Machine Learning nutzen, um unseren Apps das Sehen, Hören, Sprechen und Verstehen zu erlauben. Dieses Jahr war wieder ein guter Teil der Build 2017 Keynote den Neuerungen auf diesem Sektor gewidmet.

The post Meine künstliche Intelligenz appeared first on BASTA!.

]]>
 

Neuigkeiten zu den Cognitive Services

Die Cognitive Services sind wahrlich ein gutes Beispiel für gelungene Developer Experience. Zunächst die technischen Möglichkeiten: Nur einen REST-Aufruf entfernt bietet sich dem Entwickler eine Welt voller Möglichkeiten, die unweigerlich begeistern muss. Foto machen, uploaden, und das Gesicht wird identifiziert, Emotionen werden erkannt und das Alter wird bestimmt. Kostenpunkt: 0,001 Euro pro Abfrage, 30 000 Abfragen pro Monat sind gratis. Entwickeln Sie so etwas einmal selbst.
Mindestens genauso wichtig ist der absolute Fokus auf rasches Ausprobieren. Die Website der Cognitive Services ermöglicht fast überall ein Testen mit Demodaten – und es fällt schwer, sich loszureißen, wenn man die ersten Services einmal ausprobiert hat.

Anpassbarkeit

Viele der Services sind deshalb attraktiv, weil sie bereits „angelernt“ sind: Das Computer-Vision-API nimmt jedes beliebige Foto entgegen und erkennt beispielsweise Personen, Tiere, Großstädte und Gegenstände aufgrund der unzähligen Testdaten. Wir müssen nichts bereitstellen, keine Modelle trainieren, es funktioniert einfach.

Schon bisher gab es aber auch Services, die einen individuellen Endpunkt mit spezifisch angelernten Daten zur Verfügung stellen, um domänenspezifisch zu unterstützen. So war beispielsweise der Language Understanding Intelligent Service (LUIS) dafür optimiert, zuvor eingelernte Sätze zu verstehen und auch in abgewandelter Form wiederzuerkennen. Nicht alle Sätze, sondern speziell angelernte Sätze für die eigene Domäne.

Drei der vier neuen Services, die nun auf der Build 2017 vorgestellt wurden, tragen den Begriff „Custom“ im Namen und zeigen damit, wohin die Reise geht.

Custom Vision Service

Der Custom Vision Service ist wie LUIS für Bilder: Über das API oder das webbasierte Portal lädt man Beispielbilder hoch und vergibt Tags. Anschließend lässt man den Service trainieren und erhält einen REST-Endpunkt, dem neue Bilder zur Analyse übergeben werden können. Das Ergebnis ist eine Einschätzung, zu viel Prozent welcher Tag auf das Bild zutrifft.

Wie gut funktioniert das in der Praxis? Vor einiger Zeit haben wir uns für eine Software aus dem Therapiebereich mit der automatisierten Erkennung des „Tangram“-Spiels beschäftigt. Mithilfe von sieben geometrischen Formen (fünf Dreiecke, ein Quadrat, ein Parallelogramm) müssen vorgegebene Bilder gelegt werden, nahezu die gesamte Tierwelt ist als Tangram-Vorlage verfügbar. Es war damals eine große technische Herausforderung, diese Erkennung zu implementieren. In vielen Manntagen wurden von hochqualifizierten Softwareentwicklern komplizierte Algorithmen implementiert und verschiedenste Lösungsansätze versucht. Vielleicht würden wir heute anders an die Sache herangehen.

Abb. 1: Image-Upload

Im ersten Schritt erstellt man ein Projekt auf https://www.customvision.ai und fügt die ersten Fotos hinzu (Abb. 1). In meinem Fall habe ich mit mehreren Hunde- und Katzentangrams gestartet und sie auf verschiedenen Untergründen und aus verschiedenen Winkeln fotografiert. Die Bilder müssen manuell getagt werden, pro Tag sind mindestens fünf Bilder notwendig (in der Dokumentation ist teilweise noch von dreißig Bildern die Rede – je mehr, desto besser). Anschließend startet man den ersten Trainingslauf, und nach wenigen Sekunden ist der Tangram-Erkennungsservice testbereit.

Abb. 2: Erste Erkennung

Nach der ersten Euphorie („Meine Katze wird erkannt!“) folgte sogleich die Ernüchterung: Selbst die lieblos am Tisch verstreuten Tangramteile wurden als Katze erkannt. Eigentlich war alles Katze (Abb. 2). Offenbar hatte der Service etwas Falsches gelernt, das kommt in den besten Familien vor (siehe dazu auch den kuriosen angeblichen Versuch des Pentagons, feindliche Panzer mit neuronalen Netzwerken zu erkennen).

Also nächster Durchgang: Bilder von zufällig angeordneten Tangram-Teilen hochladen und als „Tangramteile“ klassifizieren. Nicht Hund, nicht Katze. Und siehe da: die Erkennungsrate ging rasch nach oben, die Bilder wurden nun zuverlässig erkannt (Abb. 3).

Abb. 3: Katzen werden (endlich) erkannt

Nach demselben Schema können neue Fotos und neue Tags angelegt werden, der Service wird schrittweise verbessert. In einer eigenen „Performance“-Ansicht kann überprüft werden, ob die Erkennungsrate im Vergleich zur Vorgängerversion verbessert wurde (Abb. 4).

 


Abb. 4: Schrittweise Verbesserung

Natürlich ist das gezeigte Szenario noch nicht perfekt: Zum Beispiel erkennt es Katzentangrams auch dann, wenn ein Teil fehlt. Das ist im Sinne einer intelligenten Bilderkennung vielleicht erwünscht, im Fall von Tangrams jedoch falsch. Aber es zeigt: Mit Möglichkeiten wie dem Custom Vision Service können manche Problemstellungen auf eine komplett andere Art als bisher gelöst werden, Machine Learning und künstliche Intelligenz stehen dem normalen Entwickler zur Verfügung. Anstatt Algorithmen zu entwickeln, vertraut man einer Blackbox, die mit erstaunlich wenigen Testdaten in kürzester Zeit bereits verblüffende Erkennungsraten erzielen kann. Und mit der Weboberfläche ist das Erstellen dieser ausgeklügelten Logik auch für Nichtentwickler eine machbare Aufgabe. Denken Sie darüber nach.

 

Weitere Neuigkeiten

Neben dem Custom Vision Service wurde auf der BUILD auch der Custom Decision Service vorgestellt. Dahinter verbirgt sich ein – etwas missverständlich benannter – Zusammenschluss mehrerer existierender Cognitive Services, um personalisierte Inhalte anbieten zu können. Öffnet ein Benutzer auf Ihrer Website einen Artikel, wird durch Text- und Bildanalyse der Inhalt analysiert und dieses Profil genutzt, um andere Inhalte empfehlen zu können.

Auch die Bing Custom Search ist als Preview-Version verfügbar und bietet die Möglichkeit, unter einem individuellen Endpoint auf eine für die eigenen Bedürfnisse optimierte Bing-Suche zuzugreifen.

Der vierte Service trägt den Namen Video Indexer und verspricht die Analyse von Videoinhalten:

Wer ist zu sehen?
Was wurde gesprochen?
Wo war besonders viel Emotion zu sehen?

Spannend für alle Medien und Videoplattformen. Und für das Kürzen des nächsten Sommerurlaubfilms, Ihre Freunde werden’s Ihnen danken.

 

 

The post Meine künstliche Intelligenz appeared first on BASTA!.

]]>
Xamarin ist ein Quantensprung für .NET-Entwickler https://basta.net/blog/xamarin-ist-ein-quantensprung-fuer-net-entwickler/ Wed, 17 May 2017 15:35:40 +0000 https://basta.net/?p=20264 Als .NET-Entwickler hat man allein mit Windows schon eine große und breite Plattform, für die man entwickeln kann. Mit Xamarin öffnet sich aber noch mal ein ganzes Universum. Geht es nach Jörg Neumann, ist Xamarin das strategische Tool, um als Entwickler alle Plattformen zu erreichen, die nicht Windows sind. Ob iOS, Android, die Apple Watch und Apple TV oder via Tizen von Samsung auch Wearables und SmartTVs – alle sind möglich. Wie, erzählt Jörg Neumann im Videointerview.

The post Xamarin ist ein Quantensprung für .NET-Entwickler appeared first on BASTA!.

]]>
Der Quantensprung

Xamarin ist für ihn ein Quantensprung für .NET-Entwickler. Das .NET-Know-how inklusive XAML und Xamarin.Forms öffnet ihnen die Türen auch in die IoT-Welt und verbindet sie direkt mit der Cloud. Zudem profitieren sie von dem durchgehenden Tooling, das sie mit Visual Studio gut kennen und das von Microsoft immer weiterausgebaut wird. Das Tooling reicht allerdings nicht aus, um wirklich 100 Prozent auf jede Plattform zu liefern. Jede Plattform hat ihre Eigenheiten, die man als Entwickler auch kennen muss, und sei es nur, dass man die unterschiedlichen Distributions- und Installationsabläufe kennt. Dennoch sind das spezifische Kenntnisse, die sich schnell lernen lassen, während der Großteil der Arbeit über die schon vorhandene Kompetenz eines professionellen Entwicklers läuft. Dennoch bleibt es eine Herausforderung, wenn man für verschiedene Plattformen arbeitet, sich mit deren Weiterentwicklung und den Updates zu beschäftigen.

Aha-Effekt für .NET-Entwickler

Obwohl Jörg Neumann im UI-Geschäft ein alter Hase ist und viele Plattformen im Detail kennen gelernt hat, bekennt er im Videointerview, dass er einen Aha-Effekt erlebt hat, als .NET-Entwickler für jede Plattform entwickeln zu können.
Sein Wissen und seine Begeisterung teil Jörg mit den Teilnehmern der BASTA! in seinem Xamarin-Workshop Xamarin.Forms Deep Dive (29.09.2017) sowie dem erstmals veranstalteten Xamarin Day (27.09.2017) und dem UI Day (28.09.2017) auf der BASTA! 2017.

 

 

The post Xamarin ist ein Quantensprung für .NET-Entwickler appeared first on BASTA!.

]]>
Jubiläums-Dossier https://basta.net/blog/jubilaeums-dossier/ Wed, 26 Apr 2017 13:22:28 +0000 https://basta.net/?p=19723 Das neueste BASTA!-Dossier gibt es jetzt exklusiv als kostenlose PDF-Datei. Jetzt für den Newsletter anmelden, und Download-Link per Email erhalten!

The post Jubiläums-Dossier appeared first on BASTA!.

]]>
In unserem Jubiläums-Dossier 2017 schreiben Experten aus über 20 Jahre BASTA! zu und über aktuelle Trends der .NET und Microsoft-Technologie Welt. Lesen Sie das Dossier und übernehmen Sie Insights und Erfahrungen unserer Speaker.

60 Seiten voller .NET und Open Innovation

  • Wie statisch muss Typisierung sein?
  • ViewModels testgetrieben entwickeln
  • Echtzeitchat mit Node.js und Socket.IO
  • Entity Framework 1.0
  • TypeScript – Die Alternative für JavaScript-Hasser
  • Conversational User Interfaces mit Microsoft’s Bot Framework

Erhalten Sie praxisbezogene Insights zu .NET, Azure, TypeScript, Node.js und mehr.

 

Inhaltsverzeichnis

    • 1. .NET Framework & C#
      Tolle Typen – Wie statisch muss Typisierung sein
      von Oliver Sturm

 

 

    • 3. Web Development
      Echtzeitchat mit Node.js und Socket.IO
      von Manuel Rauber

 

    • 4. Data Access & Storage & JavaScript
      Entity Framework Core 1.0
      von Manfred Steyer

 

 

    • 6. User Interface
      Das GeBOT der Stunde?
      von Roman Schacherl und Daniel Sklenitzka

 

 

The post Jubiläums-Dossier appeared first on BASTA!.

]]>
Microservices: Reden ist Gold https://basta.net/blog/reden-ist-gold/ Wed, 26 Apr 2017 08:50:46 +0000 https://basta.net/?p=20017 In dem Architekturworkshop von Oliver Sturm bei der letzten BASTA! ging es unter anderem um Microservices. Ein Thema der Stunde ist das natürlich, das Architekturpattern dieser Zeit. Oliver hat in seiner Kolumne "Olis bunte Welt der IT" schon zuvor über Microservices und deren Möglichkeiten geschrieben. Im Zuge seines Workshops griff Oliver schließlich die Frage auf: „Wie baue ich denn all diese Dienste und wie reden die dann miteinander?“

The post Microservices: Reden ist Gold appeared first on BASTA!.

]]>

Der Microservices & Services Track auf der BASTA! 2017

„Microservices“ ist ein großes Wort, auch wenn es so klein klingt. Im Wesentlichen geht es um Dienste, und zwar um kleine Dienste. Wie klein die sein sollen, ist eine Frage, die oft und gern gestellt wird, und zu der es keine einfache, kurze und pauschale Antwort gibt. Wie so oft muss zunächst ein Verständnis her zum Pattern, bevor Fragen im Detail beantwortet werden können. Und bei Patterns ist es zum Verständnis immer wichtig, den Zweck der Sache zu beleuchten. Also sollten wir fragen: „Warum machen wir das?“, und nicht: „Wie geht das?“

Die Idee der Microservices stammt aus der Erkenntnis, dass Softwareentwicklung und deren Planung oft etwas realitätsfern sind. Ähnlich den agilen Ideen setzt man sich hier zum Ziel, eine große Aufgabe in kleine Teile zu unterteilen. Bei agiler Planung geht es um Abläufe und bei Microservices um Strukturen. Wo bisher Softwareplanung oft monolithisch betrachtet wurde, soll nun alles logisch in kleine Elemente zerlegt werden. Früher gab es Pläne, im Laufe der nächsten drei Jahre ein gewaltiges Stück Software zu bauen, das dann für die nächsten fünfzehn Jahre erfolgreich verkauft werden sollte. Irgendwann funktionierte das vielleicht auch mal, und als Unternehmensplan ist die Vorlage „fünfzehn Jahre gute Software verkaufen“ auch sicher sinnvoll. Aber mit der Realität von Kunden wie auch Entwicklern lässt sich dieser Ansatz auf technischer Ebene heute nicht mehr gut vereinbaren.

Klein machen

Die Probleme kennen Entwickler wie auch Softwaredesigner gut. Um ein Stück Software zu erzeugen, brauchen wir eine Plattform, eine Programmiersprache, verschiedene hilfreiche Libraries mit Zusatzfunktionalität und natürlich Entwickler, die sich ausführlich in diese Bauteile einarbeiten, zu Experten heranwachsen und langfristig für die Firma am Projekt arbeiten. In der wirklichen Welt ändern sich die Anforderungen allerdings leider ständig: Die Plattform der Wahl ist in ein paar Jahren vielleicht nicht mehr diejenige, mit der die meisten Endanwender arbeiten. Die Programmiersprache stellt sich längerfristig eventuell als falsche Wahl heraus, zumindest, wenn man gleichzeitig von neuen Entwicklungen profitieren möchte, die andere Entwickler verfügbar machen. Die Libraries sind meist an Plattform und Sprache gebunden, und die meisten Entwickler haben zwar gewissen Respekt vor dem Neuen. Sie wollen sich aber persönlich einen Platz in ihrem Job sichern, indem sie technisch am Ball bleiben und sind daher nicht endlos bereit, einem Weg zu folgen, der aus ihrer Sicht nicht mehr dem Stand der Technik entspricht. So wird die Pflege einer alten Software im Laufe der Zeit immer teurer, die Kunden werden immer unzufriedener, während die langfristige Planung und die monolithische Architektur intern weitreichende Überarbeitungen oder gar eine Neuentwicklung zum Tabuthema werden lassen.

Grundsätzlich kann ein Dienst auf jeder Plattform, in jeder Programmiersprache implementiert werden.

Microservices setzen auf Unabhängigkeit zwischen den Modulen eines Softwaresystems. Es sollen Dienste gebaut werden, die einzelne Aufgaben übernehmen und die voneinander entkoppelt sind. Die Aufgaben eines Dienstes sollen kurz und bündig definierbar sein; meistens denken wir dabei an eine einzelne Funktion mit einer Aufrufschnittstelle. Dadurch beantwortet sich die Frage nach der Größe eines kleinen Dienstes. Da jeder Dienst von allen anderen möglichst unabhängig ist, ergeben sich weitreichende Freiheiten: Plattform, Programmiersprache und Hilfs-Libraries können womöglich pro Dienst neu definiert werden. In einem neuen Konzept werden Sie vermutlich nicht so weit gehen wollen, denn immerhin gilt es, den Kenntnisstand von Mitarbeitern zu berücksichtigen, eventuell vorhandenen Code wiederzuverwenden und dergleichen mehr. Aber Sie gewinnen die Freiheit, später andere Entscheidungen treffen zu können – oder in gewissen Einzelfällen auch sofort. .NET Version X ist gerade erschienen und kann irgendwas Neues, von dem Sie technisch profitieren könnten? Dann kann ein Dienst oder eine Gruppe von Diensten auf .NET Version X umgestellt werden und mit der neuen Technik arbeiten, ohne dass andere Teile Ihres Systems geändert werden müssen. Vielleicht ist der Arbeitsmarkt plötzlich überschwemmt von Programmierern, die gut Python können – davon können Sie als Unternehmen profitieren, denn Teile eines Microservices-Systems können natürlich in Python programmiert werden.

Wenn nötig, einfach neu

Mit Microservices wird Wegwerfen zur Kultur. Nicht unbedingt zur Tugend, denn Neumachen kostet immer Geld, aber eben nicht mehr zur Unmöglichkeit. Wie Firmen in anderen Geschäftsfeldern etwa ein Auto bis zur Unkenntlichkeit umbauen, durch den Einbau von Spezialteilen für die Rallyeteilnahme, oder so wie eine PCI-Karte viele Jahre nach Erfindung des PCI-Busses mit einer Hardware kommunizieren kann, die es damals noch gar nicht gab, so lässt sich eine Software, die auf Microservices basiert, flexibel und modular über Jahre hinweg pflegen.
Diese Überlegungen bringen mich nun zurück zur eingangs angesprochenen Frage: „Wie sehen diese vielen Dienste denn aus?“ Die Antwort dazu ist, natürlich, ebenfalls vielschichtig. Grundsätzlich kann ein Dienst auf jeder Plattform, in jeder Programmiersprache implementiert werden. Technisch handelt es sich dabei ja „nur“ um eine Funktion (oder womöglich um ein „Netz“ von Funktionen), die einige Parameter übergeben bekommt und ein Resultat liefert. Das ist ein funktionaler Ansatz im Sinne der funktionalen Programmierung, wo Interaktion zwischen Funktionen außerhalb des simplen Schemas von Übergabeparametern und Rückgabewerten verpönt ist.

Es sollen Dienste gebaut werden, die einzelne Aufgaben übernehmen und die voneinander entkoppelt sind.

;

Interaktion muss her

Von größerem technischen Interesse als die Implementierung einzelner Dienste ist deshalb die Frage, wie diese Dienste miteinander interagieren. Um die Flexibilität zu erreichen, die das Pattern verspricht, muss dazu eine Aufrufschnittstelle her, die sich „von außen“, also von anderen Diensten, ansprechen lässt. Diese Schnittstelle muss plattformunabhängig sein, auch was den Austausch von Daten bei Über- und Rückgabe angeht. Verbreitet wird zu diesem Zweck heute die Kombination von REST als URL-basiertem Aufrufschema und JSON als effizientem Datenübertragungsformat eingesetzt. Diese Kombination ist technisch simpel, und es gibt Libraries auf sehr vielen Plattformen, die den Umgang mit REST und JSON einfach machen. Selbst wenn es solche Libraries nicht geben sollte, wären diese Mechanismen, die auf HTTP bzw. einem simplen Textformat basieren, noch immer mit wenig Aufwand nutzbar. Auf komplexe Protokolle aus der SOA-Welt wird meist bewusst verzichtet, um die Einstiegshürde so niedrig wie möglich zu halten.

In .NET können Sie einen Dienst, der mit REST und JSON arbeiten soll, einfach mithilfe von Microsofts ASP.NET-Web-API verfügbar machen. Das funktioniert auch mit .NET Core, sodass Sie plattformunabhängig sein und auch Dienste mit Docker paketieren und in beliebigen Cloud-Umgebungen laufen lassen können. Alternativ lassen sich dieselben Ergebnisse mit Nancy oder anderen Libraries auf der .NET-Plattform erzielen. Ähnliche Libraries gibt es auch für andere Plattformen: Express für JavaScript und Node.js, Django für Python, Sinatra für Ruby und viele andere mehr. Wie auch Microsoft ASP.NET MVC haben die genannten Libraries Funktionalität weit jenseits der einfachen Erstellung von REST/JSON-Diensten, sodass sie sich vielfältig einsetzen lassen.
Nachdem Dienste nun „adressierbar“ sind, besteht der nächste Schritt darin, über die Struktur des gesamten Anwendungssystems nachzudenken. Um einen Dienst direkt mit einem anderen kommunizieren zu lassen, muss zumindest einer der Dienste wissen, wo der andere zu finden ist. In einem komplexen Netz von Diensten ist diese Anforderung schwierig umzusetzen, und die Situation wird wesentlich komplizierter, wenn etwa Mechanismen zur Lastverteilung oder zur Skalierung von Diensten oder Dienstgruppen zum Einsatz kommen. Daher nutzen viele Microservices-Konzepte einen Vermittler, englisch Broker, der Nachrichten entgegennehmen und an Dienste weiterleiten kann. Der Broker ist natürlich selbst ein Dienst und passt somit gut ins Konzept.

Broker vermitteln zwischen Diensten

Broker schreiben die meisten Programmierer nicht selbst, da es bereits viele vorhandene Lösungen gibt. Zum Beispiel sind das Message-Queue-Systeme wie RabbitMQ, Redis oder Microsoft MSMQ, oder auch Dienste, die von einer Cloud verfügbar gemacht werden, wie Amazon SQS oder Azure Queue Storage bzw. Service Bus. Viele Konzepte setzen direkt auf eine dieser Technologien, andere verwenden eine Abstraktionsschicht wie NServiceBus für .NET, um sich nicht von einer bestimmten Broker-Implementation abhängig zu machen. Viele Lösungen unterstützen auch das standardisierte Protokoll AMQP, mit dem ein Client einen Broker ansprechen kann, ohne dessen Implementatierung zu kennen. Letztlich ist es auch möglich, einen Broker wiederum per REST anzusprechen, um das Gesamtsystem so offen wie möglich zu halten.

Von größerem technischen Interesse als die Implementierung einzelner Dienste ist deshalb die Frage, wie diese Dienste miteinander interagieren.

Wenn Sie meine Kolumne regelmäßig verfolgen, erinnern Sie sich bestimmt an meinen Artikel zum Thema Akka.NET [1], der Implementierung eines Aktorenframeworks für .NET. Ein System, das auf Microservices basiert und einen Broker zum Austausch von Informationen zwischen Diensten verwendet, zeigt bemerkenswerte Parallelen zu den Ideen der Aktoren. Letztere verarbeiten ebenfalls Nachrichten und sind voneinander vollständig entkoppelt, während die Infrastruktur für den Austausch von Informationen durch asynchrone Mechanismen sorgt – und Akka.NET bietet eigene Möglichkeiten zur verteilten Ausführung von Aktoren.

Aktoren sind auch Dienste, aber anders

Es gibt zwei wichtige Unterschiede zwischen typischen Aktorensystemen und Microservices. Erstens bietet ein Aktorensystem viel organisatorische Funktionalität, die den Betrieb des Gesamtsystems stabiler macht. Es werden Hierarchien von Aktoren aufgebaut, in denen ein „Parent“ jeweils für den Betrieb seiner „Kinder“ verantwortlich ist, und es gibt Überwachungs- und Benachrichtigungsfunktionen, mit deren Hilfe der Lebenszyklus einzelner Aktoren kontrolliert werden kann. Damit sind Aktoren, wenn auch funktional eigenständig, wesentlich stärker in das Gesamtsystem eingebunden, als das für Microservices wünschenswert ist. Letztlich sind diese Grenzen natürlich fließend und manche Microservices-Systeme bauen ebenfalls Gruppen von Diensten auf, die stark voneinander abhängig sind.

Der zweite wichtige Unterschied ergibt sich aus dem ersten: Die Protokolle, die ein Aktorensystem zum Datenaustausch zwischen Aktoren verwendet, sind oft komplexer und darauf ausgerichtet, relevante Verwaltungsinformationen zusammen mit den Nutzdaten zu übertragen. Technisch ist es durchaus möglich, beliebige Kommunikationsprotokolle in einem Aktorensystem zu verwenden und manche Entwickler benutzen Aktorensysteme gemeinsam mit Message-Queue- oder Service-Bus-Varianten. Allerdings darf nicht übersehen werden, dass die Versprechungen von Aktorensystemen in Hinsicht auf die langfristige Laufstabilität letztlich auf den implementierten Konzepten zur Dienstüberwachung und Lebenszyklusverwaltung basieren. Während also ein externer Dienst sich technisch durchaus in ein solches System „einklinken“ kann, muss er sich dann entsprechend den Erwartungen dieses Systems verhalten und er verliert dabei einen Großteil der Unabhängigkeit, die ein eigenständiger Dienst haben sollte.

Daher nutzen viele Microservices-Konzepte einen Vermittler, englisch Broker, der Nachrichten entgegennehmen und an Dienste weiterleiten kann. Der Broker ist natürlich selbst ein Dienst und passt somit gut ins Konzept.

Trotz dieser Betrachtungen lassen sich Aktoren natürlich innerhalb eines Microservices-Systems einsetzen. Immerhin geht es dort besonders um den Einsatz geeigneter Technologien für jedes einzelne Teilsystem, und mithilfe von Aktoren lassen sich etwa Datenverarbeitungsalgorithmen stabil und modular umsetzen, deren externe Schnittstellen dann in Form eines oder mehrerer Dienste angeboten werden können. Mit Akka.Cluster gibt es in diesem Bereich sogar ein Modul, das auf Basis von Aktorensystemen automatisch Gruppen von Aktorensystemen verteilen kann, basierend allerdings noch immer auf den komplexen, wenn auch optimierten, internen Protokollen. Die Firma Lightbend, involviert in die Entwicklung von Akka für Java, bietet mit Lagom ein neues Framework an, das auf Basis von Akka Microservices implementiert. Diese Lösung ist derzeit für .NET meines Wissens noch nicht verfügbar.

Zum Abschluss möchte ich ein Framework ansprechen, das ich für eine sehr leistungsfähige Basis für Microservices halte. Dabei handelt es sich um das in JavaScript programmierte Seneca, das eine besonders interessante Kombination von Features bietet. Sie konfigurieren beim Einsatz von Seneca Dienste, die mithilfe von Pattern-Matching auf Nachrichten reagieren können und bei deren Empfang ihre Logik ausführen und Resultate liefern. Der Umgang mit dem API von Seneca ist sehr einfach und so gelingt es schnell, ein Netzwerk von Diensten aufzubauen. Nun hat Seneca weiterhin die Fähigkeit, auf Basis der Patterns, auf die die Dienste antworten, auch Routen zu definieren, die automatisch bestimmte Nachrichten über externe Wege weiterleiten, die auf Basis zahlreicher Plug-ins flexibel konfigurierbar sind. Natürlich können Sie Dienste über REST-URLs zugreifbar machen, aber auch die Verwendung einer Message Queue ist mit zwei Zeilen Code eingerichtet. Jedes Dienstprogramm, groß oder klein, kann so auf einfache Weise manche Dienste direkt lokal ansprechen und andere „remote“ über unterschiedliche Protokolle, ohne dass ein Dienst selbst etwas davon wissen muss, wie das Gesamtsystem aufgebaut ist.

Fazit

Microservices stellen ein mächtiges und vielschichtiges Pattern dar, von dem beinahe jedes Anwendungssystem profitieren kann. Die Frage nach der Kommunikation in einem solchen System ist eine der wichtigsten und die Antworten sind vielfältig, weil sie unterschiedliche Anwendungsfälle abdecken. Es empfiehlt sich in jedem Fall, nach Hilfsmitteln auf den Plattformen der Wahl Ausschau zu halten, da die Verwendung von Standardsystemen wie RabbitMQ, Cloud-Infrastruktur wie Amazon SQS oder Azure Service Bus und Libraries wie Seneca Ihnen viel Arbeit ersparen kann.

[1] „Olis bunte Welt der IT“, Windows Developer 11.2016, S. 14

;


;

The post Microservices: Reden ist Gold appeared first on BASTA!.

]]>
2017 ist ein Jubiläumsjahr für die BASTA! https://basta.net/blog/2017-ist-ein-jubilaeumsjahr-fuer-die-basta/ Thu, 30 Mar 2017 08:55:31 +0000 https://basta.net/?p=19216 Im Herbst 2017 feiert die BASTA! ihr 20-jähriges Bestehen. In einer schnelllebigen Industrie wie der IT haben wir in zwanzig Jahren viele Firmen, Veranstaltungen und Technologien kommen und gehen sehen. Vieles hat sich verändert, manches ist geblieben und schon Totgesagte sind wieder aktuell. Die BASTA! ist geblieben, stetig gewachsen, stand und steht mit ihrem Programm in guten wie in schlechten Zeiten .NET-Entwicklern zur Seite.

The post 2017 ist ein Jubiläumsjahr für die BASTA! appeared first on BASTA!.

]]>

Der Microservices & Services Track auf der BASTA! 2017

An die guten Zeiten erinnert man sich gerne, die schlechten bleiben oft besser im Gedächtnis. Gerade Microsoft sorgt für schmerzliche Erinnerungen, wenn es selbst vielversprechende Technologien verworfen hat, die gerade noch marktführend waren – es sei nur an Silverlight erinnert. Der große Shift über Windows 8 zu Windows 10 oder die resolute Einführung von Azure sind weitere Beispiele für große Veränderungen. Aber auch wenn Microsoft den Kurs radikal geändert hat, wussten unsere Speaker immer Rat und haben den Teilnehmern praktische und schnelle Lösungen präsentiert, um die Hürden gekonnt zu nehmen. In diesem Geiste ist die BASTA! zwar älter geworden, aber nicht gealtert. Sie ist immer flexibel mit der Zeit gegangen, hat die neuen Entwicklungen vorausgespürt, den Teilnehmern genau die Informationen gegeben, die sie heute und morgen für ihre Arbeit brauchen.

In diesem Herbst ist die BASTA! daher nicht nur wieder der richtige Ort für Weiterbildung und Networking, es ist der richtige Ort zum Feiern. Eine Konferenz dieser Größe und Bedeutung macht man nicht allein, ein treues Publikum, engagierte Speaker und verlässliche Partner zählen auch dazu – ihnen gilt unser Dank.

21375394683_fd5e4a9145_k

Feste feiern

Feste muss man feiern, wie sie fallen. Und das werden wir auch tun. Wir feiern in diesem Herbst 20 Jahre BASTA!, und Sie sind alle herzlich dazu eingeladen. Was 1997 als BASic TAge gestartet ist, hat sich in zwei Jahrzehnten zur größten unabhängigen, deutschsprachigen Konferenz rund um Microsoft-Technologien entwickelt und ist in dieser Zeit für viele Leute zu einer festen Größe im Jahresplan geworden.

Darauf sind wir stolz. Das wollen wir feiern, zusammen mit Ihnen, unseren Teilnehmern, Sprechern, Partner und Veteranen. Wir laden Sie alle zu einer großen Oktoberfest-Feier am Dienstagabend der BASTA! 2017 in Mainz ein und bieten neben kulinarischen Genüssen und dem einen oder anderen gepflegten Bier unterhaltsame Rückblicke und Einblicke in die 20-jährige BASTA!-Geschichte. Freuen Sie sich auf ein Wiedersehen mit Sprechern und Partnern der ersten Stunde.

Praxisorientiert, am Puls, wenig Marketing und viel Mehrwehrt

Eins ist und bleibt klar, die BASTA! ist auch nach zwanzig Jahren nie rückwärtsgewandt. Fest in der Gegenwart verortet, sind wir auch immer einen Schritt in der Zukunft. Wir präsentieren in den zahlreichen Keynotes, Sessions und Workshops das beste und aktuellste Know-how, das professionelle Entwickler heute und morgen brauchen. Darauf können Sie auch die nächsten zwanzig Jahre zählen.

Auf diese Weise hat sich auch die BASTA! gewandelt und sich immer wieder an neuen Entwicklungen und wichtigen Trends orientiert. Das sieht man natürlich am besten am breit gefächerten Programmangebot der BASTA!. Vor einigen Jahren haben wir beispielsweise zusammen mit Christian Weyer den Modern Business Application Day entwickelt und waren damit Vorreiter einer Cross-Plattform-Entwicklung, die heute fast schon Mainstream ist. Ist doch inzwischen den meisten bewusst, dass sie nicht mehr nur für eine Plattform entwickeln und dabei auch nicht mehr nur auf eine Technologie setzen können. Vom klassischen .NET über Web-APIs, Azure, JavaScript und HTML5 (https://basta.net/html5-javascript/) werden heute Apps und Lösungen entwickelt, die keine Grenzen mehr kennen. Der im Herbst erstmals angebotene Xamarin Day mit Jörg Neumann bestärkt diese Entwicklung. Ganz egal ob iOS, Android oder Windows – für C#-Entwickler ist das Bauen von Lösungen für jedes Betriebssystem zum Heimspiel geworden.

Aber auch in traditionellen Bereichen verändert sich die Entwicklung. Fast von Anfang an ist Holger Schwichtenberg, der Dotnet Doktor aka Mr. Entity Framework, als Sprecher bei der BASTA! dabei. Auf seine Expertise in Sachen .NET zählen wir auch heute z. B. im Data Access Day. 15 Jahre nach Veröffentlichung des großen .NET Frameworks führt Microsoft mit .NET Core eine neue Plattform ein, die Entwicklern noch mehr Möglichkeiten bietet und weitere Hürden einreist. Können sie doch damit für Windows, Linux und macOS zugleich entwickeln. Zahlreiche Sessions der BASTA! 2017 zeigen im Detail, wie es geht.

schwichtenberg_core3_1

Neue Technologien, neue Offenheit

Gerade die Beispiele Cross-Plattform und .NET Core zeigen, wie sehr sich die Entwicklung im Microsoft-Umfeld und damit auch die BASTA! in den vergangenen Jahrzehnten gewandelt hat. Von einem geschlossenen, eher autoritativen, manchmal fast alternativlosen/eingrenzenden Technologiestack hin zu einer transparenten, offenen, Open-Source-orientierten und auf Kompatibilität ausgerichteten Angebotsfülle. So sind das .NET Framework und viele Teile von Azure von Microsoft Open Source gestellt worden und werden in Zusammenarbeit mit einer wachsenden Community weiterentwickelt. Auch die Entscheidung für die enge Integration von z. B. Git oder JavaScript sind ein Paradigmenwechsel seitens Microsoft.

Gerade JavaScript ist ein gutes Beispiel. War es bei gestandenen .NET-Entwicklern lange unbeliebt und auch ungeeignet, auf Systemebene damit zu arbeiten, sind es jetzt die zahlreichen Frameworks, die Toolunterstützung und die Entwicklung von TypeScript, die JavaScript fast zur natürlichen Zweitsprache für C#-Entwickler machen. Nicht umsonst ist wahrscheinlich TypeScript von Anders Hejlsberg, dem „Vater“ von C#, entworfen worden. Gerne betont wird in diesem Zusammenhang, dass Googles Angular inzwischen mit TypeScript weiterentwickelt wird. Eine Zusammenarbeit, die man vor wenigen Jahren so nicht erwartet hätte. Noch dazu zählt Angular zu den beliebtesten Frameworks bei den Teilnehmern. Daher finden Sie viele Sessions auf der BASTA!, in denen Angular ein wichtiges Thema ist, z.B. im Web Development Day.

Um zum Schluss den Rahmen noch weiter zu ziehen, darf Azure nicht unerwähnt bleiben. Mit seinem entschlossenen Schritt in die Cloud hat Microsoft ein deutliches Zeichen gesetzt und sich von den Wettbewerbern abgesetzt. Neben Amazons AWS bietet Azure aktuell das größte Cloud-Angebot. Ein Angebot, das .NET-Entwickler nahtlos in ihre Arbeit einbeziehen können. Die BASTA! bietet dazu zahlreiche Sessions zu Themen wie Docker, API, Microservices und mobile Entwicklung, die nicht Azure direkt thematisieren, aber praktische Einsatzmöglichkeiten zeigen, die Entwicklern den Schritt in die Cloud ermöglichen.

Auch hier folgt die BASTA! ihrer Tradition und bleibt Vorreiter und Wegbereiter für neue Technologien. Wie schon gesagt: Alleine kann man so einen Weg nicht gehen. Es braucht schon viele Individualisten, die gemeinsam die Welt verändern wollen. Auf der BASTA! treffen sie sich – seit zwanzig Jahren.


 

 

The post 2017 ist ein Jubiläumsjahr für die BASTA! appeared first on BASTA!.

]]>
TypeScript – Grundlagen für .NET-Entwickler https://basta.net/blog/typescript-grundlagen-fuer-net-entwickler/ Mon, 30 Jan 2017 16:25:09 +0000 https://basta.net/?p=18185 Das von Microsoft entwickelte TypeScript ist eine Obermenge von JavaScript und genießt heute eine große und stetig wachsende Beliebtheit. Google hat beispielsweise das eigene Single-Page-Application-(SPA-)Framework Angular in TypeScript geschrieben. Doch was macht das vom C#-Erfinder Anders Hejlsberg vorangetriebene TypeScript so interessant? Was sind die Vorteile gegenüber klassischem JavaScript?

The post TypeScript – Grundlagen für .NET-Entwickler appeared first on BASTA!.

]]>

Der JavaScript & HTML5 Track auf der BASTA! 2017

JavaScript für Unternehmensanwendungen

Die Sprache JavaScript wurde 1995 in ein paar Wochen entwickelt. Die Idee war, Code auf Webseiten ausführen zu können, um Inhalt dynamisch anzupassen. Damals konnte sich vermutlich niemand vorstellen, dass mit JavaScript Unternehmensanwendungen mit 100 000 oder mehr Zeilen Code umgesetzt werden. Doch das ist heute der Fall. Zwar lassen sich mit JavaScript solche Anwendungen bauen. Doch dabei gibt es einen großen Nachteil: JavaScript ist eine dynamische Sprache, es gibt keine statische Typisierung von Variablen. Die statische Typisierung, die von Sprachen wie C# oder Java bekannt ist, führt bei falschen Zuweisungen zu entsprechenden Compile-Fehlern, und nicht zu Fehlern, die erst zur Laufzeit auftreten. In C# lässt sich aufgrund der starken Typisierung beispielsweise einer String-Variablen kein Integer zuweisen. In JavaScript dagegen lässt sich einer Variablen ein beliebiger Inhalt zuweisen, da die Variable als solche gar keinen Typ hat:

 

let firstName = "Thomas";

firstName = 5; // OK

 

Die firstName-Variable erhält zuerst einen String, dann die Number 5. Das ist in JavaScript völlig in Ordnung und führt zu keinem Fehler. Doch für eine robuste Architektur ist eine statische Typisierung extrem hilfreich, die eben während des Entwickelns und Kompilierens bereits viele Fehler abfangen kann. Und genau diese Typisierung besitzt TypeScript, was auch der Ursprung des Namens ist.

Statische Typisierung

TypeScript stellt eine Obermenge von JavaScript dar. Somit ist jeder JavaScript-Code bereits gültiger TypeScript-Code. Bei einer Migration von JavaScript nach TypeScript werden üblicherweise einfach Typen eingeführt, um mögliche Fehler bereits zur Compile-Zeit abzufangen. Die firstName-Variable aus dem gezeigten Beispiel lässt sich in TypeScript mit einer so genannten Typannotation versehen. Der Typ wird dabei nach dem Variablennamen angegeben. Variablennamen und -typ werden mit einem Doppelpunkt getrennt. Der nachfolgende Code zeigt die firstName-Variable mit der Angabe des string-Typs. Wird in TypeScript dieser Variablen ein Wert vom Typ number zugewiesen, führt dies im Gegensatz zu JavaScript zu einem Compile-Fehler:

 

let firstName: string = "Thomas";

firstName = 5; // Kompilierfehler

 

Die statische Typisierung bringt neben Compile-Fehlern weitere Vorteile: Tools wie Visual Studio Code können deutlich bessere Unterstützung als bei reinem JavaScript bieten, weil der Typ zur Entwicklungszeit schon feststeht. Somit gibt es Funktionen wie IntelliSense, Go to Definition und vieles mehr, die das nutzen können.

TypeScript kompilieren

Neben der statischen Typisierung hat TypeScript ein weiteres sehr wichtiges Merkmal: TypeScript kompiliert zu reinem JavaScript-Code. Das heißt, dass TypeScript lediglich zur Entwicklungszeit wichtig ist. Zur Laufzeit benutzt der Browser klassisches JavaScript, das aus TypeScript kompiliert wurde. Doch jetzt kommt der eigentliche Clou: Mit TypeScript lässt sich die erstellte JavaScript-Version bestimmen. So kann nach ES2015 oder auch nach ES5 kompiliert werden. Dies ist insbesondere interessant, wenn man bedenkt, dass Browser immer eine Weile brauchen, bis der letzte ECMAScript-Standard implementiert ist. Und ist der Standard implementiert, ist noch nicht gewährleistet, dass die User auch die letzte Browserversion installiert haben, mit der der JavaScript-Code ausgeführt werden kann und richtig funktioniert. Mit TypeScript ist dies kein Problem mehr, da sich die neuen Features einfach in eine ältere, von allen gängigen Browsern unterstützte JavaScript-Version kompilieren lassen.

Beispielsweise wurden mit ES2015 Klassen eingeführt, um objektorientiert zu programmieren. Klassen sind somit auch in TypeScript verfügbar. Doch der TypeScript-Code lässt sich nicht nur nach ES2015 kompilieren, sondern auch nach ES5. Somit lassen sich Klassen in TypeScript wie in ES2015 nutzen, anschließend aber nach ES5 kompilieren, was von den gängigen Browsern unterstützt wird.

Mit der statischen Typisierung erhalten Entwickler Fehler bereits während des Entwickelns. Ebenso gibt es zahlreiche Features im Tooling, wie IntelliSense, Go to definition und mehr. Ein weiterer Vorteil der statischen Typisierung ist, dass der Code selbst-dokumentierend ist. Hat ein Funktionsparameter einen Typ, ist klar, was an die Funktion übergeben werden muss. Gibt es dagegen wie in JavaScript keinen Typ, bedarf es Kommentaren, die beschreiben, was die Funktion eigentlich verlangt. Da das Kompilat von TypeScript reines JavaScript ist, gibt es keinen Grund, warum man in JavaScript-Projekten nicht auf TypeScript setzen sollte.

 

Was man wie mit TypeScript machen kann, zeigt Thomas Claudius Huber in seinem Workshop “Einführung in TypeScript: von 0 auf 100 in einem Tag”, am Freitag, den 24.2.2017


 

 

The post TypeScript – Grundlagen für .NET-Entwickler appeared first on BASTA!.

]]>
Visual Studio Team Services und Team Foundation Server erweitern und anpassen https://basta.net/blog/visual-studio-team-services-und-team-foundation-server-erweitern-und-anpassen/ Mon, 16 Jan 2017 15:33:37 +0000 https://basta.net/?p=18078 Visual Studio Team Services bzw. der Team Foundation Server sind mittlerweile sehr offene und interoperable Application-Lifecycle-Management-Produkte aus dem Haus Microsoft. Seit der anfänglichen Anpassbarkeit von Work-Item-Typen wurde der Funktionsumfang in Sachen Integration und Erweiterbarkeit stark vergrößert. Seit Kurzem kann auch die Weboberfläche nach Belieben erweitert werden. Mit Update 3 können nun auch für den Team Foundation Server 2015 eigene Dashboard-Widgets erstellt werden. Es ist also an der Zeit, einen Blick auf die neuen Möglichkeiten zu werfen.

The post Visual Studio Team Services und Team Foundation Server erweitern und anpassen appeared first on BASTA!.

]]>
Ein anpassbares und erweiterbares ALM-Tool zu haben, ist heutzutage für den Erfolg von wesentlicher Bedeutung. Die Möglichkeiten, Work-Item-Typen zu erweitern oder gar neue Entitäten zu erstellen, kennt vermutlich jeder. Dieser Artikel legt den Fokus auf die Anpassung der Weboberfläche sowie auf die Integration von eigenen Services in Visual Studio Team Services (VSTS) und Team Foundation Server (TFS). Abbildung 1 zeigt, welche Integrationspunkte zur Verfügung stehen.

Wie oft hatte man sich in der Vergangenheit gewünscht, an einer bestimmten Stelle einen zusätzlichen Button hinzuzufügen oder gar eigene Views einzublenden. Diese Möglichkeit ist mit der aktuellen Version nun gegeben. Die Erweiterungen in der Oberfläche enthalten Code in Form von JavaScript zur Interaktion mit dem System, die Visualisierung wird mittels HTML implementiert. Eigene Backend-Services können damit aber nicht integriert werden. Sobald Servicelogik erforderlich ist, kann dies nur als externer Service implementiert werden, also z. B. als eigenständige Webapplikation.

Diese Applikationen können über die REST-Schnittstelle mit dem VSTS bzw. TFS interagieren. Zur Benachrichtigung der externen Applikation stehen so genannte WebHooks zur Verfügung. Zu guter Letzt können dem TFS bzw. VSTS noch eigene Build-Tasks über die Extensibility-Schnittstelle zur Verfügung gestellt werden. Weitere Details zu den einzelnen Erweiterungspunkten sind auf der Extensibility-Webseite von VSTS zu finden. Neben einer detaillierten Auflistung werden entsprechende Beispiele zur Verfügung gestellt.

Abb. 1: Übersicht über die Integrationspunkte von Extension in VSTS/TFS

Abb. 1: Übersicht über die Integrationspunkte von Extension in VSTS/TFS

Dashboard-Widget über ein REST-API erstellen

In diesem Artikel wird die Extensibility-Schnittstelle anhand eines eigenen Dashboard-Widgets erklärt. Um die Komplexität klein zu halten, wollen wir ein einfaches Widget in Form einer Build-Ampel erstellen (Abb. 2). Die Ampel kommuniziert mit dem Build-System über ein REST-API, um den aktuellen Status periodisch zu erfragen. Da wir für verschiedene Build-Definitionen verschiedene Ampeln auf dem Dashboard positionieren möchten, benötigen wir ebenfalls eine Konfigurationsmöglichkeit für den Nutzer, in der er die Größe des Widgets sowie die gewünschte Build-Definition auswählen kann.

Abb. 2: Build-Traffic-Lights-Widget als Beispiel für diesen Artikel

Abb. 2: Build-Traffic-Lights-Widget als Beispiel für diesen Artikel

UI-Extensions bestehen primär aus HTML und JavaScript und den üblichen Webartefakten wie Bildern und CSS-Dateien. Damit VSTS respektive TFS weiß, was wie und wo intergiert werden muss, beinhaltet jede Extension ein Manifest. Das Manifest, das als JSON-Datei definiert wird, beinhaltet alle Informationen, Dateispezifikationen sowie Integrationspunkte. Dazu kommt die Definition der Scopes, also die Definition der Zugriffsrechte der Erweiterung. Vor der Installation wird der Nutzer darüber informiert, auf was die Erweiterung Zugriff wünscht, und muss dies entsprechend bestätigen. Ist eine Erweiterung einmal ausgerollt, kann der Scope nicht mehr verändert werden. So wird verhindert, dass durch ein Update plötzlich mehr Berechtigungen als einmal bestätigt eingefordert werden können.


Melden Sie sich für unseren Newsletter an und erfahren Sie als Erster, wann die nächste BASTA! online geht.
[mc4wp-simple-turnstile]



Generell wird die Sicherheit großgeschrieben. Eine UI-Erweiterung läuft immer „sandboxed“ in einem dedizierten iFrame und erhält über ein SDK Zugriff auf den VSTS/TFS – samt Services und Events. Sorgen wegen der Authentifizierung muss man sich bei der Verwendung des SDK keine machen, da der aktuelle Nutzerkontext übernommen wird. Jeder API-Call wird gegenüber den definierten und durch den Nutzer bestätigten Scopes geprüft und bei einem Fehlverhalten blockiert. Damit alle Artefakte sowie das Manifest ausgeliefert und installiert werden können, erstellen wir als Artefakt ein Paket in Form einer VSIX-Datei. Diese kann manuell auf den TFS geladen oder über den VSTS Marketplace vertrieben werden.

Grundsätzlich können wir die Erweiterung mit jedem Texteditor erstellen, jedoch wollen wir auch in diesem kleinen Beispiel den Entwicklungsprozess gleich sauber aufgleisen und verwenden hierzu Visual Studio. Für die Libraries und Zusatztools verwenden wir den Node Package Manager (npm). Damit wir die Paketerstellung automatisieren können, kommt Grunt als Task-Runner zum Einsatz. Wer das alles nicht von Hand aufbauen möchte, kann die Visual-Studio-Erweiterung VSTS Extension Project Templates installieren. Hiermit verfügt Visual Studio über einen neuen Projekttyp, der das Erweiterungsprojekt mit allen Abhängigkeiten und Tools korrekt aufsetzt. Zudem möchten wir in unserem Beispiel nicht direkt JavaScript-Code schreiben, sondern TypeScript zur Implementierung verwenden. Abbildung 3 zeigt die Projektstruktur in Visual Studio für unsere Erweiterung.

Abb. 3: Projektstruktur des Build-Traffic-Lights-Widgets

Abb. 3: Projektstruktur des Build-Traffic-Lights-Widgets

Unsere Manifestdatei befüllen wir mit den üblichen Informationen zum Autor sowie der Beschreibung der Erweiterung. Von entscheidender Bedeutung sind der Scope für die Berechtigungen sowie die Contribution Points, in denen die eigentliche Integration beschrieben wird. Da wir auf Build-Informationen lesend zugreifen möchten, wählen wir den Scope vso.build_execute. Als Contribution Points definieren wir zwei UI-Elemente: ein Widget-UI sowie ein Konfigurations-UI. In Listing 1 sind die entsprechenden Stellen fett hervorgehoben.

{
  "manifestVersion": 1,
  "id": "BuildTrafficLights",
  "version": "0.1.16",
  "name": "Build Traffic Lights",
  "scopes": [ "vso.build_execute" ],
  ...
  "contributions": [
    {
      "id": "BuildTrafficLightsWidget",
      "type": "ms.vss-dashboards-web.widget",
      "targets": [
        "ms.vss-dashboards-web.widget-catalog",
        "4tecture.BuildTrafficLights.BuildTrafficLightsWidget.Configuration"
      ],
      "properties": {
        "name": "Build Traffic Lights Widgets",
        "uri": "TrafficLightsWidget.html",
        ...
        "supportedScopes": [
          "project_team"
    ]}},
    {
      "id": "BuildTrafficLightsWidget.Configuration",
      "type": "ms.vss-dashboards-web.widget-configuration",
      "targets": [ "ms.vss-dashboards-web.widget-configuration" ],
      "properties": {
        "name": "Build Traffic Lights Widget Configuration",
        "description": "Configures Build Traffic Lights Widget",
        "uri": "TrafficLightsWidgetConfiguration.html"
}}]}

Aufgrund des Manifests wird unsere Erweiterung richtig im Widgetkatalog aufgeführt, und aufgrund der Contribution Points werden die richtigen HTML-Dateien für die UI-Erweiterungen angezogen. Die Implementierung der Views ist nichts anderes als klassische Webentwicklung. Jedoch ist die Integration der Komponente über das zur Verfügung gestellte SDK von entscheidender Bedeutung.

Das SDK liegt uns in Form einer JavaScript-Datei (VSS.SDK.js) in unserem Projekt vor. Da wir mit TypeScript arbeiten, laden wir ebenso die dazugehörigen Typinformationen. Nach der Referenzierung der Skriptdatei in unserer HTML-Datei können wir nun über das VSS-Objekt die Erweiterung registrieren. Zudem geben wir VSTS/TFS bekannt, wie die Runtime unsere JavaScript-Module finden und laden kann. In unserem Fall ist der gesamte Code in TypeScript implementiert und wird als AMD-Modul zur Verfügung gestellt. Da wir unsere Build-Ampel dynamisch in unserem TypeScript-Code aufbauen werden, enthält die HTML-Datei nur das Grundgerüst des Widgets (Listing 2).

Listing 2: "TrafficLightsWidget.html"
<script type="text/javascript">
    // Initialize the VSS sdk
    VSS.init({
      explicitNotifyLoaded: true,
      setupModuleLoader: true,
      moduleLoaderConfig: {
        paths: {
          "Scripts": "scripts"
        }
      },
      usePlatformScripts: true,
      usePlatformStyles: true
    });
    // Wait for the SDK to be initialized
    VSS.ready(function () {
      require(["Scripts/TrafficLightsWidget"], function (tlwidget) {
      });
    });
 </script>

Damit unser Widget und seine Controls nicht vom Look and Feel von VSTS/FS abweichen, gibt uns das SDK die Möglichkeit, über WidgetHelpers.IncludeWidgetStyles() die Styles von VSTS/TFS zu laden und in unserem Widget zu verwenden. Das erspart uns einiges an Designarbeit. Listing 3 zeigt zudem das Auslesen der Konfiguration sowie der Instanziierung unseres Ampel-Renderers TrafficLightsCollection.

Listing 3: "TrafficLightsWidget.ts
/// 
/// 
import TrafficLights = require("scripts/TrafficLightsCollection"); 

function GetSettings(widgetSettings) {...}

function RenderTrafficLights(WidgetHelpers, widgetSettings) {
  var numberOfBuilds = widgetSettings.size.columnSpan;
  var config = GetSettings(widgetSettings);
  if (config != null) {
    var trafficLights = new TrafficLights.TrafficLightsCollection(
      VSS.getWebContext().project.name, config.buildDefinition, 
      numberOfBuilds, document.getElementById("content"));
  }
  else {...}

}

VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
  WidgetHelpers.IncludeWidgetStyles();
  VSS.register("BuildTrafficLightsWidget", function () {
    return {
      load: function (widgetSettings) {
        RenderTrafficLights(WidgetHelpers, widgetSettings);
        return WidgetHelpers.WidgetStatusHelper.Success();
      },
      reload: function (widgetSettings) {...}
    };
  });
  VSS.notifyLoadSucceeded();
});

Um nun mit dem REST-API von VSTS/TFS zu kommunizieren, können wir über das SDK einen entsprechenden Client instanziieren. Der Vorteil dieser Vorgehensweise liegt darin, dass wir uns nicht über die Authentifizierung kümmern müssen. Die REST-Aufrufe erfolgen automatisch im Sicherheitskontext des aktuellen Nutzers. In Listing 4 ist der entsprechende Auszug zu sehen.

Listing 4: "TrafficLightsCollection.ts"
/// 
import Contracts = require("TFS/Build/Contracts");
import BuildRestClient = require("TFS/Build/RestClient");

export class TrafficLightsCollection {
  ...
  constructor(projectname: string, builddefinition: number, 
    numberofbuilds: number, element: HTMLElement) {
    ...
  }

  public updateBuildState() {
    var buildClient = BuildRestClient.getClient();
    buildClient.getBuilds(this.projectname, [this.buildDefinitionId, ..., 
      this.numberOfBuilds).then((buildResults: Contracts.Build[]) => {
        this.builds = buildResults;
        this.renderLights();
      });
    }, err => { this.renderLights(); });
  }
}

Was jetzt noch fehlt, ist die Möglichkeit, unser Widget zu konfigurieren (Listing 5). Jedes Ampel-Widget benötigt eine entsprechende Konfiguration, in der die gewünschte Build-Definition ausgewählt und gespeichert wird. Die Grunddefinition der Konfigurationsansicht (HTML-Datei) verhält sich genauso wie die beim Widget. Im Code für die Konfigurationsansicht müssen zwei Lifecycle-Events überschrieben werden: load und onSave(). Hier können wir über das SDK unsere spezifischen Einstellungswerte als JSON-Definition laden und speichern. Damit die Livepreview im Konfigurationsmodus funktioniert, müssen entsprechende Änderungsevents publiziert werden. Die aktuellen Konfigurationswerte werden mittels TypeScript mit den UI-Controls synchron gehalten.

import BuildRestClient = require("TFS/Build/RestClient");
import Contracts = require("TFS/Build/Contracts");

export class TrafficLightsWidgetConfiguration {
  ...
  constructor(public WidgetHelpers) {    }

  public load(widgetSettings, widgetConfigurationContext) {
    this.widgetConfigurationContext = widgetConfigurationContext;
    this.initializeOptions(widgetSettings);
    this.selectBuildDefinition.addEventListener(
      "change", () => { this.widgetConfigurationContext
        .notify(this.WidgetHelpers.WidgetEvent.ConfigurationChange,
        this.WidgetHelpers.WidgetEvent.Args(this.getCustomSettings()));
      });
    ...
    return this.WidgetHelpers.WidgetStatusHelper.Success();
  }

  public initializeOptions(widgetSettings) {
    ...
    var config = JSON.parse(widgetSettings.customSettings.data);
    if (config != null) {
      if (config.buildDefinition != null) {
        this.selectBuildDefinition.value = config.buildDefinition;
      }
    }
  }

  public onSave() {
    var customSettings = this.getCustomSettings();
    return this.WidgetHelpers.WidgetConfigurationSave
      .Valid(customSettings);
  }
}

VSS.require(["TFS/Dashboards/WidgetHelpers"], (WidgetHelpers) => {
  WidgetHelpers.IncludeWidgetConfigurationStyles();
  VSS.register("BuildTrafficLightsWidget.Configuration", () => {
    var configuration = 
      new TrafficLightsWidgetConfiguration(WidgetHelpers);
    return configuration;
  })
  VSS.notifyLoadSucceeded();
});

Ein komplettes Code-Listing wäre für diesen Artikel zu umfangreich. Entsprechend steht der gesamte Code dieser Beispiel-Extension auf GitHub zur Verfügung.

Entwicklungsworkflow von Extensions

Wer Extensions entwickelt, setzt sich primär mit klassischen Webentwicklungstechnologien auseinander. Doch wie bekommen wir nun die Extension in den TFS oder VSTS, und wie können wir debuggen? Als Erstes benötigen wir ein Extension Package in Form einer VSIX-2.0-Datei. Diese können wir anhand des Manifests und dem Kommandozeilentool tfx-cli erstellen. Das CLI wird als npm-Paket angeboten und entsprechend in unserem Projekt geladen. Wie bereits weiter oben erwähnt, möchten wir diesen Schritt als Grunt-Task in den Visual Studio Build integrieren. Wie in Abbildung 4 zu sehen ist, verknüpfen wir den Grunt-Task mit dem After-Build-Event von Visual Studio. Somit erhalten wir nach jedem Build das entsprechende Package.

Abb. 4: Grunt-Task zur Erstellung des Pakets

Abb. 4: Grunt-Task zur Erstellung des Pakets

Der nächste Schritt ist das Publizieren in TFS oder VSTS. Im Fall von TFS wird die VSIX-Datei über den Extension-Manager hochgeladen und in der gewünschten Team Project Collection installiert. Soll die Extension in VSTS getestet werden, ist das Publizieren im Marketplace notwendig. Keine Angst, auch hier können die Extensions zuerst getestet werden, bevor sie der Allgemeinheit zugänglich gemacht werden. Extensions können außerdem als private Erweiterungen markiert werden. Somit stehen sie nur ausgewählten VSTS-Accounts zur Verfügung.

Nun ist die Extension verfügbar und kann getestet werden. Hierzu wenden wir die Entwicklertools der gängigen Browser an. Die Extension läuft in einem iFrame; entsprechend kann die JavaScript-Konsole auf den gewünschten iFrame gefiltert, Elemente inspiziert und der TypeScript- bzw. JavaScript-Code mit dem Debugger analysiert werden.

Einbindung von externen Services mit REST-API und WebHooks

Die aktuelle Erweiterungsschnittstelle von VSTS/TFS erlaubt nur Frontend-Erweiterungen. Eigene Services oder Backend-Logik können nicht integriert werden. Entsprechend muss diese Art von Erweiterung extern als Windows-Service oder Webapplikation betrieben bzw. gehostet werden. VSTS und TFS stellen ein REST-API zur Verfügung, mit dem fast alle Aktionen getätigt werden können. Entwickelt man mit .NET, so stehen entsprechende NuGet-Pakete zur Verfügung.

Oft bedingen eigene Logikerweiterungen aber, dass man auf Ereignisse in VSTS/TFS reagieren kann. Ein Polling über das API wäre ein schlechter Ansatz. Entsprechend wurde in VSTS/TFS das Konzept von WebHooks implementiert. Über die Einstellungen unter SERVICE HOOKS können eigene WebHook-Empfänger registriert werden. Jede WebHook-Registrierung ist an ein bestimmtes Event in VSTS/TFS gebunden und wird bei dessen Eintreten ausgelöst.

Der WebHook-Empfänger ist an und für sich relativ einfach zu implementieren. VSTS/TFS führt einen POST-Request an den definierten Empfänger durch, und der Body beinhaltet alle Eventdaten, wie zum Beispiel die Work-Item-Daten des geänderten Work Items bei einem Work-Item-Update-Event. Einen POST Request abzuarbeiten, ist nicht weiter schwierig, jedoch ist es mit einem gewissen Zeitaufwand verbunden, die Routen zu konfigurieren und den Payload richtig zu parsen. Zum Glück greift uns Microsoft ein wenig unter die Arme. Für ASP.NET gibt es ein WebHook-Framework, das eine Basisimplementierung für das Senden und Empfangen von WebHooks bereitstellt. Zudem ist ebenfalls eine Implementierung für VSTS-WebHook-Events verfügbar, was einem das gesamte Parsen des Payloads abnimmt. Nach dem Erstellen eines Web-API-Projekts muss lediglich das NuGet-Paket Microsoft.AspNet.WebHooks.Receivers.VSTS hinzugefügt werden. Zu beachten ist hierbei, dass das Framework sowie die Pakete noch im RC-Status sind und entsprechend über NuGet als Pre-Release geladen werden müssen.

Um auf VSTS/TFS-Events reagieren zu können, muss von der entsprechende Basisklasse abgeleitet und die gewünschte Methode überschrieben werden (Listing 6).

public class VstsWebHookHandler : VstsWebHookHandlerBase
{
  public override Task ExecuteAsync(WebHookHandlerContext context,
                                    WorkItemUpdatedPayload payload)
  {   // sample check
    if (payload.Resource.Fields.SystemState.OldValue == "New" && 
      payload.Resource.Fields.SystemState.NewValue == "Approved")
    {
      // your logic goes here...
    }
    return Task.FromResult(true);
  }
}

Das WebHooks-Framework generiert über die Konfiguration entsprechende Web-API-Routen. Damit nicht jeder einen Post auf den WebHook-Receiver absetzen kann, wird ein Sender anhand einer ID Identifiziert, die in der Web.config-Datei hinterlegt wird (Abb. 5). Der entsprechende URL muss dann noch im VSTS/TFS hinterlegt werden.

Abb. 5: Registrierung des WebHook-Receivers in VSTS

Abb. 5: Registrierung des WebHook-Receivers in VSTS

Fazit

Die hier gezeigte UI-Integration bietet weitere Möglichkeiten, den eigenen Prozess bis in das Standard-Tooling zu integrieren. Somit sind nahezu grenzenlose Möglichkeiten für eine optimale Usability und Unterstützung der Nutzer gegeben. Wer sich mit HTML, CSS und JavaScript/TypeScript auskennt, dem dürfte das Erstellen eigener Extensions nicht allzu schwer fallen. Für Nutzer können die neuen Funktionen nur Vorteile haben, zumal die Werbefläche von VSTS/TFS fast nicht mehr verlassen werden muss.

 

 

The post Visual Studio Team Services und Team Foundation Server erweitern und anpassen appeared first on BASTA!.

]]>
Sicherheit über den Open Source Identity Server https://basta.net/blog/sicherheit-ueber-den-open-source-identity-server/ Wed, 21 Dec 2016 15:34:06 +0000 https://basta.net/?p=17963 Dominick Baier stellt den Projekthintergrund des Identity Servers vor und beschreibt die Vorteile eines Open Source .NET-Projekt, das in der .NET Foundation mit ihren zahlreichen Partnern verankert ist.

The post Sicherheit über den Open Source Identity Server appeared first on BASTA!.

]]>
Sicherheit ist ein wesentliches Feature von Anwendungen und Diensten. Nicht zuletzt die Diskussionen und Schlagzeilen rund um die Wahlen in den USA machen das immer wieder deutlich. Aber Sicherheit ist auch ein Feature, das erst dann wirklich auffällt, wenn es fehlt. Dann ist es allerdings schon zu spät. Entwickler sollten Sicherheit bei ihren Produkten und für ihre Firmen immer gleich mitdenken, auch wenn es der erste Punkt ist, an dem man Schmerzen spürt und mehr Zeit investieren muss, als man gerne möchte oder der Projektplan zu lässt. Dominick Baier ist seit über zehn Jahren Security-Advisor der BASTA! und sorgt dafür, dass die Teilnehmer der BASTA! im Security Track der Konferenz Sessions zu aktuellen Themen finden, die das Bewusstsein für Sicherheit wecken und zugleich einen praktischen Einstieg für erfahrene .NET-Entwickler bieten.

Auf der BASTA! Spring 2017 bietet Dominick neben seinen Session auch einen Workshop an, der an einem Tag aufzeigt, warum moderne Anwendungen moderne Sicherheit brauchen und wie Entwickler das erreichen können. Gerade heutzutage sind die mobilen Web-/Native-basierte Architekturen und die dazugehörigen API-Backends typische Herausforderungen für hohe Sicherheitsanforderungen. Wer als Entwickler solche Anwendungen realisieren möchte, sollte sich mit dem modernen Securitystack, bestehend aus OpenID Connect, OAuth 2.0 und JSON Web Tokens sowie deren Implementierung, in ASP.NET und MVC und Web API auskennen.

Ein zentrales Element dabei ist der Identity Server, der es als Framework auf .NET-Basis erlaubt für Anwendungen und Firmen eine modere Sicherheitsplattform zu schreiben und zu implementieren. Als Open Source Projekt, mit aktiver Community-Beteiligung, ist der Identity Server immer einen aktuellen Entwicklungsstand, der mit den Sicherheitsprotokollen, aber auch den immer neuen Angriffen schritthält. Im Interview mit Mirko Schrempp, Windows Developer Redakteur und Program Chair der BASTA!, stellt Dominick Baier den Projekthintergrund des Identity Servers vor und beschreibt die Vorteile eines Open Source .NET-Projekt, das in der .NET Foundation mit ihren zahlreichen Partnern verankert ist.

 

Interview mit Dominick Baier

 

 

The post Sicherheit über den Open Source Identity Server appeared first on BASTA!.

]]>
Azure App Services https://basta.net/blog/azure-app-services/ Tue, 13 Dec 2016 12:09:27 +0000 https://basta.net/?p=17849 Wer bisher einen einfachen Einstieg in die Welt von Microsoft Azure suchte, war mit Azure-Websites gut bedient: das Hosten von Webanwendungen, angereichert mit Cloud-Features wie Scale-up oder Scale-out. Ende März wurden Websites als Teil der neu vorgestellten App-Services umbenannt in Web-Apps – und erweitert um teils bekannte (Mobile-Apps) und teils komplett neue Komponenten (Logic-Apps und API-Apps). Hier ein erster Überblick.

The post Azure App Services appeared first on BASTA!.

]]>
Microsoft treibt die Entwicklung von Azure mit hoher Geschwindigkeit voran. So gut wie jeden Monat werden neue Ankündigungen veröffentlicht. Selbst intensive Nutzer sind herausgefordert, wenn sie an allen Fronten Bescheid wissen wollen – von Azure-Anfängern gar nicht zu sprechen. Nicht gerade dienlich war dabei die bisherige Vorgehensweise, alle Features quasi gleichwertig nebeneinander zu positionieren. Da gab es Websites, virtuelle Maschinen und SQL-Datenbanken gleichgestellt mit HDInsight, Media-Services und Machine Learning. Welche Komponenten gemeinsam Sinn ergeben und zusammenspielen, war schwer ersichtlich; schon gar nicht aber konnten verschiedene Azure-Services innerhalb derselben Instanz betrieben werden. Wer beispielsweise einen Mobile-Service und eine Website betrieben hat, musste getrennt dafür zahlen.

Mit den neu vorgestellten App-Services wird versucht, vier zusammenhängende Dienste unter einem gemeinsamen Namen anzubieten. Eine Grundbedingung aus der Marketingabteilung war offensichtlich die Verwendung des Worts App im Namen: So wurden die Websites zu Web-Apps, die Mobile-Services zu Mobile-Apps und zwei Neuzugänge auf Logic-App bzw. API-App getauft. Jeder dieser Services kann weiterhin separat und unabhängig genutzt werden – doch es gibt eine Story, wie die genannten Komponenten zusammenspielen können, und damit vielleicht einen leichteren Einstieg in die Welt von Microsoft Azure.

Web-Apps

Aber der Reihe nach. Am wenigsten spektakulär verlief die Ankündigung der App-Services für die Nutzer von Azure-Websites. Die Umbenennung blieb die einzige direkt sichtbare Auswirkung, Web-Apps können nicht mehr und nicht weniger als die bisherigen Websites. Sämtliche bereits deployten Anwendungen waren vom ersten Tag an unter dem neuen Begriff sichtbar, kein Migrationsaufwand, keine Probleme. Der Mehrwert kommt erst durch die optionale Nutzung der weiteren Services, die in einem bestehenden Webcontainer ohne zusätzliche Kosten betrieben werden können.

Mobile-Apps

Unter dem Namen Mobile-Services sind mehrere Angebote vereint, die das Entwickeln von Apps erleichtern konnten: Ein REST-basierter Zugriff auf Daten, das Senden von Push Notifications quer durch alle Plattformen oder die Authentifizierung mittels Providern wie Facebook, Twitter oder ein Microsoft-Account. Zum Teil ist es auch möglich, die Services durch eigenen Backend-Code anzupassen: Die REST-Datenschnittstelle stellt zum Beispiel Erweiterungspunkte zur Verfügung, die ähnlich wie Datenbank-Trigger genutzt werden können. Als Programmiersprachen für das Backend stehen Node.js oder .NET zur Verfügung. Die gesamte Funktionalität ist über eine REST-basierte Schnittstelle zu konsumieren, es gibt aber SDKs, um den Zugriff aus Windows Phone, iOS und Android zu erleichtern.

Im neuen App-Services-Paket tauchen nun große Teile der Funktionalität unter dem Namen Mobile-Apps wieder auf – aber nicht ganz so reibungslos und unverändert wie bei den zuvor genannten Websites. Erstes Anzeichen: Bisherige Mobile-Services wurden nicht automatisch migriert, im alten Azure-Portal befinden sich weiterhin alle Instanzen unter unverändertem Namen (eine Anleitung zur Migration befindet sich hier). Mit ein Grund kann sein, dass als Sprache für das Backend in Mobile-Apps vorerst nur .NET zur Verfügung steht – die in den Mobile-Service gebräuchlichere Node.js-Variante fehlt noch. Der für das Backend notwendige Code kann nun aber gemeinsam mit der Web-App ohne zusätzliche Kosten im selben Container gehostet werden – inklusive Auto-Scaling, Traffic-Manager-Support oder Deployment-Vorteilen wie Continuous Integration und Staging Deployments. Als weitere Neuerung sind Verbindungen zu On-Premise-Datenbanken möglich, ein eingerichtetes virtuelles Netzwerk mit Point-to-Site-VPN wird vorausgesetzt. Wer vor der Aufgabe steht, eine mobile App für iOS, Android oder Windows Phone zu entwickeln und dafür Push Notifications, Authentifizierung oder (On-Premise)-Datenzugriff benötigt, sollte sich Mobile-Apps ansehen.

Es ist anzunehmen, dass die bisherigen Mobile-Services über kurz oder lang komplett durch die neuen Mobile-Apps ersetzt werden, für den Moment gibt es allerdings beide Varianten parallel.

API-Apps

Kommen wir zu den Neuerungen: API-Apps sind brandneu und fügen der Azure-Palette wieder einen deutlichen Mehrwert hinzu. Im Wesentlichen handelt es sich dabei um Web-API-Projekte, die um Metadaten und Authentifizierung angereichert sind.

Als Format für diese Metadaten kommt – ganz im Sinne des aktuellen Microsoft-Mindsets – keine hauseigene, proprietäre Lösung, sondern ein bewährtes, existierendes Open-Source-Projekt namens Swagger zum Einsatz. Eine Swagger-Definition besteht aus einer Beschreibung der einzelnen Methoden und deren Parameter in einem spezifizierten JSON-Format. Alter Wein in neuen Schläuchen? CORBA? WSDL? Im Prinzip ja. Das Swagger-Format ist aber sehr schlank und erfordert keine Änderung von bestehenden APIs – theoretisch könnte die Swagger-Definition sogar von jemand anderem als dem API-Hersteller geschrieben werden. Die größten Vorteile, die Metadaten mit sich bringen, sind schnell beschrieben: automatische Dokumentation und Client-SDK-Generierung.

Hello, API-App

schacherl_azure_1

Abb. 1: Erstellen eines Azure-API-App-Projekts in Visual Studio

Wie sieht das in der Praxis aus? Im neuesten Azure-SDK (März: 2.5.1) sind die App-Services bereits integriert. Beim Erstellen einer neuen ASP.NET-Webapplikation steht als Template „Azure-API-App-(Preview)“ zur Verfügung, dadurch wird ein Web-API-Projekt mit einigen zusätzlichen Referenzen erstellt (Abb. 1). Sollten Sie ein bestehendes ASP.NET-Web-API-Projekt verwenden wollen, klicken Sie mit der rechten Maustaste auf das Projekt und anschließend auf ADD | Azure API App SDK. Sie werden anschließend gefragt, ob die Swagger-Metadaten automatisch erstellt werden sollen (Abb. 2, die Antwort lautet: Ja).

schacherl_azure_2

Abb. 2: Metadatengenerierung oder -auswahl

Das Erstellen der Web-API-Controller unterscheidet sich nicht von der gewohnten Art und Weise; wie bisher leiten Sie von ApiController ab, erstellen darin Actions und verwenden ggf. Routing-Attribute (Listing 1). Einziger Hinweis: Ein Bug verhindert (zumindest bis April) das Überladen von Action-Methoden. Vermeiden Sie also beispielsweise zwei Get-Methoden, die sich nur durch einen Parameter unterscheiden – das Azure-Portal hat ansonsten Probleme beim Abrufen der Metadaten.

public class WishlistController : ApiController
{
  public string Get()
  {
    return "Hello API App!";
  }

  public async Task Post(WishRequest request)
  {
    // store data
  }
}

Was Ihr Web-API nun zu einer API-App macht, sind die Metadaten. Die automatische Metadatengenerierung sollte aktiviert sein; erstes Erkennungsmerkmal ist die Datei apiapp.json im Hauptverzeichnis mit einigen allgemeinen Metadaten. Wichtiger ist die Klasse SwaggerConfig im Unterordner App_Start. Hier können Sie in die Generierung der Metadaten eingreifen und beispielsweise festlegen, ob auch das Swagger-UI bereitgestellt werden soll: ein interaktives UI zum Testen des API.

Wenn Sie die Anwendung ausführen, können Sie den Erfolg unmittelbar testen: Unter http://localhost:<port>/swagger/docs/v1 können Sie das Swagger-JSON-Format ansehen, unter /swagger erscheint das UI (Abb. 3).

schacherl_azure_3

Abb. 3: Swagger-UI

Damit steht dem Veröffentlichen nichts mehr im Weg: Auch der Publish-Dialog (rechte Maustaste auf das Projekt | Publish) kennt bereits Microsoft-Azure-API-Apps als Ziel. Über den Dialog kann eine bestehende API-App ausgewählt oder eine neue erstellt werden. Beim ersten Mal wird auch die App in Azure erstellt, teilweise ist anschließend ein zweites beherztes Anstoßen des Publish-Vorgangs erforderlich.

Wenn alles erfolgreich war, sehen Sie die neu angelegte API-App im Preview-Azure-Portal. Auch dort können Sie die Metadaten („API-Definition“) abrufen und überprüfen, ob alles geklappt hat (Abb. 4).

schacherl_azure_4

Abb. 4: Swagger-Metadaten im Azure Portal

Nun ist die Zeit der Ernte gekommen: Aufgrund der Metadaten können Sie sich ein passendes Client-SDK generieren lassen – HttpClient-Aufrufe und manuelles Parsen von Response-Nachrichten gehören der Vergangenheit an. Egal ob in einer Konsolenanwendung oder in einer Windows-Phone-App: Klicken Sie beim Projekt auf Add | Azure API App Client und wählen Sie die gerade bereitgestellte API-App aus. Auf Basis der Swagger-Definition werden entsprechende Interfaces und Klassen zum Zugriff auf das API zur Verfügung gestellt – übrigens nicht nur für .NET, sondern auch für Node.js, Java, PHP und Python. In Listing 2 sehen Sie, wie einfach die zuvor erstellten Get- und Post-Requests nun in einer Windows-Phone-App aufgerufen werden können.

IMySuperWishlistApi api = new MySuperWishlistApi();
var getResponse = await api.Wishlist.GetAsync();

var wish = new WishRequest()
{
  UserName = "[email protected]",
  Wish = "An API that works"
};

await api.Wishlist.PostAsync(wish);

Neben den Metadaten kann auch Authentifizierung ein Grund für die Verwendung von API-Apps sein. Derzeit sind drei verschiedene Sicherheitslevel wählbar: öffentlich, authentifiziert oder intern. Letzteres ermöglicht einen Aufruf für alle Services innerhalb der gleichen Ressource, fällt die Wahl hingegen auf authentifiziert, muss ein Authentifizierungsprovider (Microsoft-Account, Facebook etc.) konfiguriert werden. Die Einstellungen finden Sie im neuen Azure-Portal bei der API-App unter Settings | Application settings bzw. Authentication, eine genaue Anleitung hier. Authentifizierung ohne ein OAuth-Buch lesen zu müssen und Tokens zu parsen – ein verlockendes Angebot.

Fast schon zur Gewohnheit gehört, dass Sie mittels Remote-Debugging den laufenden Service mit Visual Studio debuggen können. Gerade für die Fehlerfindung zu Beginn ein äußerst hilfreicher Wegbegleiter.

Logic-Apps

Sie haben gesehen, wie einfach es ist, ein eigenes API inklusive Metadaten bereitzustellen. Zusätzlich gibt es bereits eine unglaubliche Vielzahl von Services – teilweise kostenlos, teilweise kostenpflichtig – die auf ihre Verwendung warten: Dropbox, OneNote, Facebook, Twitter, Yammer, Slack, Twilio etc.

Diese Vielzahl an APIs, die zusätzlich um Metadaten angereichert werden, hat vermutlich zur Idee der vierten Komponente geführt: Logic-Apps. Diese zumindest grafisch auffälligste Neuerung weckt wieder die Idee, dass Softwareentwicklung irgendwann nur mehr das Zusammensetzen fertiger Bausteine sein könnte (was ich weder hoffe, noch glaube). Mithilfe eines grafischen Editors können Sie Services aneinanderreihen und die Ergebnisse des einen Service wieder als Eingabeparameter für den nächsten Service verwenden.

Ein Beispiel: Prüfen Sie alle fünf Minuten, ob es aktuelle Twitter-Nachrichten zu einem bestimmten Hashtag gibt und übergeben Sie das Ergebnis an Ihr API. Überprüfen Sie Ihren POP3-Posteingang nach neuen Mails mit dem Betreff „Wichtig“ und senden Sie in diesem Fall eine SMS an Ihr Handy. Fragen Sie Ihren Service in regelmäßigen Abständen nach Neuigkeiten und posten Sie diese gleichzeitig auf Twitter, Yammer und Facebook.

Die gute Nachricht: Alle erwähnten Services sind bereits jetzt im Azure-Marketplace zu finden, alle genannten Beispiele könnten schon jetzt als Logic-App umgesetzt werden. Die Vielfalt erhöht den Spaßfaktor deutlich: Irgendeinen Service findet man immer, der unbedingt noch eingebaut werden muss.

In Abbildung 5 sehen Sie beispielsweise einen POP3-Connector, der einmal pro Minute den Posteingang überprüft und beim Eintreffen einer E-Mail unsere zuvor erstellte API-App aufruft. Im dritten Schritt wird Twilio verwendet, um den Betreff der E-Mail per SMS zu versenden.

schacherl_azure_5

Abb. 5: Erstellung einer Logic-App

Jeder Service kann so konfiguriert werden, dass er nur beim Erfüllen einer bestimmten Bedingung ausgeführt wird – allerdings ist die zugrunde liegende Formelsprache aktuell eher schlecht dokumentiert. Im Azure-Portal erhalten Sie detaillierte Monitoring-Reports über jede Abarbeitung: Bis hin zu den einzelnen Requests können Sie so nachverfolgen, ob Ihre Logic-App wie gewünscht funktioniert.

Fazit

Microsoft versucht, mit den App-Services ein Paket zu schnüren, das die Entwicklung von Webanwendungen und mobilen Apps vereinfachen kann. Das Gesamtpaket ist umfangreich, im Detail warten aber derart viele Möglichkeiten, dass trotz des Versuchs einer durchgängigen Story die Einarbeitungszeit beträchtlich ist. Lassen Sie sich davon nicht abschrecken, nutzen Sie, was Sie benötigen. Sie möchten eine Website hosten? Nehmen Sie Web-Apps. Sie benötigen Push Notifications oder möchten einen Login in Ihre App einbauen? Testen Sie Mobile-Apps. Möchten Sie Ihr API authentifiziert oder mit Metadaten bereitstellen? API-App. Oder möchten Sie bestehende APIs zu einem größeren Ganzen orchestrieren? Logic-App.

Viel Spaß beim Ausprobieren und Kennenlernen der Azure App Services.


Aufmacherbild: Cloud computing via Shutterstock / Urheberrecht: Stokkete

The post Azure App Services appeared first on BASTA!.

]]>
Buchverlosung: SQL Server – Performanceprobleme analysieren und beheben https://basta.net/blog/buchverlosung-sql-server-performanceprobleme-analysieren-und-beheben/ Thu, 08 Dec 2016 11:52:17 +0000 https://basta.net/?p=17666 Datenbanken bilden das Rückgrat nahezu jeder Businessanwendung. Dabei spielt oft die Performance eine entscheidende Rolle. Dieses Buch von Robert Panther stellt für alle Versionen bis einschließlich SQL Server 2016 in kompakter Form dar, was zu prüfen ist, und wo mit möglichst geringem Aufwand schnell eine Verbesserung der Performance erzielt werden kann.

The post Buchverlosung: SQL Server – Performanceprobleme analysieren und beheben appeared first on BASTA!.

]]>

Jetzt Registrieren und an der Buchverlosung teilnehmen

SQL-Server-Performanceanalyse_2400x_rgb

Warum dieses Buch?

Jeder Datenbankadministrator (oder Berater in diesem Umfeld) hat sicherlich schon einmal die Situation erlebt, dass bei einer Anwendung, die bereits längere Zeit problemlos lief, sich die Anwender plötzlich beschweren, dass die Performance unerträglich schlecht und ein normales Arbeiten kaum noch möglich ist. Entweder dauern einzelne Aktionen deutlich zu lange, oder Anwender erhalten eventuell auch Timeout-Meldungen, die belegen, dass eine Aktion zu diesem Zeitpunkt gar nicht ausgeführt werden konnte, weil sie zu lange gedauert hat.
Wenn es schlecht läuft, haben die Klagen der Anwender über die schlechte Performance schon Teile des Managements erreicht; im ungünstigsten Fall waren diese davon sogar unmittelbar selbst betroffen.

Nun gilt es, gleichzeitig schnell zu reagieren, dabei aber dennoch wohlüberlegt zu handeln, denn ein Schnellschuss kann das Eingrenzen des wesentlichen Problems erschweren und im Extremfall sogar zu einer Verschlechterung der Performance führen. Die möglichen Ursachen des Problems können sehr vielseitig sein:

  • Hardware zu schwach
  • fehlende Indizes
  • zu viele Indizes
  • veraltete Statistiken
  • schlecht formulierte Abfragen
  • Sperren und Deadlocks
  • lang dauernde Abfragen (oder Änderungen) etc.

Daraus ergeben sich auch mindestens genauso viele Lösungsansätze:

  • Hardware erweitern
  • fehlende Indizes erstellen
  • überflüssige Indizes löschen
  • Dateien auf verschiedene Platten (bzw. Plattensysteme) verteilen
  • Dateien vergrößern
  • Wiederherstellungsmodus anpassen
  • SQL-Abfragen optimieren
  • richtige Transaktionslevel verwenden
  • kleinere Transaktionen nutzen etc.

Um schnell Abhilfe zu schaffen, ergibt es natürlich wenig Sinn, alle möglichen Lösungsansätze der Reihe nach durchzugehen. Dies würde einen erheblichen Aufwand verursachen, ohne dass eine Garantie auf Besserung des Problems gegeben wäre. Dazu erfordern einige Ansätze einen Neustart des Serverdienstes (und damit zusätzliche Ausfallzeiten) oder gar Änderungen an der Anwendung selbst. Dabei sollte es das primäre Ziel sein, quasi minimalinvasiv zu agieren, also mit möglichst geringen Änderungen eine möglichst deutliche Verbesserung der Performance zu erreichen. Erst wenn die akuten Probleme beseitigt sind, kann anschließend eine ausführliche Analyse erfolgen, um auch kleinere oder noch nicht akute Probleme zu beseitigen. Insgesamt hat sich ein dreistufiges Vorgehen bewährt:

1. Erste Hilfe bei Performanceproblemen
2. Ausführliche Performanceanalyse
3. Langfristige Maßnahmen

An diesem dreistufigen Vorgehen orientiert sich auch der Aufbau des Buchtexts (Kapitel 2–4), denen ein Kapitel folgt, in dem die wichtigsten mit SQL Server ausgelieferten Tools zur Performanceanalyse kurz dargestellt sind.
Dabei ist insbesondere der erste Teil – wie das Buch insgesamt auch – sehr kompakt gehalten, damit man bei akuten Performanceproblemen nicht lange lesen muss, um eine schnelle Besserung zu erzielen. Aus demselben Grund werden Sie im Buch auch keine langen Listings finden. Wenn Sie ein akutes Performanceproblem bei einer Datenbank haben, möchten Sie sicherlich keine Zeit damit verschwenden, lange SQLSkripte abzutippen. Stattdessen wird in den meisten Fällen beschrieben, wie Sie Performanceanalysen über die Benutzeroberfläche der diversen Tools durchführen können.1

Für wen ist dieses Buch gedacht?

Der Text richtet sich primär an Datenbankadministratoren und Berater, die in diesem Umfeld tätig sind. Da die Übergänge zwischen den Tätigkeitsfeldern meist fließend sind, können die Inhalte auch für Anwendungs- und Datenbankentwickler eine wertvolle Hilfe sein. An Vorkenntnissen werden vor allem Grundkenntnisse mit SQL Server vorausgesetzt, insbesondere T-SQL und der Umgang mit dem SQL Server Management Studio sollten bekannt
sein.

The post Buchverlosung: SQL Server – Performanceprobleme analysieren und beheben appeared first on BASTA!.

]]>
.NET Core 1.1, ASP.NET Core 1.1 und Entity Framework Core 1.1 https://basta.net/blog/net-core-1-1-asp-net-core-1-1-und-entity-framework-core-1-1/ Wed, 30 Nov 2016 16:21:06 +0000 https://basta.net/?p=17432 Am 16. November ist im Rahmen der Microsoft-Konferenz Connect(); 2016 die erste funktionale Erweiterung für die Core-Produktfamilie erschienen. Wir Entwickler dürfen uns über gesteigerte Performance und die Rückkehr einiger Funktionen freuen. Mit Xamarin.Forms für das Tizen-Betriebssystem auf Basis von .NET Core wächst die Hoffnung auf ein echtes Cross-Plattform-XAML.

The post .NET Core 1.1, ASP.NET Core 1.1 und Entity Framework Core 1.1 appeared first on BASTA!.

]]>
Zum programm


Das .NET-Kern-Update

Auch wenn die Core-Produkte bereits in der Version 1.0 schon in vielen Fällen schneller liefen als die Vorgänger aus der .NET-„Full“-Framework-Ära, arbeitet Microsoft weiterhin an der Performance. In .NET Core 1.1 setzt Microsoft nun auch auf die Profile-guided Optimization (PGO). Hierbei wird im Zug des Ablaufs von Musteranwendungen die Reihenfolge der tatsächlichen Nutzung des Programmcodes in der CLR und den Bibliotheken aufgezeichnet. Auf Basis dieser Messdaten optimiert Microsoft dann die Reihenfolge des Programmcodes im Kompilat. In .NET Core 1.1 hat Microsoft laut .NET Blog lediglich eine einfache „Hello World“-Anwendung für die Datenaufzeichnung verwendet, damit dann allerdings schon eine 15-prozentige Leistungssteigerung bei der ASP.NET-Core-Beispielanwendung MusicStore erzielt. Angesichts dieses Resultats kann man die Hoffnung haben, dass Microsoft in Zukunft auf Basis von Messdaten aus echten .NET-Core-Anwendungen noch mehr aus diesem Optimierungsverfahren herausholen wird.

Dass die Leistung von .NET Core in Version 1.1 besser ist als bei Version 1.0, beweist auch der zehnte Platz im Vergleich mit anderen Webframeworks in der dreizehnten Ausgabe des TechEmpower-Webserverframework-Performanztests. Allerdings wurde dieser Platz mit einer Webanwendung erzielt, die kein HTML, sondern „Plain Text“ mit ASP.NET Core ohne das MVC-Framework erzeugt, und auf Linux. Beim Generieren der Ausgabe mit dem MVC-Framework rutscht ASP.NET Core in der Leistung demgegenüber auf den 29. Platz ab. Allerdings rangiert es damit dann zwar hinter Java Servlets, jedoch immer noch vor Node.js und deutlich vor PHP.

schwichtenberg_kern_1

Abb. 1: ASP.NET Core 1.1 im Vergleich zu Java Servlets, Node.js, Node.js mit Express und PHP (Quelle: Microsoft Connect(); 2016)

.NET Core für mehr Betriebssysteme

Zu den bisher unterstützten Versionen von Windows, Linux und Mac kommen bei den Betriebssystemen mit Core 1.1 nun hinzu:

  • Windows Server 2016
  • Linux Mint 18
  • OpenSUSE 42.1
  • macOS 10.12
  • auf Linux basierendes Betriebssystem Tizen

.NET Core für Tizen (Abb. 2) wird dabei von Samsung entwickelt. Die „Visual Studio Tools for Tizen“ nutzen hier Xamarin.Forms für Benutzeroberflächen. Laut Tizen Developers Blog wird in der ersten Preview der Visual Studio Tools für Tizen bereits „99 % von Xamarin.Forms“ unterstützt. Das ist insofern bemerkenswert, als damit der Schritt, Xamarin.Forms auch für Benutzeroberflächen auf Linux anzubieten, sehr nahe liegt. Auch könnte .NET Core bald Mono als Basis für Xamarin.Forms in iOS, Android und Windows 10 ablösen.

Mehr APIs

Nicht viel versprechen sollte man sich allerdings von den „1 380 zusätzlichen APIs“, die Microsoft laut Blogeintrag in .NET Core 1.1 ergänzt hat. Mit APIs sind nicht komplette Bibliotheken gemeint, vielmehr hat Microsoft dabei jede neue Klasse und jedes neue Klassenmitglied einzeln gezählt. Beim Blick in die Liste der API-Neuerungen findet der Entwickler leider auch wenig, was er typischerweise braucht. Die Ergänzungen betreffen fast ausschließlich interne Funktionen von .NET Core bzw. dienen der Unterstützung für die Version 7.0 der Programmiersprache C#. Die .NET Standard Library ändert den Versionsstand daher auch nur leicht von 1.6 auf 1.6.1. Die unter dem Slogan „One library to rule them all“ groß angekündigte Angleichung an die Programmierschnittstellen der Base Class Library des großen .NET Framework 4.6 wird leider erst im Jahr 2017 im Rahmen der .NET Standard Library 2.0 erfolgen.

Core-Werkzeuge bleiben unfertig

Ebenfalls noch nicht am Ziel ist Microsoft bei den .NET-Core-Werkzeugen. Alle Tools, einschließlich der Visual-Studio-Integration, bleiben vorerst weiterhin im Preview-Stadium. Die Version des SDK ändert sich nur leicht im Vergleich mit der bisherigen Version „1.0 Preview 2“ (für .NET Core 1.0) auf „1.0 Preview 2.1“ (für .NET Core 1.1). Die aktuelle Installationsdatei des SDK heißt dotnet-dev-win-x64.1.0.0-preview2-1-003177.exe bzw. dotnet-dev-win-x86.1.0.0-preview2-1-003177.exe. Dabei darf man sich von der „1.0“ im Dateinamen nicht verwirren lassen: Dies ist die Versionsnummer des SDK, nicht von .NET Core. Wenn man sich die Dateieigenschaften ansieht, liest man dort unter dem Punkt Description: „Microsoft .NET Core 1.1.0 – SDK 1.0.0 Preview 2.1-003177 (x64)“ (Abb. 2).

schwichtenberg_kern2

Abb. 2: Der Dateiname der SDK-Version zeigt nicht, auf welche .NET-Core-Version sie sich bezieht; das sieht man erst in den erweiterten Dateieigenschaften

Erschwerend kommt noch hinzu, dass Microsoft die Visual-Studio-2015-Werkzeuge nicht aktualisiert hat. So erzeugt Visual Studio 2015 beim Anlegen neuer Projekte auch nach der Installation des SDK also immer .NET-Core-1.0-Projekte. Hier muss der Entwickler dann stets die global.json (ändern der Versionszeichenkette auf „1.0.0-preview2-1-003177“) sowie die project.json (ändern der Version von Microsoft.NETCore.App auf „1.1.0“) manuell anpassen. Auch beim Anlegen neuer .NET-Core-1.1-Projekte mit dem Kommandozeilentool dotnet-new gibt es Probleme. Dazu sei auf [9] verwiesen.

Im Rahmen von Visual Studio 2017 Release Candidate, das bisher Visual Studio “15” hieß, kommt hingegen bereits die Preview-3-Version des SDK zum Einsatz, die – wie von Microsoft angekündigt – nicht mehr auf .xproj– und project.json-Dateien basiert, sondern eine neue Version der XML-basierten MSBuild-Dateien (.csproj) verwendet. Bestehende .xproj– und project.json-Dateien konvertiert Visual Studio 2017 beim Öffnen des Projekts in .csproj-Dateien. Der Entwickler muss jedoch bedenken, dass es keine Option zur Rückkonvertierung gibt, falls er später dann doch mit dem JSON-Format oder Visual Studio 2015 weiterarbeiten möchte. Microsoft spricht in diesem Zusammenhang von der „Alphaversion“ der MSBuild-based .NET-Core-Tools, die Teil des .NET Core SDK 1.0 Preview 3 sind und auf .NET Core 1.1 RTM basieren. Auch im neuen Visual Studio for Mac, das als Preview erschienen ist und eine erweiterte Version des bisherigen Xamarin Studio darstellt, kann der Entwickler mit .NET Core 1.1 und dem Preview 3 des SDK arbeiten.

 

Entity Framework Core 1.1

Bei Entity Framework Core 1.1 hat Microsoft einige Funktionen nachgerüstet, die es im Vorgänger ADO.NET Entity Framework zum Teil schon lange gibt, die jedoch zur 1.0-Version von Core noch nicht fertig waren:

  • Die Methode Find() in der Klasse DbSet<T> lädt ein Objekt anhand der Primärschlüsselwerte. Find() hat die besondere Verhaltensweise, dass es nach einem Objekt zuerst im First-Level-Cache des Entity-Framework-Core-Kontexts sucht und nur dann eine Datenbankabfrage startet, wenn das Objekt dort nicht enthalten ist.
  • Neben dem Eager Loading und dem Pre-Loading verbundener Datensätze bietet Entity Framework Core 1.1 nun auch wieder ein explizites Laden über die Klasse EntityEntry<T> an, z. B. ctx.Entry(flug).Reference(x => x.Flugzeugtyp).Load() bzw. ctx.Entry(flug).Collection(x => x.Buchungen).Load().
  • Zur vereinfachten Implementierung von Undo-Funktionen und Konfliktlösung gibt es nun wieder die Methoden Reload() und GetDatabaseValues().
  • Die Wiederaufnahme abgebrochener Datenbankverbindungen (Connection Resiliency) zum Microsoft SQL Server oder SQL Azure ist ebenfalls wieder möglich.

Darüber hinaus gibt es neue Features zum Mapping von Fields auf Tabellenspalten mit HasField(“name”) sowie dem Mapping auf speicheroptimierten Tabellen von Microsoft SQL Server (ab Version 2014) mit ForSqlServerIsMemoryOptimized() – jeweils anzuwenden im Fluent-API bei OnModelCreating().

ASP.NET Core 1.1

Auch in ASP.NET Core hat Microsoft fehlende Funktionen nachgerüstet. Im Gegensatz zu den Nachrüstarbeiten bei Entity Framework Core hat Microsoft jedoch bei ASP.NET Core in dem aus Version 1.0 bekannten Stil die APIs radikal geändert:

  • Für das URL Rewriting – wahlweise mit serverseitigem Umlenken oder clientseitigen Redirect – gibt es die neue Komponente AspNetCore.Rewrite mit der Klasse RewriteOptions mit den Methoden AddRedirect(), AddRewrite(), AddRedirectToHttps(), AddRedirectToHttpsPermanent(), AddIISUrlRewrite() und AddApacheModRewrite().
  • Zur Geschwindigkeitsoptimierung kann man HTTP-Antworten nun wieder komprimieren (Paket AspNetCore.ResponseCompression) oder zwischenspeichern (Paket Microsoft.AspNetCore.ResponseCaching).
  • Mit dem WebListener-Server (Paket Net.Http.Server) für das Hosting bietet Microsoft jetzt eine Alternative zu Kestrel an. Der WebListener ist ein eigenständiger Webserver, der sich nicht in den IIS oder IISExpress integriert. Er basiert auf dem Windows-HTTP-Server-API und dem Http.Sys-Kernel-Mode-Treiber und bietet Windows-spezifische Verfahren zur Windows-Authentifizierung, zum Port Sharing sowie Caching. Mit dem WebListener-Server läuft die ASP.NET-Core-Anwendung jedoch nicht mehr auf Linux und macOS, sondern allein noch auf Windows 7/Windows Server 2008 R2 und höher.
  • In ASP.NET Core 1.0 hatte Microsoft View Components und Tag Helper zur Wiederverwendung von Markup und Logik eingeführt. In Version 1.1 gibt es nun eine Integration beider Konzepte: Während eine View Component bisher immer per InvokeAsync(“viewcomponentname”) in eine View einzubinden war, kann der Entwickler dies nun über einen Tag Helper (<viewcomponentname>…</viewcomponentname>) auch eleganter vornehmen.
  • Die Kernfunktionen von ASP.NET Core bildet der Entwickler in der Startup-Klasse durch eine Aneinanderreihung von Middleware-Komponenten. Die in ASP.NET Core 1.1 eingeführte neue Annotation [MiddlewareFilter] erlaubt nun, auch Middleware-Komponenten auf einzelne Controller oder Actions zu beschränken, z. B. die HtmlMinificationMiddleware mit [MiddlewareFilter(typeof(HtmlMinificationPipeline))].
  • Die neue Komponente AspNetCore.AzureAppServicesIntegration speichert alle von den Logger-Objekten erzeugten Nachrichten in Microsofts Cloud.
  • Mit den drei Paketen AspNetCore.DataProtection.Redis, Microsoft.AspNetCore.DataProtection.AzureStorage und Microsoft.Extensions.Configuration.AzureKeyVaultkann der Entwickler sensible Informationen, wie kryptografische Schlüssel in Redis oder Microsoft Azure, verwalten.
  • Der CookieTempDataProvider (Paket AspNetCore.Mvc.ViewFeatures) ermöglicht es, in dem TempData-Objekt gespeicherte Daten nun auch per Cookie auf dem Client zu speichern.

Zu beachten ist, dass einige der ergänzten Komponenten (z. B. Microsoft.AspNetCore.Rewrite, Microsoft.AspNetCore.ResponseCompression) die Versionszählung bei 1.0 beginnen, auch wenn Sie im Rahmen von .NET Core 1.1 erstmals erschienen sind, während andere wie Microsoft.AspNetCore.ResponseCaching mit 1.1.0 starten. Vom Paket Microsoft.Net.Http.Server gibt es die Versionen 1.0 und 1.1.

Fazit

Für das erste Release hinter dem Komma hat die Core-Familie in der Version 1.1 einiges zu bieten. Besonders spannend finde ich persönlich aber .NET Core für Tizen mit Xamarin.Forms. Diese Entwicklung gibt Hoffnung, dass Microsoft oder ein anderes Mitglied der .NET Foundation Xamarin.Forms zum vollwertigen UI-Framework für .NET Core ausbaut. In Visual Studio 2017 gibt es immerhin schon einmal eine grafische Preview-Funktion für Xamarin.Forms (jedoch noch keinen Drag-and-Drop-Designer).


The post .NET Core 1.1, ASP.NET Core 1.1 und Entity Framework Core 1.1 appeared first on BASTA!.

]]>
Die BASTA! Spring 2017 – reißt die Mauern endlich ein! https://basta.net/blog/reisst-die-mauern-endlich-ein/ Wed, 02 Nov 2016 11:31:18 +0000 https://basta.net/?p=17276 Die Microsoft-Welt hat sich radikal verändert. Windows 10 ist gekommen um zu bleiben, Azure und andere Cloud-Lösungen wie AWS behaupten sich nicht nur am Markt, sie wachsen und sind profitabel. Klassische und moderne Technologien finden über Services und APIs zueinander, uns selbst unsere Arbeitskultur verändert sich durch agile Entwicklung und DevOps stetig. Die Grenzen zwischen ehemals getrennten Welten und Kulturen fallen oder bekommen neue Übergänge. Die BASTA! bietet Ihnen die Möglichkeit, diese Grenzen zu durchbrechen und diese neuen Übergänge effektiv zu nutzen.

The post Die BASTA! Spring 2017 – reißt die Mauern endlich ein! appeared first on BASTA!.

]]>
Zum programm

Wer will schon einen Übergang verpassen oder vor verschlossenen Schranken stehen? Die Sessions, Workshops und Labs der BASTA! helfen Ihnen dabei, sich gut informiert Ihren Weg in einer immer offeneren Entwicklerwelt zu bahnen. Microsofts Motto „mobile first, cloud first“ zielt schließlich auch auf Ihre eigene Mobilität ab.

Grenzen öffnen, Übergänge schaffen

Nicht ganz überraschend sind es in der IT oftmals technische Standards, die den Charakter der Grenze bestimmen. Denn sie entscheiden darüber, ob die Grenze dicht ist, oder ob es nicht doch ein Schlupfloch gibt, das man als Übergang nutzen könnte.

Mit einer HTML-Seite wird man in jedem Browser auf jeder Plattform einen ordentlichen Auftritt haben, mit Webtechnologien eine App schreiben, die überall ohne Einschränkungen funktioniert – das sieht ein Standard so vor. Mit einer klassischen C#- oder .NET-Software wird man hingegen nicht auf ein iOS- oder Android-Gerät kommen.

Wir werden ausloten, wie und mit welchen Werkzeugen sich überall Grenzen überschreiten lassen. Ob Sie Mono-, Multi- oder Cross-Plattform arbeiten wollen, sollen schließlich Sie bestimmen, nicht irgendeine Grenze.

Um Sie mit dem nötigen Rüstzeug auszustatten, bieten Ihnen die Workshops, Special Days und Sessions der BASTA! praxisorientiertes Wissen von Experten – ob nun im .NET Framework- und C#-Track oder im HTML5 & JavaScript-Track.

 

Die Mauer muss weg!

Es ist an der Zeit, die Mauern einzureißen – allen voran in unseren Köpfen. Denn die Technologie hat diesen Schritt schon lange gemacht.

Beispielsweise zeigt der Modern Business Applications Day, wie Sie vom Backend bis zum Frontend Cross-Plattform-Anwendungen schreiben können. JavaScript bzw. TypeScript, Web APIs und Frameworks wie Angular 2 und Electron sind dabei der Generalschlüssel zum Öffnen der Grenze.

BILD1-Alternative
Abb.: JavaScript führt das Popularitätsranking

 

Mit den gleichen Werkzeugen, ergänzt um ASP.NET und andere Webtechnologien, sind Sie aber auch in jedem Browser zu Hause.

Aber auch als klassischer .NET- und C#- Entwickler müssen Sie nicht auf Ihrer Seite der Grenze verharren. Tools wie Xamarin erlauben Ihnen, Anwendungen in den Ihnen vertrauten Sprachen zu entwickeln und dann wie gewohnt auf Windows zu bringen. Aber eben auch – grenzüberschreitend – auf iOS und Android zu releasen. Der Univeral Apps Day bietet hierfür zahlreiche Einstiegspunkte.

Diplomatie statt Invasion

Damit das alles nicht im Chaos endet und einer Invasion gleicht, ist es wichtig, mit der Gegenseite ins Gespräch zu kommen.

In Zeiten agiler Entwicklung und kurzer Entwicklungszyklen, wachsender Service-Angebote und kompetenter Kunden, kann man Software nicht mehr einfach nur über den Zaun werfen. Das Mindeste sind hier bilaterale Gespräche zwischen Entwickler und Betrieb – DevOps ist das passende Schlagwort.

Wer einen Schritt weitergehen will, der sollte sich überlegen, ob er aus den bilateralen gleich multilaterale Gespräche macht. Reden Sie nicht nur mit dem Betrieb, sprechen Sie auch mit der Fachabteilung und Ihren Nutzern. Finden Sie heraus, wie Sie Services nutzen können, um sich die Arbeit leichter zu machen. Überlegen Sie, welches API Ihr Produkt sinnvoll und effizient für andere Produkte öffnen kann. APIs und Services sind hervorragende Grenzöffner!

Der Agile & DevOps Track zeigt Ihnen, wie Sie Meister der Diplomatie werden.

 

BASTA_SE17_NL_600x200_38210_v1

 

Globalisierung in der IT

Ob man nur im Binnenmarkt bleibt oder globalisiert, ist abhängig von Mut und vom eigenen Geschäftsmodell. Aber man sollte wissen, dass es diese Möglichkeiten gibt. Egal welche Grenze Sie überschreiten wollen, Sie können aus dem umfassenden Angebot der BASTA! genau das Paket schnüren, das Sie brauchen. Mit den Special Days bieten wir Ihnen praktische Konfigurationen an, die Sie jeder Zeit erweitern können.

Was auch immer Sie tun, nach der BASTA! werden Sie nicht mehr ehrfurchtsvoll vor einer Grenze stehen und sich fragen, was auf der anderen Seite ist. Sie werden erkennen, dass Grenzen nichts weiter als bunte Linien auf veralteten Strategiepapieren sind, die sie – mit dem richtigen Rüstzeug ausgestattet – ohne weiteres überschreiten können.

Ich freue mich darauf, Sie auf der BASTA! zu treffen!

Mirko Schrempp, Program Chair der BASTA! und Redakteur des Windows Developer

 

Zum programm

The post Die BASTA! Spring 2017 – reißt die Mauern endlich ein! appeared first on BASTA!.

]]>
Top 20 Social Influencers in .NET Development https://basta.net/blog/top-20-social-influencers-in-net-development/ Mon, 29 Aug 2016 15:19:05 +0000 https://basta.net/?p=14701 Who are the most influential .NET people in the Twittersphere? After analyzing thousands of accounts, we created a list of people that every .NET or Microsoft enthusiast should be following.

The post Top 20 Social Influencers in .NET Development appeared first on BASTA!.

]]>

The .NET, Windows & Open Innovation Conference BASTA! proudly presents the top 20 social influencers regarding the Microsoft and .NET World. All influential people have something in common: they can spread ideas faster and better than anyone else. We are aware that following those people has a handful of perks, including staying on top of the latest news and trends. Therefore, we decided to concoct a list of Twitter accounts all Microsoft and .NET fans should follow.

This list was created by analysing the MozRank and Klout scores of thousands of twitter profiles ranking them by both quality and influence.

If we missed anybody, it has nothing to do with the fact that we don’t think that they are just as important or shareworthy as any person on this list.

We congratulate everyone who made it into this list. If you click on the name or picture of each speaker you will be redirected to their twitter profile where you’ll find the useful information they share daily.

Enjoy!

METHODOLOGY

We first generated a list of one thousand .NET-related Twitter accounts (including all accounts that are related to the world of Microsoft in their bio or in any of their tweets.)

To score the account and rank them accordingly, we analyzed their social authority and reach using two key metrics: MozRank and Klout.

Moz Social Authority Score: Social Authority score is composed of:

    1. The retweet rate of users’ last few hundred tweets.
    2. The recency of those tweets.
    3. A retweet-based model trained on user profile data.

Visit this MOZ blog post for more in-depth information.

Klout Score: Klout uses more than 400 signals from eight different networks to update the Klout Score daily. It’s mainly based on the ratio of reactions a user generates compared to the amount of content he shares. Read more at the Klout score blog.

INFOGRAPHIC

 NET_Influencers_hoch_1200x600_36635_v6 (1)

THE SCORES

Scott Hanselman @shanselman

Tech, Code, YouTube, Race, Linguistics, Web, Parenting, Black Hair, STEM, Inclusion, @OSCON chair, Phony. MSFT, but these are my opinions. @overheardathome

Total score: 165 = Klout score: 79 + Social Authority score: 86

Klout score
Moz score
Total score

Daniel Rubino @Daniel_Rubino

Editor-in-chief of Windows Central. Full-time curmudgeon, science geek and play-acting anarchist. HoloLens user. [email protected]

Total score: 152 = Klout score: 81 + Social Authority score: 71

Klout score
Moz score
Total score

Mary Jo Foley @maryjofoley

I am All About Microsoft on ZDNet (http://blogs.zdnet.com/microsoft ). Oh, and I am also sometimes (more often than not) all about craft beer.

Total score: 150 = Klout score: 86 + Social Authority score: 64

Klout score
Moz score
Total score

Troy Hunt @troyhunt

Pluralsight author. Microsoft Regional Director and MVP for Developer Security. Online security, technology and “The Cloud”. Creator of @haveibeenpwned.

Total score: 150 = Klout score: 73 + Social Authority score: 77

Klout score
Moz score
Total score

Paul Thurrott @thurrott

Paul Thurrott is an award-winning technology journalist and blogger and the author of over 25 books. He is the majordomo at http://Thurrott.com .

Total score: 145 = Klout score: 70 + Social Authority score: 75

Klout score
Moz score
Total score

Chris Heilmann @codepo8

Londoner, German, European. Developer Evangelist – all things open web, writing and helping. Works at Microsoft on Edge, opinions totally my own. #nofilter

Total score: 141 = Klout score: 67 + Social Authority score: 74

Klout score
Moz score
Total score

Dan Wahlin @DanWahlin

Founder of Wahlin Consulting. Consulting & training on Angular, Node.js, ASP_NET, C#, Docker. Author for Pluralsight and Udemy. https://about.me/danwahlin

Total score: 131 = Klout score: 68 + Social Authority score: 63

Klout score
Moz score
Total score

Scott Guthrie @scottgu

I live in Seattle and build a few products for Microsoft

Total score: 130 = Klout score: 60 + Social Authority score: 70

Klout score
Moz score
Total score

Robert Cecil Martin @unclebobmartin

Software Craftsman

Total score: 127 = Klout score: 61 + Social Authority score: 66

Klout score
Moz score
Total score

John Papa @John_Papa

Husband, father, and Catholic enjoying every minute with my family. Disney fanatic, evangelist, HTML/CSS/JavaScript dev, speaker, and Pluralsight author.

Total score: 120 = Klout score: 58 + Social Authority score: 62

Klout score
Moz score
Total score

Kelly Sommers @kellabyte

4x Windows Azure MVP & Former 2x DataStax MVP for Apache Cassandra, Backend brat, big data, distributed diva. Relentless learner. I void warrantie

Total score: 120 = Klout score: 54 + Social Authority score: 66

Klout score
Moz score
Total score

John Allspaw @allspaw

CTO, Etsy. Dad. Author. Guitarist. Student of sociotechnical systems, human factors, and cognitive systems engineering.

Total score: 116 = Klout score: 56 + Social Authority score: 60

Klout score
Moz score
Total score

Christopher Webb @Technitrain

SQL Server MVP, consultant (http://www.crossjoin.co.uk ), trainer (http://www.technitrain.com ) & blogger (http://blog.crossjoin.co.uk/ ) into SSAS, MDX, PowerPivot and DAX

Total score: 113 = Klout score: 55 + Social Authority score: 58

Klout score
Moz score
Total score

Steven Guggenheimer @StevenGuggs

Microsoft’s Chief Evangelist of Developer Experience (DX) – part geek, part businessman – all mobile, all cloud, all tech…all the time.

Total score: 112 = Klout score: 53 + Social Authority score: 59

Klout score
Moz score
Total score

Rey Bango @reybango

I love the Web. Microsoft Developer Advocate for web developers and Microsoft Edge. Contributing Editor to Smashing Mag. Honey badger. Opinions are mine.

Total score: 112 = Klout score: 53 + Social Authority score: 59

Klout score
Moz score
Total score

Erich Gamma @ErichGamma

Software developer and skier

Total score: 111 = Klout score: 52 + Social Authority score: 59

Klout score
Moz score
Total score

K. Scott Allen @OdeToCode

Author @pluralsight & Visual Studio MVP

Total score: 110 = Klout score: 53 + Social Authority score: 57

Klout score
Moz score
Total score

Lee Stott @lee_stott

Engaging & helping UK Academia understand how Microsoft Developer technologies can help complement existing strategies within teaching, learning & research.

Total score: 106 = Klout score: 54 + Social Authority score: 52

Klout score
Moz score
Total score

Anders Hejlsberg @ahejlsberg

Technical Fellow at Microsoft

Total score: 104 = Klout score: 47 + Social Authority score: 57

Klout score
Moz score
Total score

Martin Beeby @thebeebs

Works for Microsoft as a Technical Evangelist.

Total score: 99 = Klout score: 48 + Social Authority score: 51

Klout score
Moz score
Total score

The post Top 20 Social Influencers in .NET Development appeared first on BASTA!.

]]>
„Die HoloLens strahlt vom ersten Moment an eine eigene Faszination aus“ https://basta.net/blog/hololens-dotnet-entwickler/ Thu, 11 Aug 2016 13:50:15 +0000 https://basta.net/?p=14371 Windows 10 läuft als erste Windows Version mit dem gleichen Kern auf zahlreichen Devices. Das Neueste und Zukunftsweisende ist die HoloLens – Microsofts AR bzw. Mixed-Reality-Brille. Die HoloLens ist stark in das Windows 10 und Azure-Konzept von Microsoft eingebunden – daher sollte jeder .NET-Entwickler und BASTA!-Teilnehmer UWP Apps dafür entwickeln können. Wir haben mit Roman Schacherl und Daniel Sklenitzka, softaware gmbh, über die HoloLens gesprochen.

The post „Die HoloLens strahlt vom ersten Moment an eine eigene Faszination aus“ appeared first on BASTA!.

]]>

Was macht die HoloLens für euch als Entwickler spannend?

Die HoloLens strahlt vom ersten Moment an eine eigene Faszination aus – irgendwie ein Blick in die Zukunft, plötzlich Realität geworden. Wir beschäftigen uns auch in der klassischen Software Entwicklung gerne mit User Experience und der Gestaltung von Benutzeroberflächen – da steht man bei der HoloLens natürlich wieder ganz am Anfang.


Augmented Reality, Virtual Reality, Mixed Reality – das ist das Marketing gerade auch kräftig am Werkeln, aber denkt ihr, dass Geräte wie die HoloLens in den Massenmarkt kommen und zur Alltags-Reality werden oder bleiben sie eher eine spannende Lösung für Spezialanwendungen?

Es wird auch in Zukunft noch Anwendungsfälle geben, wo wir mit 2D, Maus und Tastatur ganz gut aufgehoben sind. Und wir werden in den nächsten Jahren nicht alle mit spacigen Visierhelmen durch unsere Umgebung laufen.

Aber die Geräte werden vielfältiger und billiger – und das ermöglicht wieder mehr Lösungen, auch im Businessumfeld. Microsoft hat Windows Holographic gegenüber Drittanbietern geöffnet, man darf gespannt sein, welche Devices in den nächsten Jahren auf den Markt kommen. Zumindest der Spielesektor wird dafür sorgen, dass Mixed Reality den Massenmarkt erreicht – wir sehen aber auch darüber hinaus viel Potential.


Wo seht ihr den “sinnvollsten” Einsatzbereich für die HoloLens?

Gelten Spiele bereits als „sinnvoll“? Die Spieleindustrie hat sicher ganz viele Ideen, mit Mixed Reality ein komplett neues Erlebnis zu kreieren.

Ansonsten sind wir gerade in der Phase, verschiedene Einsatzmöglichkeiten auszuprobieren: Wo bringt 3D wirklich Mehrwert? Wo genügt Augmented Reality, wo benötigt man Mixed Reality? Wie komplex kann die Navigation werden? Es sind noch viele Fragen zu klären und viele kreative Lösungen zu finden.


Wie hoch ist die Hürde für .NET-Entwickler, um für die HoloLens zu schreiben?

Es ist relativ einfach, ein fertiges 3D-Modell in der HoloLens anzuzeigen. Die .NET-Entwickler müssen sich dafür nur wenig Unity-Know-How aneignen, das geht rasch.

Aber: um eine ernsthafte HoloLens-Anwendung zu bauen, ist die Hürde ungleich höher. 3D-Modelle und -Animationen, Sprachsteuerung, Navigationsdesign – hier warten viele Herausforderungen, in denen der typische .NET-Entwickler selten Erfahrung vorweisen kann.

In der BASTA!-Session: Holo, World! Entwickeln für HoloLens, stellen Roman und Daniel den Einstieg in die Entwicklung für die HoloLens im Detail vor.

The post „Die HoloLens strahlt vom ersten Moment an eine eigene Faszination aus“ appeared first on BASTA!.

]]>
BASTA! 2016 – bis Donnerstag, 4. August 2016, anmelden und sparen https://basta.net/blog/programm-highlights-im-ueberblick/ Mon, 01 Aug 2016 09:48:09 +0000 https://basta.net/?p=14098 Die Herbst-Ausgabe der BASTA! lädt vom 19. bis 23 September nach Mainz ein – wer sich bis Donnerstag, den 4. August, anmeldet, kann kräftig sparen.

The post BASTA! 2016 – bis Donnerstag, 4. August 2016, anmelden und sparen appeared first on BASTA!.

]]>
Die nächste große Etappe im Microsoft-Universum ist schon morgen, also am 2. August 2016, erreicht, denn dann erscheint Windows 10 Anniversary (alias Projekt Redstone) – das erste große Update von Windows 10 rund ein Jahr nach Veröffentlichung des neuen Microsoft-OS. Das passende SDK ist natürlich auch in Arbeit und dürfte ebenfalls bald in den Händen der Entwickler landen. Aber auch die Core-Produkte .NET Framework Core, ASP.NET Core und Entity Framework Core sind vor kurzem jeweils in der Version 1.0 erschienen, und bieten mit ihrer Modularität viele neue Möglichkeiten – aber auch Herausforderungen. Da gilt es für Entwickler, einen kühlen Kopf zu bewahren und sich auf dem Laufenden zu halten; am besten mit einem Blick auf das anstehende Programm der BASTA! 2016, die vom 19. bis 23 September in Mainz stattfinden wird.

 

BASTA!-Programm-Highlights im Überblick

Wer sich noch jetzt anmeldet, kann nicht nur kräftig sparen, sondern die Microsoft-Welt in allen Facetten kennenlernen: Das Vortragsprogramm der BASTA! 2016 zeigt relevante Entwicklungen im Bereich der Microsoft- und Web-Technologien und blickt natürlich auch auf zukünftige Trends. An den Hauptkonferenztagen vom 20. bis 22. September präsentieren über 80 renommierte Konferenzsprecher fundierte Vorträge, die sich in themenspezifische Tracks gliedern. Das Themenspektrum der Vorträge erstreckt sich vom .NET-Framework und C#, Agile und DevOps, Data Access und Storage über JavaScript, HTML5 und Security bis hin zu User Interface und Web Development.

Wie immer beginnt und endet die Konferenz am Montag (19. September) und am Freitag (23. September) mit den ganztägigen Power Workshops. Hier können Interessierte ihr Wissen in der Praxis ausbauen und vertiefen. Dabei stehen den Teilnehmern erfahrene Trainer zur Seite, die anhand von praktischen Beispielen relevante Technologie- Entwicklungen vermitteln.

Einen umfassenden Einblick zum Entity Framework erhalten die Teilnehmer beim Workshop „Bye-bye DataSet – Eleganter Datenzugriff mit Entity Framework Code-based Modelling“ mit Dr. Holger Schwichtenberg (IT-Visions.de/5Minds IT-Solutions) und Manfred Steyer (softwarearchitekt.at/ IT-Visions.de). Microsoft bezeichnet das Entity Framework als „recommended Data-Access -Technology for new Applications” – das DataSet steht zunehmend im Abseits. Das sollte Grund genug sein, sich den Object-relational Mapper als Alternative zum DataSet anzusehen, sind sich die beiden Entity-Experten einig. Strategietipps für .NET-Programmierer und Architekten gibt es bei Oliver Sturm (DevExpress) in seinem „.NET-Programmierer-Workshop“ am Konferenz-Montag (19. September).

C#-Revolution“ heißt es außerdem beim Workshop von Rainer Stropek (software architects) am Konferenz-Montag (19. September). In dem Workshop erfahren die Teilnehmer unter anderem Neuerungen in C# und Visual Studio anhand von Codebeispielen.

Im Fokus der Hauptkonferenztage stehen die Special Days: Im Modern Business Applications Day lernen Teilnehmer beispielsweise, wie man – aufbauend auf Erfahrungen im .NET-Umfeld – die Erwartungen von Benutzern erfüllen kann. Praxisnah zeigen Experten, wie man mit HTML5 und JavaScript/TypeScript die Reichweite von Applikationen auf mobile Geräte aller Art ausdehnen kann. Dabei kommen vor allem leichtgewichtige Architekturen mit Web-APIs und Push-Services zum Zug.

Timo Korinth widmet sich in seiner Session dem Flexbox-Modell, das nun endlich alle notwendigen Hilfsmittel liefert, um auch komplexe Applikationslayouts einfach und komplett ohne JavaScript oder jQuery zu erstellen.

Zudem stellt Roman Schacherl das Microsoft Bot Framework vor, das die Implementierung von so genannten „Conversational User Interfaces“ erleichtern soll. Einen kleinen Vorgeschmack auf das Thema gibt es übrigens in diesem Interview.

Was sich hinter dem Begriff Adaptive UI verbirgt, zeigt Jörg Neumann in seiner Session „Adaptive UI: Cross-Device-Designtechniken„. In Tomas Claudius Hubers Session geht es darüber hinaus um die Entwicklung von Universal-Windows-Platform (UWP)-Apps.

Spannend wird es außerdem in der Keynote-Session „The Ethics of Virtual Reality“, in der sich der renommierte Philosoph und Neurowissenschaftler Michael Madary von der Universität Mainz der Frage stellt, wie Virtual Reality unsere Welt beeinflussen wird: „Virtual reality (VR) hardware is now commercially available. There is a great deal of excitement about the uses of VR for educational, therapeutic, and entertainment purposes, but there has been less attention to the possible harmful effects of VR.“

Begleitend zum Vortrags-Programm der BASTA! erwartet die Teilnehmer vom 20. bis 22. September außerdem eine breit aufgestellte Expo etablierter Unternehmen der IT-Industrie. Im Rahmen verschiedener Abendveranstaltungen haben die Teilnehmer zudem die Möglichkeit, ihr berufliches Netzwerk auszubauen und sich mit Konferenz-Sprechern, Vertretern der Industrie sowie anderen Teilnehmern zu aktuellen Themen auszutauschen.

 

Jetzt anmelden und kräftig sparen

Aber das beste kommt erst noch: Wer sich bis Donnerstag, den 4. August anmeldet, erhält das 5-Tages-Special und spart mit satten Frühbucherpreisen. Wer sich etwa mit drei oder mehr Kollegen anmeldet, kann 10 Prozent extra sparen.

The post BASTA! 2016 – bis Donnerstag, 4. August 2016, anmelden und sparen appeared first on BASTA!.

]]>
BASTA! Labs – Keine Theorie, nur Praxis https://basta.net/blog/basta-labs-keine-theorie-nur-praxis/ Thu, 21 Jul 2016 10:32:46 +0000 https://basta.net/?p=13888 DevOps, Cloud, agile Methoden in der Entwicklung und im Management, aber auch schnelle Release-Zyklen, User-Centric-Design und Produktentwicklungen für den dynamischen Mobile-Markt – all diese Themen bestimmen in zunehmendem Maße Ihren Arbeitsalltag als Entwickler und Projektverantwortlicher. Sei es, weil man nur mal ausprobieren will, worüber Medien, wie der Windows Developer, schreiben – oder man sieht, dass Wettbewerber in diese Richtung gehen oder Kunden mit entsprechenden Anforderungen auf Sie zukommen.

The post BASTA! Labs – Keine Theorie, nur Praxis appeared first on BASTA!.

]]>
Zusammen wird Innovation draus

Schaut man sich die Themen im Einzelnen an, was wir hier in den BASTA!-Sessions ausgiebig tun, sind die für sich in jedem Fall interessant, aber vielleicht nicht unbedingt spektakulär. Bringt man jedoch all diese unterschiedlichen Themenbereiche zusammen und verbindet sie sinnvoll, bilden sich neue Möglichkeiten, Erfahrungen, Chancen und Produktideen. Aus diesem Zusammenspiel entstehen offene Räume für Innovationen, weil die Grenzen zwischen den Abteilungen, Rollen und Technologien fallen. Im Unternehmensalltag hat man – auch wenn es wünschenswert ist und wir nur dazu raten können – nicht immer die Zeit, ein Experiment aufzusetzen, an dem man einfach nur lernen kann. Doch auf der BASTA! möchten wir gemeinsam mit Ihnen genau das tun! Deshalb haben wir uns ein Experiment ausgedacht, das uns und Ihnen den Freiraum gibt, persönliche Erfahrungen mit dem Zusammenspiele einzelner Methoden und Technologien zu machen.


BASTA! Labs – offener Raum für neue Erfahrungen

Die BASTA! Labs sind das neue Session-Format der BASTA!. Als professionelle .NET-Entwickler haben Sie auf Basis Ihres Know-hows völlig neue Möglichkeiten, Ihre Produkte zu entwickeln und auf den Markt zu bringen. Und wir bieten Ihnen mit der BASTA! die Gelegenheit, diese Möglichkeiten auszuloten. Fokussiert auf ein Thema konzentrieren Sie sich darauf in gut drei Stunden ein Produkt zu erstellen, ein Methode zu erlernen oder die Cloud zu erobern und dabei DevOps-Luft zu schnuppern. Im direkten Austausch bringen sich alle Beteiligten mit ihren Erfahrungen und ihrer Neugier ein, um Impulse für Neues zu geben und zu bekommen – das gilt auch für die BASTA! Labs Trainer. Der Fokus auf eine Methode oder Technologie, die aber nur in enger Verzahnung mit andern zum Ergebnis führt, bildet die Grundlage eines kreativen und strukturierten Kommunikationsprozesses. Wir laden Sie ein, das BASTA! Lab Ihrer Wahl zu betreten, darin ein Team zu bilden und gemeinsam ein Ziel zu verfolgen. Zusammenarbeit und Kommunikation stehen dabei im Mittelpunkt. Zeitgemäßes (Er-)Arbeiten und Lernen im Team ist dabei das Mindeste, was Sie an Erfahrung mitnehmen werden. Im Idealfall greifen Sie den Impuls der BASTA! Labs auf und bringen ihn in Ihrem Unternehmen ein.


Drei Labs für einen Anfang

Wir starten unser Experiment auf dieser Konferenz mit drei BASTA! Labs zu folgenden Themen

  • Kulturwandel für mehr Agilität: In diesem BASTA! Lab geht es um die Werte, die Agilität begünstigen, welche Herausforderungen in Unternehmen typischerweise bestehen und welche Rolle die Unternehmenskultur bei der Transformation zu Agilität spielt.
  • Cloud Revolution Lab – Entwickeln mit der Cloud: Ziel dieses Labs ist es, anhand eines größeren, durchgängigen Beispiels den Einsatz von Cloud-Technologien, insbesondere Visual Studio Team Services und Azure, im Entwicklungsprozess zu demonstrieren.
  • Let’s get real – Mobile App Development im DevOps-Stil: Von der Konzeption bis zur Auslieferung – und alle machen mit. Egal mit welcher Technologie, am Ende steht eine App!

Mehr Informationen zu den Labs bekommen Sie auf der BASTA! Lab-Seite.


Anmeldung nur vor Ort auf der BASTA!

Damit das Ganze funktioniert, ist die Teilnehmerzahl pro Lab begrenzt – bitte haben Sie dafür Verständnis. Wenn Sie teilnehmen möchten, melden Sie sich auf der BASTA! ab Dienstagvormittag am Check-in für ihr Wunsch-Lab an. Nutzen Sie die Möglichkeit und geben Sie uns nach den Labs Feedback, gerne auch direkt an mich auf der Konferenz, damit wir die BASTA! für Sie als .NET-Entwickler noch besser machen können.

Ich bin gespannt auf Ihre Eindrücke und Ideen.


Mirko Schrempp, Program Chair der BASTA! und Redakteur des Windows Developer

The post BASTA! Labs – Keine Theorie, nur Praxis appeared first on BASTA!.

]]>
“Die wenigsten Unternehmen haben das Agile Manifest verstanden!” https://basta.net/blog/die-wenigsten-unternehmen-haben-das-agile-manifest-verstanden/ Sun, 10 Jul 2016 09:34:08 +0000 https://basta.net/?p=13418 Agil ist agil, oder? Dabei kennen nicht wenige die Situation, dass man im Unternehmen „agil arbeitet“, jedoch gefühlt unendlich weit davon entfernt ist. Mit dem Trainer Frank Düsterbeck haben wir darüber gesprochen, was sich dahinter verbirgt, und was man beachten muss, wenn man Agilität im Unternehmen einführen möchte.

The post “Die wenigsten Unternehmen haben das Agile Manifest verstanden!” appeared first on BASTA!.

]]>
Frank, agil ist nicht gleich agil. Du bist der Überzeugung, dass man unterschiedliche Ebenen der Agilität im Unternehmen unterscheiden sollte. Welche wären das?

Frank Düsterbeck: Genau das ist eine zentrale Frage. Das Agile Manifest – welches letztendlich durch seine Werte und Prinzipien das Wort „Agil“ definiert – hat als Leitsatz: „Wir erschließen bessere Wege, Software zu entwickeln, indem wir es selbst tun und anderen dabei helfen“. Was bedeutet dann Agilität genau auf Projekt-, Team- oder gar Unternehmensebene? Gibt es Agilität auch in IT-fremden Abteilungen oder Bereichen?

Ich denke: ja. Agil bezieht sich zwar klar auf Softwareproduktion. Agil verbindet aber immer den Umgang mit Menschen und den Umgang mit Komplexität. Das kann man überall anwenden, wenn man die Herausforderungen der jeweiligen Anwendungsfelder berücksichtigt.

Aus deiner Praxiserfahrung: Wie viele der Unternehmen, die überzeugt sind, agil zu arbeiten, tun das wirklich?

Düsterbeck: Ein wirkliches tiefes Verständnis für das agile Manifest ist in den wenigsten Unternehmen vorhanden. Leider hängt das Agile Mindset eines Unternehmens immer stark von den wirklichen Entscheidern im Unternehmen ab. Das Mittelmanagement kann z. B. nur sehr eingeschränkt agil wirken, wenn der C-Level noch im Command & Control hängt.

 

Familiär geführten Unternehmen tragen, ohne es zu wissen, viel Agilität in sich.

Hinzu kommt: Mit den Werten im Hintergrund die Prinzipien zu leben, ist für viele Unternehmen, insbesondere jene, die durch Shareholder wieder bestimmten Zwängen unterlegen, nicht einfach. Die Unternehmen, die wir beraten, sind teilweise streng hierarchisch strukturiert bis hin zu agilen Unternehmen. Jedes dieser Unternehmen schätzt sich aber ehrlich und richtig ein, ob es agil ist oder nicht. In meiner BASTA! Session „Hurra, wir werden agil – aber wie?“ gehe ich insbesondere darauf ein, wie seitens der Unternehmensführung und bei den Mitarbeitern ein agiles Bewusstsein geschaffen werden sollte.

Was ich interessant finde ist, dass es in Deutschland schon immer das Familienunternehmen gab. Nicht wenige dieser „familiär“ geführten Unternehmen tragen, ohne es zu wissen, viel Agilität in sich.

Angenommen ein Unternehmen möchte agile Arbeitsweisen einführen: Was sind aus deiner Sicht die drei wichtigsten Aspekte bzw. Bereiche, die im Vorfeld analysiert werden sollten, um überhaupt zu entschieden, ob und wie man agiles Arbeiten einführt?

Düsterbeck: Das lässt sich leider pauschal nicht beantworten. Die Einführung agiler Rahmenwerke wie z. B. Scrum und Praktiken wie z. B. Story Mapping hängt immer sehr stark von der Organisation des Unternehmens ab. Wir betrachten also, wenn wir Unternehmen bei der agilen Transition begleiten, zuerst das Handlungsfeld der Organisation und Transition. Dieses gibt den Rahmen. Im Weiteren gucken wir auf die Menschen und deren Interaktionen, die Prozesse und Praktiken und die Technologien und Infrastrukturen.

Das sind jetzt zwar vier Aspekte (anstatt der in der Frage geforderten drei) aber diese sind nicht getrennt voneinander zu betrachten, sondern in der Analyse immer in ihrem Zusammenspiel. Anhand dessen lässt sich dann genauer sagen, was zu tun ist, um bessere Wege für die Entwicklung von Software zu erschließen.

Ist die Entscheidung für die Einführung von Agilität gefallen, was sind gerade in der Anfangszeit die schwierigsten Hürden?

Düsterbeck: Die äußeren Zwänge und Rahmenbedingungen, in denen sich ein Unternehmen befindet. Ist die Entscheidung hin zur Agilität gefallen, das Bewusstsein aller Beteiligten für das Warum geschärft, kann es „eigentlich“ losgehen. Veränderungen bedeuten aber oftmals im ersten Schritt auch Verschlechterungen bzw. Investitionen und wirken sich selten ad hoc positiv auf die Wertschöpfung bzw. Wirtschaftlichkeit aus. Ist ein Unternehmen unter Druck, muss handeln, kann aber nur wenig „Change“ verkraften, so führt dies oft dazu, dass die notwendigen Veränderungen auf die lange Bank geschoben und die alten Muster weitergelebt werden.

 

Die Unternehmenskultur kann eine große Hürde sein.

Eine weitere große Hürde kann die Kultur eines Unternehmens sein. Diese lässt sich weder direkt beeinflussen noch mal eben schnell ändern. Herrscht z. B. eine schlechte Fehlerkultur, werden Mitarbeiter oder Betriebsräte mit Sicherheit Ängste vor Transparenz, Inspektion und Adaption haben, wie z. B. Scrum fordert, und sich ggf. auch dagegen stemmen.

Agile Praktiken werden immer häufiger auch in anderen Bereichen wie etwa dem Marketing oder HR angewendet, sodass sie im Sinne einer DevOpsBiz-Zusammenarbeit anschicken, auch die Firmenkultur zu verändern. Was sind deine Erfahrungen mit der Ausdehnung agiler Praktiken auf Bereiche abseits der Softwareentwicklung?

Düsterbeck: Äußerst positive. Früher hat die Softwareentwicklung versucht, von der Industrie abzugucken und entsprechende Prozesse und Praktiken wie den Wasserfall, Gantt-Diagramme etc. übernommen – mit wenig oder sogar verheerendem Erfolg. Diese Vorgehensweisen passen leider nicht auf die Softwareentwicklung. Sie sind dort schlicht nicht erfolgreich anwendbar, um der Komplexität gerecht zu werden. Heute ist die Welt „VUCA“. Das bedeutet, dass das, was wir in der Softwareentwicklung in Bezug auf den Umgang mit Komplexität gelernt haben, auch auf andere Anwendungsbereiche projizierbar ist. Wir beraten inzwischen längst nicht nur IT-Abteilungen oder IT-Unternehmen, sondern immer mehr andere Industrie- und Dienstleitungsunternehmen der verschiedensten Branchen. Ein Thema ist hier immer, wie Agilität für ihre Geschäftsmodelle und Organisationen einsetzbar ist. Die Digitalisierung spielt hier die treibende Rolle.

The post “Die wenigsten Unternehmen haben das Agile Manifest verstanden!” appeared first on BASTA!.

]]>
BASTA! 2016 – bis Donnerstag, 30. Juni 2016, anmelden und sparen https://basta.net/blog/basta-2016-bis-donnerstag-30-juni-2016-anmelden-und-sparen/ Mon, 27 Jun 2016 10:17:23 +0000 https://basta.net/?p=13130 Die Herbst-Ausgabe der BASTA! lädt vom 19. bis 23. September nach Mainz ein – wer sich bis Donnerstag, den 30. Juni, anmeldet, kann kräftig sparen.

The post BASTA! 2016 – bis Donnerstag, 30. Juni 2016, anmelden und sparen appeared first on BASTA!.

]]>
Der Sommer ist so langsam voll im Gange, doch das Windows-Entwicklerteam arbeitet weiterhin auf Hochtouren und hat in den letzten Wochen einen Windows-10-Anniversary-Build nach dem anderen veröffentlicht – für Desktop und für Mobile. Daneben sind seit kurzem zahlreiche neue Docker-Produkte für Microsoft verfügbar, von denen vor allem die Azure-Cloud profitieren soll, darunter zum Beispiel die Container-Administrationssoftware Docker Datacenter (DCC). Doch damit nicht genug, auch rund um Visual Studio und den Code-Editor Visual Studio Code gab es einige Updates. An allen Ecken und Kanten kommen also Neuerungen auf Entwickler zu, mit denen es sich auseinanderzusetzen gilt. Wer bei diesen rasanten Entwicklungen auf dem neuesten Stand bleiben und mithalten will, wirft am besten einen Blick auf das anstehende Programm der BASTA!, die vom 19. bis 23 September in Mainz stattfinden wird.

 

BASTA!-Programm-Highlights im Überblick

Wer sich noch jetzt anmeldet, kann nicht nur kräftig sparen, sondern die Microsoft-Welt in allen Facetten kennenlernen: Das Vortragsprogramm der BASTA! 2016 zeigt relevante Entwicklungen im Bereich der Microsoft- und Web-Technologien und blickt natürlich auch auf zukünftige Trends. An den Hauptkonferenztagen vom 20. bis 22. September präsentieren über 80 renommierte Konferenzsprecher fundierte Vorträge, die sich in themenspezifische Tracks gliedern. Das Themenspektrum der Vorträge erstreckt sich vom .NET-Framework und C#, Agile und DevOps, Data Access und Storage über JavaScript, HTML5 und Security bis hin zu User Interface und Web Development.

Wie immer beginnt und endet die Konferenz am Montag (19. September) und am Freitag (23. September) mit den ganztägigen Power Workshops. Hier können Interessierte ihr Wissen in der Praxis ausbauen und vertiefen. Dabei stehen den Teilnehmern erfahrene Trainer zur Seite, die anhand von praktischen Beispielen relevante Technologie- Entwicklungen vermitteln.

C#-Revolution“ heißt es etwa beim Workshop von Rainer Stropek (software architects) am Konferenz-Montag (19. September). „C# und .NET machen einen radikalen Wandel durch. Open Source, Plattformunabhängigkeit, grundlegendes Redesign, neue Compilerplattform, als C#- Entwickler gibt es viel Neues zu lernen“, erklärt der C#-Experte. In dem Workshop erfahren die Teilnehmer unter anderem Neuerungen in C# und Visual Studio anhand von Codebeispielen.

Einen umfassenden Einblick zum Entity Framework erhalten die Teilnehmer außerdem beim Workshop „Bye-bye DataSet – Eleganter Datenzugriff mit Entity Framework Code-based Modelling“ mit Dr. Holger Schwichtenberg (IT-Visions.de/5Minds IT-Solutions) und Manfred Steyer (softwarearchitekt.at/ IT-Visions.de).

Im Fokus der Hauptkonferenztage stehen die Special Days: Im Modern Business Applications Day lernen Teilnehmer beispielsweise, wie man – aufbauend auf Erfahrungen im .NET-Umfeld – die Erwartungen von Benutzern erfüllen kann. Praxisnah zeigen Experten, wie man mit HTML5 und JavaScript/TypeScript die Reichweite von Applikationen auf mobile Geräte aller Art ausdehnen kann. Dabei kommen vor allem leichtgewichtige Architekturen mit Web-APIs und Push-Services zum Zug.

So geht Christian Weyer in seiner Session „Leichtgewichtige Architekturen mit ASP.NET Web API und SignalR“ den Fragen auf den Grund, wie man eine leichtgewichtige Integration von Systemen mit interoperabler Kommunikation gestemmt bekommt, und wie man Daten eigener Services mittels Real-Time-Web-Kommunikation in Clients hinein pushen kann.

Timo Korinth widmet sich in seiner Session dem Flexbox-Modell, das nun endlich alle notwendigen Hilfsmittel liefert, um auch komplexe Applikationslayouts einfach und komplett ohne JavaScript oder jQuery zu erstellen.

Gebündelte Themen rund um die native Cross-Plattform-Entwicklung gibt es außerdem auf dem Universal Apps Day. Mit der Windows Universal Platform hat Microsoft ein Ökosystem für Apps geschaffen, die nicht nur auf PCs, sondern gleichermaßen auch auf Tablets, Smartphones, IoT-Devices und weiteren Formfaktoren laufen. Hierdurch eröffnen sich dem Entwickler viele neue Betätigungsfelder jenseits der klassischen Desktopentwicklung.

Unter anderem stellt Roman Schacherl  das Microsoft Bot Framework vor, das die Implementierung von so genannten „Conversational User Interfaces“ erleichtern soll. Einen kleinen Vorgeschmack auf das Thema gibt es übrigens in diesem Interview.

Was sich hinter dem Begriff Adaptive UI verbirgt, zeigt Jörg Neumann in seiner Session „Adaptive UI: Cross-Device-Designtechniken„. In Tomas Claudius Hubers Session geht es darüber hinaus um die Entwicklung von Universal-Windows-Platform (UWP)-Apps.

Spannend wird es außerdem in der Keynote-Session „The Ethics of Virtual Reality“, in der sich der renommierte Philosoph und Neurowissenschaftler Michael Madary von der Universität Mainz der Frage stellt, wie Virtual Reality unsere Welt beeinflussen wird: „Virtual reality (VR) hardware is now commercially available. There is a great deal of excitement about the uses of VR for educational, therapeutic, and entertainment purposes, but there has been less attention to the possible harmful effects of VR.“

Begleitend zum Vortrags-Programm der BASTA! erwartet die Teilnehmer vom 20. bis 22. September außerdem eine breit aufgestellte Expo etablierter Unternehmen der IT-Industrie. Im Rahmen verschiedener Abendveranstaltungen haben die Teilnehmer zudem die Möglichkeit, ihr berufliches Netzwerk auszubauen und sich mit Konferenz-Sprechern, Vertretern der Industrie sowie anderen Teilnehmern zu aktuellen Themen auszutauschen.

 

Jetzt anmelden und kräftig sparen

Doch das Beste kommt erst noch: Wer sich bis Donnerstag, den 30. Juni, anmeldet, spart bis zu 200 Euro mit den Frühbucherpreisen. Daneben gibt es den Kollegenrabatt: Wer sich mit drei oder mehr Kollegen anmeldet, kann 10 Prozent extra sparen.

The post BASTA! 2016 – bis Donnerstag, 30. Juni 2016, anmelden und sparen appeared first on BASTA!.

]]>
Node.js – nur ein Hype oder doch mehr? Ein Blick auf die Licht- und Schattenseiten der Technologie https://basta.net/blog/node-js-ein-blick-auf-die-licht-und-schattenseiten-der-technologie/ Mon, 20 Jun 2016 14:16:05 +0000 https://basta.net/?p=12835 Im Jahr 2009 hat Node.js das Licht der Welt erblickt. Mithilfe der Technologie findet JavaScript auch auf dem Webserver eine immer größere Verbreitung, denn Entwickler können damit auf Client und Server die gleiche Programmiersprache verwenden. Namenhafte Firmen wie Microsoft selbst, Google, PayPal, New York Times, GitHub u.v.m. setzen bereits auf das leistungsstarke Node.js.

The post Node.js – nur ein Hype oder doch mehr? Ein Blick auf die Licht- und Schattenseiten der Technologie appeared first on BASTA!.

]]>
Wir sprechen mit Gregor Biswanger darüber, wieso sich auch .NET-Entwickler damit auseinandersetzen sollten, wie der aktuelle Entwicklungsstand von Node.js aussieht, welche Einsatzmöglichkeiten es gibt und wo die Stärken und Schwächen der Technologie liegen. Mehr Details zu diesen Fragen verrät Gregor Biswanger in seiner BASTA!-Session „Kuck mal, Node.js! Einstieg für .NET-Entwickler“ am 21. September 2016.

Herr Biswanger, das Jahr 2009 war die Geburtsstunde von Node.js. Wie hat sich die Technologie seitdem entwickelt?

Das Jahr 2009 war für JavaScript allgemein ein evolutionäres Jahr. Nicht nur Node.js erblickte das Licht der Welt, sondern ebenfalls das etablierte Cross-Platform Framework Nitobi PhoneGap, das heute auch als Apache Cordova bekannt ist. Darauf folgten die berühmte NO-SQL-Datenbank MongoDB und viele weitere spannende JavaScript-Technologien.

Zudem erhielt ECMAScript selbst ein wichtiges Update mit dem „strict mode“-Feature, das aus der Sprache Perl übernommen wurde und für eine erweiterte Fehlerprüfung sorgt.

Es hat sich also einiges bewegt in dieser Zeit. Der Papa von Node.js, Ryan Dahl, hatte wohl selbst nicht mit einem so großen Erfolg gerechnet. Denn ein weiteres wichtiges Jahr für ihn und JavaScript war 2011. Er erhielt zu dieser Zeit zwei wertvolle Auszeichnungen. So wurde ihm auf der Oscon-Konferenz der O’Reilly-Open-Source-Award verliehen und auf der InfoWorld erhielt er den Bossie Award für die beste Open-Source-Software in der Kategorie „Entwicklertools“.

Ein Jahr später nahm Node.js auch nochmal so richtig Fahrt auf und landete bei GitHub auf Platz 2, als das beliebteste Repository. Auf Platz 1 war übrigens Twitter Bootstrap.

Ein paar tausend Forks zeigten, dass die Entwickler-Community viele weitere Ideen mit Node.js hatte. So entstand zum Beispiel auch das Node-WebKit, mit dem sich Cross-Platform- Desktop-Software bauen lässt, ohne das ein Framework wie die Java-Runtime oder das .NET-Framework benötigt wird. Das ist aus Deployment-Sicht ein Hochgenuss. Zudem entstanden Module für einen hardwarenahen Zugriff, sodass man auf Mikrocontrollern im IoT-Bereich ebenfalls mit JavaScript arbeiten kann.

Jetzt wäre natürlich die Frage, ob Node.js nicht nur ein Hype ist? Nein, nach sieben Jahren hat sich Node.js mit seinen Vorzügen der Performance, Skalierbarkeit und Produktivität, bei namenhaften Unternehmen etabliert. So setzt PayPal, LinkedIn, Walmart, The New York Times, Trello, ebay, GitHub, Google und sogar Microsoft mit Yammer auf Node.js.

Der Technologieradar von ThoughtWorks bewertet Node.js daher als eine ausgereifte und bewährte Technologie, die für den Unternehmenseinsatz geeignet ist.

Warum sollten sich .NET-Entwickler Ihrer Meinung nach mit Node.js auseinandersetzen?

Wir von CleverSocial arbeiten bereits seit einem Jahrzehnt mit .NET und C#. Wir haben uns auch auf die Cross-Platform-Entwicklung spezialisiert; hierbei ist JavaScript führend an der Spitze. Das JavaScript jetzt auch noch serverseitig zum Einsatz kommen sollte, belächelten wir immer wieder und verstanden nicht, weshalb man sich so etwas antun möchte. Als wir uns durch unsere Erfahrungen mit dem Node-Webkit dann mit Node.js vertrauter machten, wagten wir bei einem neuen Produkt, auf Node.js zu setzen.

Wir wurden extrem positiv überrascht von unserer Produktivität und dem leichtgewichtigen Code, sodass wir uns die Frage stellten, wieso überhaupt noch .NET?

Dass nicht nur wir eine so tolle Erfahrung gemacht haben, zeigt ein spannender Artikel des PayPal-Engineering-Teams. Das Team startete im Januar 2013 mit fünf Java-Entwicklern, um eine Web-App zu programmieren. Sie verwendeten hierbei das Spring Framework. Im März 2013 wagten sie sich zusätzlich mit zwei Entwicklern an Node.js. Nun ist auch für PayPal die große Überraschung gekommen. Im Juni 2013 hatten beide Teams die fertige Web-App mit der gleichen Funktionalität. Das bedeutet: Sie haben mit weniger Entwicklern fast doppelt so schnell programmiert. Außerdem hatten sie beim Node.js-Projekt circa 33 Prozent weniger Code und circa 40 Prozent weniger Daten.

Somit gibt es zahlreiche Gründe für Node.js, nicht nur für .NET-Entwickler. Uns hat es auf jeden Fall überzeugt.

Für welche Zwecke kann Node.js eingesetzt werden?

Node.js läuft auf Windows, Mac und Linux – auf dem Server und auf dem Client. Ebenfalls läuft es auf Mikrocontrollern wie Arduino, Intel Galileo Board oder Intel Edison.

Somit kann Node.js für eine Vielzahl an Zwecken eingesetzt werden. Als Web-Server für das Backend, als Desktop-Software oder eben als Anwendung für IoT-Geräte.

Welche Vorteile ergeben sich für Entwickler bei der Nutzung von Node.js?

Auf jeden Fall Produktivität und eine hohe Performance. Node.js ist aktuell der zweitschnellste Web-Server. Auf Platz eins ist Google´s GO-Lösung; wobei man hier serverseitig wieder eine andere Sprache beherrschen muss. Wenn man heutzutage eine Web-Anwendung baut, muss man spätestens beim Client mit JavaScript arbeiten. Das ist immer wieder dann ein starker Kontext-Wechsel, wenn man serverseitig mit einer anderen Sprache arbeitet. Bei Node.js bewege ich mich immer in der gleichen Umgebung. Das man hierbei auch Code wiederverwenden kann, ist ein erheblicher Vorteil. Man bedenke nur einmal das Datenformat JSON, das sich ohne weiteres in beiden Welten nutzen lässt.

Auf welche Herausforderungen müssen sich Entwickler einstellen, die Node.js nutzen?

Auf ein erneutes lernen. Wobei das für Softwareentwickler zu ihrem Beruf gehört. Sowie jeder Steuerberater jedes Jahr neue Gesetze lernen muss. Wer JavaScript hingegen nur oberflächlich beherrscht und darauf weiter arbeiten möchte, sollte es unbedingt intensiver kennenlernen. Das gefährliche an JavaScript ist die C-ähnliche Syntax, die damals aus Marketingzwecken einfloss. Hierbei möchte der .NET-Entwickler gewohnte Aspekte wiederverwenden und stößt nur auf Probleme. Hat man hingegen die Sprache wirklich richtig kennengelernt, bemerkt man, weshalb selbst C# in Sachen Funktionalität immer mehr in die Richtung von JavaScript rückt. Als Beispiel wäre hier nur mal das dynamic-Feature oder „Projekt Roslyn“ zu erwähnen. Bei meinen JavaScript- und Node.js-Schulungen konnte ich selbst jahrelangen JavaScript-Entwicklern die Augen öffnen. Ich kann es daher nur jedem ans Herz legen. Schaut euch an, was unter der Haube von JavaScript oder Node.js läuft. Ihr könnt nur profitieren.

Node.js und ASP.NET im Vergleich: Wo liegen die jeweiligen Stärken, wo die Schwächen?

Das lustige hierbei ist, das Microsoft mit ASP.NET Core genau die gleichen Konzepte von Node.js adaptieren möchte. Das gelingt ihnen aber nach zwei Jahren Entwicklung nur sehr langsam. Wir haben es bei einem unserer ASP.NET-Core-Prototypen aktuell aufgegeben.

Eine große Stärke von ASP.NET ist auf jeden Fall die tolle Unterstützung von O/R-Mappern wie dem Entity Framework. Wobei man bei Node.js mit einer relationalen Datenbank jeden SQL-Befehl selbst schreiben muss. Anders hingegen ist es bei NO-SQL-Datenbanken. Hier hat man bei Node.js mehr Komfort, als unter .NET. Die Datenbank liefert JSON direkt und für die Abfragen gibt es JavaScript-Funktionen. Für die Kommunikation wird kein Mapping benötigt, was wiederum einen Pluspunkt auf Seiten der Produktivität gibt.

Eine weitere Stärke bei ASP.NET ist, dass es für unterschiedliche Belange einen goldenen Weg gibt. In JavaScript gibt es hingegen tausende an Möglichkeiten für eine Lösung. Was man allerdings auch wieder positiv bewerten kann, wenn man eine wirkliche Auswahl möchte. So verfügt beispielsweise der Node-Package-Manager über 236.209 Pakete. NuGet hingegen über lediglich 49.781 (Stand Januar 2016).

Was die Sprache anbetrifft, ist C# eine richtig tolle Sprache, die aber auch ihre Grenzen hat. Man bedenke nur einmal, wie kompliziert es wird, wenn man mit Generics und Reflection komplexe Lösungen bauen möchte. Das geht bei JavaScript erheblich einfacher. Bei der Verwendung mit TypeScript wird hierbei der gleiche C#-Komfort gewährleistet, was die Fehleranfälligkeit angeht.

Im Großen und Ganzen möchte ich mich aber auch nicht nur auf DIE eine Technologie festlegen. Wir haben bei unseren Produkten ebenfalls polyglotte Lösungen und da wird sich auch der Weg in der Entwicklerwelt hinbewegen. Wir haben für spezielle Anwendungsfälle ein Web-API und nutzen dazu den SQL Server, an anderer Stelle kommt hauptsächlich nur Node.js, MongoDB und Redis zum Einsatz. Das alles für eine Web-Anwendung. Nicht ohne Grund wird das Thema Microservices immer relevanter.

The post Node.js – nur ein Hype oder doch mehr? Ein Blick auf die Licht- und Schattenseiten der Technologie appeared first on BASTA!.

]]>
.Net Core 1.0 ist eine neue Welt! – Schnellere Innovationen, Komplexität, Cross-Platform und Open Source https://basta.net/blog/net-core-1-0-ist-eine-neue-welt-schnellere-innovationen-komplexitaet-cross-platform-und-open-source-interview-mit-christian-nagel/ Mon, 13 Jun 2016 09:34:55 +0000 https://basta.net/?p=12152 Mit .NET Core 1.0 gibt es bei .NET die größten Änderungen seit dem .NET Framework 1.0. .NET ist Open Source, läuft nicht nur auf Windows, sondern auch auf Linux und Mac, und kann mit kleinen NuGet Packages viel agiler weiterentwickelt werden. Wir sprechen mit Christian Nagel über das Konzept hinter und den aktuellen Stand von .NET Core 1.0, welche Vor- und Nachteile Entwickler erwarten dürfen und ob sich der Umstieg lohnt. Noch detaillierter geht Christian Nagel auf diese Fragen in seiner BASTA! Session "ASP.NET Core und ASP.NET MVC Core 1" am 22. Februar 2017 ein.

The post .Net Core 1.0 ist eine neue Welt! – Schnellere Innovationen, Komplexität, Cross-Platform und Open Source appeared first on BASTA!.

]]>

Hier geht´s zum aktuellen .NET Core 2.0 Beitrag

Herr Nagel, welches neue Konzept verbirgt sich hinter .NET Core 1.0?

Es ist eine Version 1, auch wenn es bei RC1 noch .NET Core 5.0 war. Version 1 ist aber viel zutreffender. Version 1 heißt aber nicht, dass noch nicht viel dabei ist – im Gegenteil, .NET Core ist jetzt schon riesig. Version 1 soll mehr darauf hinweisen, dass es sich um einen Neustart handelt, es ist ein neues .NET.

Um die Konzepte auf die .NET Core aufbaut zu beleuchten, macht es zuerst Sinn, die Gründe die zu .NET Core führten zu beleuchten:

  • Schnellere Innovationen

NuGet Packages, die schon mit dem .NET Framework eingeführt wurden, ermöglichen schnellere Updates als wir es mit dem .NET Framework gewohnt waren. Als .NET 1.0 im Jahr 2000 vorgestellt wurde, war es noch möglich auf Update-Zyklen von ca. zwei Jahren aufzubauen. Heute ist das nicht mehr möglich, wir wollen schneller auf neue Features zugreifen. Mit einigen Bereichen vom .NET Framework ist das auch schon geschehen. Entity Framework 6 wird komplett aus NuGet Packages geliefert, zudem auch z.B. System.Collections.Immutable und System.Collections.Concurrent. Allerdings: Funktionalität, die bereits mit dem .NET Framework am System installiert ist, und auch die Common Language Runtime kann nicht so einfach ausgetauscht werden, um neue Features anzubieten. Dazu sind Administratorrechte notwendig. Entity Framework selbst hatte viele Jahre darunter gelitten, dass dieses Framework teilweise mit dem .NET Framework am System installiert wurde und zusätzliche Klassen über NuGet Packages gekommen sind. Damit gab es einige Einschränkungen.

Bei .NET Core 1.0 kommt jetzt alles über NuGet Packages – auch die Runtime. Das ermöglicht schnelle Innovationen in allen Bereichen.

Nachdem mit Applikationen jetzt auch die Runtime ausgeliefert wird, muss ich mit Webanwendungen nicht mehr darauf warten, bis mein Provider die neueste .NET Version am Server installiert hat. Welche Version ich verwende, entscheide ich jetzt mit der Applikation.

  • Komplexität des .NET Frameworks

Das .NET Framework ist im Lauf der Jahre sehr komplex geworden. Viele Teile im .NET Framework bestehen auch aus Libraries, die in neuen Applikationen heute nicht mehr benötigt werden, z.B. .NET Remoting, LINQ to SQL, die alten Non-Generic Collection Klassen und viele mehr. Wenn man die letzten Jahre mit .NET mitgewachsen ist, ist das weniger ein Problem. Sehr wohl ist es aber ein Problem für Neueinsteiger in .NET. Wie soll hier entschieden werden können, welche Klassen verwendet werden sollen und welche Teile nicht mehr „State of the Art“ entsprechen?

Nachdem alle Teile von .NET Core jetzt in kleineren NuGet Packages geliefert werden, ist es möglich Referenz-Packages zu definieren. Entwickler können diese Referenz-Packages nutzen. Damit ist dann auch gleich definiert, welche Packages mit welchen Versionen in der Firma genutzt werden sollen/dürfen. Das hilft Neueinsteigern Entscheidungen zu treffen und nicht gleich das riesige .NET Framework vor sich zu sehen.

  • Cross-Platform

Windows auf allen verschiedenen Devices unterschiedlichster Art und Größen zu haben – vom Desktop und Smartphone über die HoloLens und IoT Devices ist schön. Die tatsächliche Verbreitung kann aber nicht außer Acht gelassen werden. Auf mobilen Devices haben Android und iOS weit größere Marktanteile, und am Server ist Linux weit verbreitet.

Ein großer Vorteil von .NET Core 1.0 ist etwas, das bisher nicht von .NET abgedeckt werden konnte: .NET auf Linux und OS-X – und zwar mit Support von Microsoft. Es war schon spannend zu sehen, wie rasch weitere Plattformen unter https://www.github.com/dotnet hinzugefügt wurden und praktisch alles gleich funktioniert. Ich habe inzwischen auch Vorträge auf Linux-Konferenzen gehalten. Vor wenigen Jahren war das noch nicht vorstellbar. .NET Core haben wir auf unterschiedlichen Plattformen – und auch die Tools!

  • Open Source

Dass .NET Core – inklusive der Runtime, dem C#-Compiler und ASP.NET MVC 6 – jetzt Open Source ist, hat große Vorteile. Man muss ja jetzt nicht gleich selbst Erweiterungen schreiben oder Bugs korrigieren. Ich bin seit frühen Betas dabei – so kann man auch Design-Entscheidungen mitverfolgen. Warum manches so oder so gelöst wurde, ist so leichter zu verstehen.

Zu Erweiterungen von C# 7 und auch schon C# 8 ist es z.B. jetzt möglich, Feedback zu geben und selbst die nächsten Versionen von C# zu beeinflussen. Wie erwähnt: Bug-Fixes müssen nicht selbst gemacht werden. Ich hatte mehrere Bugs auf GitHub reported. Bei einem meiner Reports habe ich ursprünglich nicht damit gerechnet, welchen Impact der Bug dann letztendlich wirklich haben würde. Aber nach nur sechs Tagen gab es einen Fix in den Daily Builds. Über GitHub lässt sich so direkt mit den .NET-Entwicklerteams kommunizieren. Erweiterungen zu schreiben, die dann auch verwendet werden können, ist natürlich auch möglich.

.NET Core 1.0 ist wirklich eine neue Welt: .NET-Code auf unterschiedlichen Plattformen, Open Source und in vielen kleinen NuGet Packages. .NET Core ist bereits in der ersten Version riesig.

Der .NET Framework & C# Track auf der BASTA! Konferenz

Wie schätzen Sie den aktuellen Stand von .NET Core 1.0 ein?

Aktuell haben wir RC2, RTM soll am 27. Juni 2016 verfügbar sein. Go live ist jetzt schon mit RC2 möglich (und der 27. Juni ist ja bald). RC1 als „Release Candidate“ zu verkaufen war ein Fehler, der Name hat sich geändert, damit auch Namespaces und Package Namen sowie auch noch das API selbst. Ich sehe RC1 nicht als „Release Candidate“ – und Microsoft mittlerweile auch nicht mehr. Mit RC2 ist das aber anders. Während es von RC1 zu RC2 noch große Änderungen gegeben hat, ist RC2 ein wirklicher „Release Candidate“. API-Schnittstellen werden nur noch geändert, wenn kritische Probleme auffallen. Die Tools werden sich noch ändern, die sind beim Release von .NET Core auch erst Preview 2. Ein Release der Tools einige Monate später ändert aber nichts an der fertigen Applikation.

.NET Core 1.0 ist jetzt einsetzbar, die meisten neuen Applikationen würde ich auch jetzt damit implementieren.

Welche Vorteile bringt .NET Core 1.0 für Entwickler?

Ein großer Vorteil von .NET Core 1.0 liegt darin, dass .NET jetzt auf anderen Plattformen verfügbar ist – nicht mehr nur Windows, sondern auch Linux und OS-X. Das ermöglicht den Einsatz von .NET in Szenarien, die früher einfach nicht möglich waren.

Darüber hinaus ist .NET Core 1.0 einfach moderner. Das .NET-Framework hat doch einige Jahre auf dem Buckel, inklusive unterschiedlichster Patterns, die implementiert wurden. Ich denke da z.B. an die unterschiedlichsten asynchronen Patterns, die seit .NET 1.0 entstanden sind, und auch im .NET-Framework durchgehend implementiert wurden. Alte Zöpfe konnten mit .NET Core abgeschnitten werden. .NET Core hat moderne Patterns im Einsatz. Dass Dependency Injection tief im Framework integriert ist, wird rasch erkannt, sobald man sich mit ASP.NET Core beschäftigt. Ich verwende Microsoft.Extensions.DependencyInjection aber nicht nur mit ASP.NET Core, sondern auch mit WPF- und UWP-Applikationen. Außerdem gibt es eine neue, flexible Konfiguration und neue Logging Interfaces mit denen bestehende Logging Frameworks verwendet werden können.

Neue Tools für die Command Line sind auch da. So ist es mit dem dotnet Command möglich, den initialen Source Code zu erzeugen, NuGet Packages zu laden und zu erstellen, das Programm zu kompilieren, zu testen, User Secrets zu managen, Entity Framework Migrations durchzuführen und vieles mehr. Dabei enthält das Tool nicht gleich alle Features, sondern es ist erweiterbar.

.NET Core 1.0 ist einfach ein modernes Framework, das die Bedürfnisse von heute erfüllt.

Wo sehen Sie aktuell Schwächen der neuen Version?

Mit dem Release von .NET Core 1.0 stehen die Tools nur als Preview zur Verfügung. Der eigentliche Release der Tools steht erst mit der nächsten Version von Visual Studio, Visual Studio „15“, auf dem Programm. Mit den Tools wird sich dabei aber auch noch viel ändern. Was mich schon immer bei .NET Core gestört hatte, ist, dass einige Tools in Visual Studio einfach nicht für diese neue Technologie zur Verfügung stehen. Der Grund: Die Tools müssten erst für das neue Projektformat von .NET Core angepasst werden. Microsoft hat aber einen Weg gefunden, doppelte Arbeit zu vermeiden. Statt diese Tools für .NET Core 1.0 anzupassen, werden in MSBUILD jetzt Features integriert, die die Vorteile von project.json ausmachen. Als Ergebnis davon werden wir ein neues Projektformat haben, das sowohl von .NET Core als auch .NET-Framework-Applikationen genutzt wird – und zwar auch auf anderen Plattformen, unabhängig von Visual Studio. Das einzige Problem dabei ist, dass es noch etwas dauert, bis das neue Projekt-File da ist.

Für den aktuellen Einsatz von .NET Core 1.0 soll das aber kein Hindernis sein. Sobald das neue Projekt-File verfügbar ist, werden bestehende Projekt-Files automatisch konvertiert – zumindest ist das der Plan. Diese Änderungen beeinflussen den C# Source Code aber keineswegs, und damit sehe ich das gar nicht so problematisch.

Eine weitere Schwäche kann die Problematik beim Portieren älterer Applikationen auf .NET Core sein. Es ist zwar von Vorteil, dass alte Zöpfe abgeschnitten wurden. Allerdings bringt das aber auch gleich den Nachteil mit sich, dass es schwierig ist, bestimmte Applikationen nach .NET Core zu portieren. Einige APIs gibt es einfach nicht in der neuen Welt. Es fehlen z.B. Application Domains, der alte, mächtige Syntax für Reflection und einiges mehr. Ein Hilfsmittel, um herauszufinden, wie weit eigene Applikationen davon betroffen sind, ist die Visual Studio Extension „.NET Portability Analyzer“. Dieses Tool zeigt die betroffenen genutzten APIs und manchmal auch Alternativen dafür.

Zukünftige Versionen von .NET Core werden dann den Umstieg von Legacy Applikationen zu .NET Core erleichtern. Application Domains, Reflection und mehr werden in zukünftigen .NET Core Versionen auch zur Verfügung stehen.

Aus der eigenen Projekterfahrung gesprochen: Wer sollte jetzt schon umsteigen?

Es gibt jetzt einige Szenarien, die früher mit .NET einfach nicht möglich waren. Da ist es einfach, sich schon jetzt für .NET Core zu entscheiden, z.B. um .NET-Applikationen auf anderen Plattformen zu ermöglichen.

Entity Framework Core ist ebenfalls auf anderen Plattformen verfügbar, bietet aber außerdem noch die Möglichkeit, NoSQL Provider zu verwenden. NoSQL Provider sind aber momentan nur in Samples verfügbar – da sollte es nicht lange dauern, bis verschiedenste Provider zur Verfügung stehen. Mit der neuen Architektur von Entity Framework ist es auch leichter Provider zu schreiben.

Bei neuen Webapplikationen sehe ich auch kaum Gründe, ASP.NET MVC 6 nicht zu verwenden. Es gibt viele Ähnlichkeiten mit ASP.NET MVC 5. Und als ASP.NET MVC Entwickler findet man sich rasch zurecht. Im Hintergrund ist aber sehr viel anders.

Bei bestehenden Applikationen macht es meist noch keinen Sinn, diese gleich auf .NET Core umzustellen. Bestehende Applikationen können aber trotzdem schon Features von .NET Core nutzen, die einige Vorteile bieten, wie z.B. das neue Dependency Injection Framework, das neue Configuration API oder auch die Logging Facade. Diese NuGet Packages können auch mit „Legacy Applications“ genutzt werden.

 

 

The post .Net Core 1.0 ist eine neue Welt! – Schnellere Innovationen, Komplexität, Cross-Platform und Open Source appeared first on BASTA!.

]]>
SQL Server 2016 Release https://basta.net/blog/sql-server-2016-release/ Thu, 02 Jun 2016 06:30:16 +0000 https://basta.net/?p=11908 Endlich ist es soweit: seit dem 01. Juni ist SQL Server 2016 verfügbar. 26 Monate sind seit dem Release der Vorgängerversion ins Land gegangen und die Entwickler haben sich Zeit gelassen. Aber es hat sich gelohnt – und das an vielen Stellen.

The post SQL Server 2016 Release appeared first on BASTA!.

]]>
Autor: Thorsten Kansy

Besonders das Thema Sicherheit wurde im SQL Server 2016 weiter nach vorne gebracht und Entwicklern wurden massiv Argument dafür entzogen, unsichere Anwendungen zu entwickeln. Das sollte sich in zukünftigen Anwendungen positiv bemerkbar machen. Die Kerntechnologien hierfür sind Row Level Security, Dynamic Data Masking und Always Encrypted.

Auch der Schritt in Richtung Hybrid Cloud, in der sich Daten teilweise lokal auf dem Server und in der SQL Azure Cloud befinden, ist ein interessanter Ansatz. So können z.B. historische Daten in die theoretisch unbegrenzte Cloud abgelegt werden und stehen jeder Zeit für Abfragen zur Verfügung ­– Cold Storage gehört damit der Vergangenheit an (Abb. 1).

SQL Server 2016 Editions

SQL Server 2016 Editions

Abb. 1: Neuerungen SQL Server 2016 [1] Screenshot (c) Microsoft

SQL Server 2016 Release

Ob sich andere Themen wie z.B. Polybase oder „R“ für SQL Server durchsetzen werden, muss sich in der Zukunft erst zeigen. Diese Technologien machen jedoch eins klar: spätestens mit SQL Server 2016 ist die Zeit der „Insellösung SQL Server“ definitiv beendet. Ein Punkt, der noch einmal durch die Ankündigung von Microsoft unterstrichen wird, dass SQL Server 2016 die erste Version sein wird, die auf Linux läuft [2]. Genau wie in anderen Bereichen (z.B. der UI-Technologie) zeigt dies ganz deutlich, dass auch Giganten der Branche wie Microsoft sich nicht nur mit sich selbst und eigenen Entwicklungen beschäftigen können.

Damit verschwimmen klare Grenzen, die Jahre lang unberührt Gültigkeit hatten. Ob das gut ist? Ich denke schon, denn so hat die beste Technik mehr Chancen, sich durchzusetzen, auch wenn sie vielleicht gerade nicht von großen Herstellern stammt. Das Microsoft diese Öffnung vielleicht nicht ganz freiwillig und nur aus Güte heraus gemacht hat, kann nur vermutet werden.

Erweiterungen in allen Teilen des SQL Server 2016

Auch wenn es oft übersehen wird, besteht der SQL Server nicht nur aus der relationalen Datenbank. Die Reporting Services (SSRS), Integration Services (SSIS) und Analysis Services (SSAS) gehören zu dem Produkt „SQL Server“ und erfahren mit der neuen Version natürlich auch ein Update.

Während diese bei SSIS und SSAS jedoch eher übersichtlich ausfallen und nur in Detailbereichen zu finden sind, wurden die Reporting Services erweitert und für Mobile-Devices fit gemacht (schließlich hat Microsoft Anfang 2015 die Firma Datazen gekauft). Das ist ein interessanter Schritt der viele Möglichkeiten eröffnet und die Plattform stärken wird. Es bleiben allerdings auch alte Schwächen, wie fehlendes Templating und Corporate Identity Unterstützung.

SQL Server 2016 - Mobile Report

SQL Server 2016 – Mobile Report

Abb. 2: Mobile Report Publisher © Microsoft [3]

Insgesamt ist SQL Server 2016 ein weiterer Schritt in eine Richtung, die uns Entwicklern viele neue, spannende Möglichkeiten eröffnet und mehr Freiheiten bieten wird – und neue komplexe Aufgaben.

Und noch eine gute Nachricht für alle Entwickler und deren Geldbeutel: die Developer Edition ist nun kostenfrei verfügbar [4] und das für SQL Server 2014 und 2016.

 

Links & Literatur

[1] http://bit.ly/25w6GXx

[2] https://www.microsoft.com/en-us/server-cloud/sql-server-on-linux.aspx

[3] https://msdn.microsoft.com/en-us/library/ms170438.aspx

[4] https://blogs.technet.microsoft.com/dataplatforminsider/2016/03/31/microsoft-sql-server-developer-edition-is-now-free/?wt.mc_id=WW_CE_DM_OO_BLOG_NONE

[5] Mehr vom Autor unter www.dotnetconsulting.de

Aufmacherbild: © istock ryccio S&S Media

The post SQL Server 2016 Release appeared first on BASTA!.

]]>
Offlinefähige Browseranwendungen mit Service Worker https://basta.net/blog/offlinefaehige-browseranwendungen-mit-service-worker/ Fri, 06 May 2016 12:08:17 +0000 https://basta.net/?p=11422 Die bereits von einigen Browsern unterstützten Service Worker erlauben das Abfangen von HTTP-Anfragen. Auf diese Weise lassen sich verschiedene Caching-Strategien für offlinefähige Browseranwendungen umsetzen.

The post Offlinefähige Browseranwendungen mit Service Worker appeared first on BASTA!.

]]>
Schon seit längerer Zeit unterstützen alle gängigen Browser Offlineszenarien. Beispielsweise können Webanwendungen über ein so genanntes Cachemanifest alle für den Offlinebetrieb benötigten Dateien angeben. Der Browser lädt diese beim ersten Besuch der Anwendung herunter und hinterlegt sie im so genannten App-Cache.

Leider hat die Anwendung selbst nur wenig Kontrolle über die Nutzung dieses Cache. Beispielsweise lädt der Browser Dateien bevorzugt aus diesem Cache, auch wenn online neuere Versionen davon zur Verfügung stehen. Daneben aktualisiert er den App-Cache nur, wenn er eine Änderung am Cachemanifest entdeckt. Ein gezieltes Aktualisieren einzelner Cacheeinträge ist hingegen nicht möglich.

Service Workers bieten hierfür eine bessere Lösung: Sie haben feingranularen Zugriff auf mehrere frei definierbare Cacheregionen und können Dateien nach eigenem Gutdünken darin verwalten. Darüber hinaus können sie HTTP-Anfragen abfangen und dynamisch entscheiden, ob diese über einen Netzwerkzugriff oder aus dem Cache zu beantworten sind. Auf diese Weise lassen sich unterschiedliche Caching-Strategien implementieren. Das Offline Cookbook von Google [1] geht darauf umfangreich ein. Einen Überblick dazu bietet Tabelle 1. Als der vorliegende Text verfasst wurde, unterstützten Chrome, Firefox und Opera dieses aufstrebende Konzept. Seitens der Teams hinter Safari und Edge gab es Interessensbekundungen.

 

Strategie Beschreibung
Cache-only Ressourcen werden ausschließlich aus dem Cache geladen.
Network-only Ressourcen werden ausschließlich über das Netzwerk geladen.
Cache, falling back to network Cache wird bevorzugt. Falls die gewünschte Ressource dort nicht zu finden ist, wird über das Netzwerk geladen.
Cache and network race Ressource wird parallel aus dem Cache und übers Netzwerk angefordert. Die zuerst erhaltene Antwort kommt zum Einsatz.
Network falling back to cache Netzwerk wird bevorzugt. Falls die gewünschte Ressource nicht übers Netzwerk geladen werden kann, kommt der Cache zum Einsatz.
Cache then network Ressource wird aus dem Cache geladen und verwendet. Parallel dazu wird eine Netzwerkanfrage gestartet. Sobald die darauf folgende Antwort vorliegt, kommt diese zum Einsatz. Darüber hinaus wird damit der Cache aktualisiert.
Generic fallback Wenn die gewünschte Ressource nicht im Cache und/oder nicht übers Netzwerk gefunden wurde, kommt eine generische Antwort zum Einsatz. Dabei kann es sich um eine benutzerfreundliche Fehlermeldung handeln.

Tabelle 1: Ausgewählte Caching-Szenarien

 

 

Service Worker

Ein Service Worker ist zunächst lediglich ein Skript, das der Browser im Hintergrund ausführt und sich für bestimmte Events registriert. Sein Wirkungsbereich erstreckt sich über den Ordner, in dem sich die Skriptdatei befindet, und bezieht auch sämtliche direkte sowie indirekte Unterordner ein. Um einen Service Worker die Kontrolle über eine gesamte Webanwendung zu geben, ist er somit in deren Root zu platzieren.

Mit der Service Worker Toolbox [2] stellt Google eine solche Bibliothek mit einem High-Level-API zur Verfügung. Das Herzstück ist dabei ein Routing-Konzept zum Abfangen von Anfragen. Eine Route definiert sich dabei durch ein URL-Muster, ein HTTP-Verb und einen Handler. Das URL-Muster ist im Wesentlichen ein URL mit Platzhalter. Greift die Anwendung mit dem definierten Verb auf ein zum Muster passenden URL zu, bringt das Service Worker Toolkit den registrierten Handler zur Ausführung. Dieser kann unter Nutzung einer festgelegten Caching-Strategie auf die Anfrage reagieren. Für die meisten der in Tabelle 1 beschriebenen Caching-Strategien bietet das Toolkit auch schon eine fertige Implementierung, sodass sich der Entwickler mit der Umsetzung dieser wohlbekannten Verfahren nicht belasten muss.

Dank des Routings kann eine Anwendung auf einfache Weise Anfragen für unterschiedliche Bereiche mit unterschiedlichen Caching-Strategien begegnen. Somit kann sie zum Beispiel Zugriffe auf statische Dateien anders handhaben als Zugriffe auf Web-APIs. Während sich womöglich im ersten Fall die Strategie Cache, falling back to network anbietet, könnte im zweiten Fall das Gegenstück Network, falling back to cache die bessere Option sein.

Nutzung der Service Worker Toolbox

Ein Beispiel für einen Service Worker, der auf der Toolbox basiert, findet sich in Listing 1. Die gesamten Quellcodedateien stellt der Autor unter [3] zur Verfügung. Das Beispiel importiert zunächst die Bibliothek sw-toolbox.js und versetzt daraufhin die Toolbox in den Debug-Modus. Dies führt zu einer Protokollierung sämtlicher durchgeführter Schritte in der Entwicklerkonsole und ist gerade für das Testen empfehlenswert. Anschließend weist das Beispiel die Toolbox an, im Zuge der Installation des Service Workers einige Dateien in den Cache zu laden. Caches von älteren Versionen desselben Service Workers entfernt es bei der darauffolgenden Aktivierung automatisch.

Danach erfolgt das Einrichten der Routen: Die Methode toolbox.router.get definiert eine Route für GET-Aufrufe, die an einem beliebigen URL der Herkunft http://www.angular.at gerichtet sind. Dabei handelt es sich um das vom Beispiel genutzte Web-API. Als Handler kommt hierfür die Methode toolbox.networkFirst, die die Strategie Network, falling back to cache implementiert.

Zusätzlich hinterlegt das Beispiel in der Eigenschaft toolbox.router.default einen Standard-Handler. Dieser kommt per Definition zur Ausführung, wenn keine der definierten Routen zu einer GET-Anfrage passt. Im betrachteten Fall handelt es sich dabei um die Funktion toolbox.cacheFirst, welche die Strategie Cache, falling back to network implementiert und sämtliche aus dem Netzwerk bezogenen Dateien für weitere Zugriffe im Cache hinterlegt.

Damit der Browser den Service Worker so schnell wie möglich aktiviert, legt das Beispiel am Ende noch Handler für die Ereignisse install und activate fest.

 

importScripts(‘/node_modules/sw-toolbox/sw-toolbox.js’);

toolbox.options.debug = true;

toolbox.precache([

‘/’,

‘/app/flugsuchen.component.css’,

‘/app/flugsuchen.component.html’,

‘/app/flugsuchen.component.js’,

‘/app/navbar.component.html’,

‘/app/navbar.component.js’,

‘/app/main.js’

]);

toolbox.router.get(‘/(.*)’, toolbox.networkFirst,
{origin: ‘http://www.angular.at’});

toolbox.router.default = toolbox.cacheFirst;

var context: any = self;

context.addEventListener(‘install’,
event => event.waitUntil(context.skipWaiting()));

context.addEventListener(‘activate’,
event => event.waitUntil(context.clients.claim()));

 

Handler beim Routing durch swtoolkit

Bei den von den Routen genutzten Handlern handelt es sich um Funktionen, die als ersten Parameter die jeweilige HTTP-Anfrage entgegennehmen und eine dazu passende HTTP-Antwort retournieren. Neben der Anfrage nehmen sie auch noch ein Objekt mit URL-Parametern sowie ein Options-Objekt entgegen. Bei Letzterem handelt es sich um das beim Einrichten der Route hinterlegte Options-Objekt, das unter anderem die Origin festlegt.

Das nachfolgende Beispiel demonstriert dies. Es implementiert die Caching-Strategie Generic Fallback. Dazu definiert es einen URL mit einem Platzhalter imgname. Der zur Laufzeit dafür eingesetzte Wert erscheint innerhalb der gleichnamigen Eigenschaft im an den Handler übergebenen zweiten Parameter mit dem Namen values. Der Handler versucht, die Anfrage mit der Funktion cacheFirst zu beantworten. Funktioniert dies nicht und befindet sich die Anwendung im Offlinebetrieb, leitet der Handler auf die sich im Cache befindliche Datei notfound.png um.

 

declare var Request: any;

toolbox.router.get(‘/img/:imgname’, (request, values, options) => {

return toolbox.cacheFirst(request)

.catch(() => toolbox.cacheOnly(
new Request(“/img/notfound.png”)));

});

 

Neben den hier betrachteten Methoden bietet das Toolkit noch einige weitere. Beispielsweise wartet es mit zusätzlichen Cachestrategien, aber auch mit Methoden für weitere HTTP-Verben auf. Es kann auch Cacheeinträge nach einer bestimmten Zeitspanne oder beim Erreichen eines Quotas entfernen. Informationen dazu bietet die Dokumentation im GitHub-Repository der Toolbox [2].

Auch zum Registrieren von Service Worker bietet die Toolbox mit dem Skript companion.js eine einfache Möglichkeit:

 

<script src=”node_modules/sw-toolbox/companion.js”
data-service-worker=”sw-with-toolbox.js”></script>

Die Datei companion.js über einen Skript-Tag zu laden und das gewünschte Service-Worker-Skript ist lediglich über das Attribut data-service-worker desselben Skript-Tags zu referenzieren.

 

[1] https://jakearchibald.com/2014/offline-cookbook/#generic-fallback

[2] https://github.com/GoogleChrome/sw-toolbox

[3] https://github.com/manfredsteyer/progressive-with-sw-toolbox-demo

 

Manfred Steyer (www.softwarearchitekt.at) ist Trainer und Berater mit Fokus auf Webtechnologien und Services. In seinem aktuellen Buch „AngularJS: Moderne Webanwendungen und Single Page Applications mit JavaScript“ behandelt er die vielen Seiten des populären JavaScript-Frameworks aus der Feder von Google.

 

The post Offlinefähige Browseranwendungen mit Service Worker appeared first on BASTA!.

]]>
Eine BASTA! für offene Innovationen und neue Wege https://basta.net/blog/eine-basta-fuer-offene-innovationen-und-neue-wege/ Wed, 27 Apr 2016 08:05:41 +0000 https://basta.net/?p=11184 Liebe Teilnehmer und Sprecher der BASTA! 2016,

eines ist seit der Einführung von Windows 8 bzw. 10 ganz sicher: Zu jeder BASTA! sieht die Microsoft-Welt anders aus. Und damit verändert sich auch das Gesicht der BASTA! Hier greifen wir die Trends und Technologien auf und verbinden sie in den Sessions und Workshops der BASTA! 2016 zu einem Angebot, das nie zuvor so breit und umfassend war.

The post Eine BASTA! für offene Innovationen und neue Wege appeared first on BASTA!.

]]>

 

Die radikale Transformation von Microsoft und seiner Produkte unter dem Motto „Mobile first, Cloud first“ hat nicht nur Auswirkungen auf Microsoft selbst, sondern auf die IT-Industrie im Allgemeinen und auf Sie, die Teilnehmer und Sprecher der BASTA!, im Besonderen.

Aufbruchsstimmung in der Microsoft-Tech-Community

Offenheit und gutes Zuhören zeichnen das neue Microsoft in der Ära von Satya Nadella aus, der in den ersten zwei Jahren als CEO viele neue Entwicklungen angestoßen hat. Was diese Offenheit, und mit ihr auch die Einladung zur Innovation bedeutet, erfahren sie kompakt und fokussiert auf der BASTA!. Als .NET-Entwickler haben Sie auf Basis ihres Know-hows völlig neue Möglichkeiten, ihre Produkte zu entwickeln und auf den Markt zu bringen. Und wir bieten Ihnen mit der BASTA! die Gelegenheit, diese Möglichkeiten auszuloten. Dazu werden wir erstmals die BASTA! Labs veranstalten.Dazu bekommen Sie in herausragenden Keynotes einen gezielten Ausblick auf die kommenden Trends und Technologien.Und wir nutzen Microsofts neue Offenheit dazu, Ihnen Technologien vorzustellen, die bisher im Microsoft-Umfeld nicht üblich waren, die Sie aber kennen sollten.

Eine Webkonferenz in der Konferenz

Viele der aktuellen Themen lassen sich aus unterschiedlichen Blickwinkeln betrachten. Wir haben dazu für Sie Pakete, die einen strategischen Aspekt bilden. Dazu zählt offensichtlich der Web Development Day am Mittwoch, mit seinen Sessions zu ASP.NET 1.0, Angular 2 und JavaScript. Hinzu kommen aber weitere Session wie TypeScript für .NET-Entwickler, HTML5-Anwendungen, Cross-Plattform-Entwicklung vom Backend bis in den Browser oder die Universal-App. In der Summe ergibt das quasi eine Webentwicklungskonferenz.

Hinzu kommen aber weitere Sessions wie TypeScript für .NET-Entwickler, HTML5-Anwendungen, Cross-Plattform-Entwicklung vom Backend bis in den Browser oder die Universal-App.

Eine Cross-Plattform- und Cross-Device-Konferenz

Aus einem anderen Blickwinkel betrachte ist die BASTA! aber auch eine Cross-Plattform- und Cross-Device-Konferenz. Denn die gleichen Themen bieten in Kombination mit anderen Special Days einen umfassenden Einblick in diesen Bereich. Dazu gehören der Cross-Plattform Day, der Modern Business Application Day und der Universal Apps Day. Hier bekommen Sie zusammengefasst praxisorientierte und zukunftsweisende Tipps zur Entwicklung mit Cordova, Xamarin oder JavaScript-Frameworks wie React, NativeScript und das schon erwähnte Angular 2. Ob Sie Web-, Hybrid- oder Native-Apps erstellen wollen, hier erfahren Sie wie es geht. Vertieft werden die Informationen dazu in den entsprechenden Workshops am Montag und Freitag: C#-Revolution mit .NET Core 1, Strategien für .NET-Entwickler und Architekten, End-to-End-App-Entwicklung mit Entity Framework, ASP.NET Web API, HTML5, Angular 2 oder Xamarin. Besonders spannend ist hier der Xamarin-Workshop von Jörg Neumann, da Microsoft Xamarin in diesem Frühjahr übernommen und umgehend in Visual Studio integriert hat. Damit steht Ihnen Xamarin nun kostenlos zur Verfügung, und Sie können mit ihrem C#-, F#- und .NET-Wissen Anwendungen entwickeln, die dann nativ auf der Windows-Universal-Plattform laufen. Das bedeutet, dass Sie mit Ihren Anwendungen nicht nur auf Windows 10 Desktop, Tablet und Phone oder auch der Xbox, dem Surface HUB und der HoloLens vertreten sind. Darüber hinaus können Ihre Kunden auch über iPhones und Android-Geräte Ihre Anwendungen nutzten.

C# – ein Klassiker

Wie Sie für diese breite Entwicklung C# und .NET nutzen können, erfahren Sie auf der BASTA! selbstverständlich auch. Mit C#, dem Klassiker, der nicht älter wird, sind Sie immer vorne mit dabei. Die C# Days mit Oliver Sturm liefern hierzu den Input, was die Features von C# 6 angeht, aber auch was die Open-Source-Politik von Microsoft für Sie bedeutet. Mit der Compiler-Plattform „Roslyn“ haben Sie ein mächtiges Werkzeug zur Hand, dass Sie für Ihren Einsatz optimieren können und die Sie gleichzeitig dabei unterstützt, Ihren Code zu optimieren. Und hier kommen natürlich die neuen Features des modularen .NET Core 1.0 zum Einsatz, die Rainer Stropek in seinem Workshop am Montag umfassend vorstellt. Mit .NET Core, das Ende 2016 vollständig kommen soll, halten auch Neuerungen in die NuGet-Paketverwaltung Einzug. Für Entwickler bedeutet das, dass ihre Anwendungen nicht mehr mit dem großen monolithischen 4.x.x-Framework ausgeliefert werden müssen, sondern nur noch Komponenten genutzt werden, die die App wirklich benötigt. Zudem bietet dies die Möglichkeit, Anwendungen auch für Linux und Mac OS zu schreiben – und das als .NET-Entwickler. Auch hier erweitern sich Ihre Möglichkeiten, und die Themenkombination C# und Linux ist plötzlich auf der BASTA! vertreten. Vor zwei Jahren noch unvorstellbar.

Für Entwickler bedeutet das, dass ihre Anwendungen nicht mehr mit dem großen monolithischen 4.x.x-Framework ausgeliefert werden müssen, sondern nur noch Komponenten genutzt werden, die die App wirklich benötigt.

DevOps, Agile und alles im Flow

Damit alle diese Technologien und Trends zusammenspielen, ist das Application Lifecycle Management (ALM) besonders wichtig. Das unterstreicht die aktuelle Entwicklung des Team Foundation Servers (TFS) 2015. Steht der TFS seit jeher im Zentrum eines Entwicklungsprojekts und bildet damit die Plattform für die am Projekt beteiligten Teammitglieder und unterschiedlichste Tools, wuchs und wächst der Umfang der Einsatzmöglichkeiten mit den neuen Anforderungen an die Softwareentwicklung. Heute steht der TFS On-Premise und online zur Verfügung, verbindet die lokalen Ressourcen mit Azure, verteilt über Git, bindet unterschiedliche Departments von der Anforderung über die Entwicklung, das Testing, das Deployment und das Reporting ein und schafft somit überhaupt erst eine flexible und effiziente Grundlage für den umfassenden Lebenszyklus einer Anwendung: von ihrer Idee bis zu ihrer Auslieferung und nachfolgenden Verbesserung. Damit wird der TFS zum mächtigen Werkzeug für .NET-Entwickler, die zeitgemäß moderne Anwendungen auf allen Plattformen für alle Plattformen entwickeln wollen.

Hier kommen endlich auch Development und Operations, also DevOps, zusammen – weil sie gute Gründe dafür haben. Denn in dem Bemühen, gute Software auszuliefern, steht zwischen der Entwicklung und dem Nutzer nun einmal der Betrieb und soll nicht zur Hürde werden. An dieser Stelle ist DevOps ganz einfach ALM zu Ende gedacht. Zum Lifecycle einer Software gehört auch das Ausliefern an den Kunden, und wenn das nicht so funktioniert, wie zuvor auf dem Testsystem der Entwickler, dann müssen die Kollegen vom Betrieb ran. Der Betrieb hätte wahrscheinlich auch gleich sagen können, dass es nicht funktionieren wird, aber Entwicklung und Betrieb haben gar nicht darüber gesprochen.

Der Agile Day, der Test und Quality Day und die ALM DevOps Days bieten einen 360-Grad-Blick auf die moderne, nutzerzentrierte Anwendungsentwicklung für .NET-Entwickler. Microsoft und andere Hersteller bieten dazu viele Tools an, aber diese müssen methodisch und organisatorisch sinnvoll eingesetzt werden. Hier werden die einzelnen Aspekte zusammengeführt und zu einer Entwicklungserfahrung, einem Produkt und einem strategischen Ziel – dem zufriedenen Kunden – zusammengeführt.

Spannende Planung, spannende Konferenz

Uns macht die Planung einer Konferenz mit dieser Bandbreite ungeheuer Spaß. Vor allem die Chance, neue Perspektiven zu öffnen und neue Themen für innovative Lösungen vorzustellen, ist wunderbar. Wir sind sicher, dass Sie als Teilenehmer für sich, Ihre Kollegen und Unternehmen hier das Wissen bekommen, dass Sie für die digitale Transformation brauchen, denn die steckt hinter allen diesen einzelnen Aspekten. Wir setzen mit der Konferenz fort, was wir im Windows Developer und auf www.entwickler.de schon seit Jahren an strategischer Analyse und Einordnung immer wieder durchspielen und auf die Probe stellen. Softwareentwicklung hat einen entscheidenden und wachsenden Anteil am Geschäftserfolg von Unternehmen, und Sie haben das Know-how dafür – in der Microsoft-Welt und darüber hinaus.

 

Ich freue mich darauf, Sie auf der BASTA! zu treffen!

Mirko Schrempp, Program Chair der BASTA! und Redakteur des Windows Developer

 

The post Eine BASTA! für offene Innovationen und neue Wege appeared first on BASTA!.

]]>