65
bastacon Jubiläums-Dossier 2017 60 Seiten gebündeltes Wissen zu Mobile-/App-Trends, -Development, -Business & -Design www.basta.net

Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

bastacon

Jubiläums-Dossier 201760 Seiten gebündeltes Wissen zu

Mobile-/App-Trends, -Development, -Business & -Design

www.basta.net

Page 2: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Inhalt.NET Framework & C#Tolle Typen – Wie statisch muss Typisierung sein? 3Kolumne: Olis bunte Welt der ITvon Oliver Sturm

Agile & DevOpsViewModels test getrieben entwickeln 7Test-driven Development (TDD) in MVVM-Appsvon Thomas Claudius Huber

VSTS/TFS – ganz nach meinem Geschmack 16Verschiedene Erweiterungs- und Anpassungsmöglichkeitenvon Marc Müller

Web DevelopmentEchtzeitchat mit Node.js und Socket.IO 22Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Mit Azure zur IoT-Infrastruktur 35Vom Gerät zum Live-Dashboardl von Thomas Claudius Huber

Data Access & StorageEntity Framework Core 1.0 42Was bringt der Neustart? von Manfred Steyer

HTML5 & JavaScriptDie Alternative für JavaScript-Hasser 48TypeScript = JavaScript + x von Dr. Holger Schwichtenberg

Asynchrones TypeScript 58TypeScript lernt async/awaitvon Rainer Stropek

User InterfaceDas GeBOT der Stunde? 64Conversational UIs: Ein erster Blick auf das Microsoft Bot Framework von Roman Schacherl und Daniel Sklenitzka

Page 3: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 3

DOSSIER .NET Framework & C#

von Oliver Sturm

Im Mainstream der Softwareentwicklung, besonders im großen Bereich der Businesssoftware, ist man in Hin-sicht auf Entenprobleme gern anderer Meinung. Sta-tisch typisierte Sprachen haben in diesem Bereich lange dominiert, und diese Sprachen mögen eine Ente nur, wenn sie klar definiert ist. Gefieder- und Schnabelfarbe, Gewicht und Schwanzlänge müssen sorgsam in einem Objektmodell abgelegt und notwendige Schnittstellen wie ISchwimmfähigesTier definiert und implementiert werden. Irgendwann darf dann auch gequakt und ge-schwommen werden, aber nur unter penibler Kontrolle der Compiler- und Laufzeitsysteme.

Statische Typisierung – eine EvolutionViele Programmierer sind der Auffassung, dass diese statische Typisierung sich aus einem evolutionären Vorgang entwickelt hat und letztlich grundlegend „besser“ ist als eine offenere Anschauung der Enten-problematik. In einer bestimmten Gruppe von Spra-chen ist diese Ansicht durchaus nachvollziehbar. In Sprachen wie C und später Pascal gab es zunächst nur eine trügerische Art von Typsicherheit: Es gab zwar Typen, aber es war sehr einfach, dem Compiler sei-nen Irrtum in Hinsicht auf eine bestimmte Typisierung mit einem simplen Cast verständlich zu machen. Alles basierte auf Zeigern, und letztlich zählte das Verständ-nis des Programmierers mehr als das des Compilers.

Kolumne: Olis bunte Welt der IT

Tolle Typen – Wie statisch muss Typisierung sein?Wenn es quakt wie eine Ente und schwimmt wie eine Ente, dann ist es eine Ente. Sie haben diesen Spruch schon einmal gehört – er wird von Verfechtern der dyna-mischen Programmierung seit Jahren immer wieder vorgetragen. Die Ente ist sinn-bildlich ein Objekt, ein Element, mit dem anderer Code arbeitet, und an das dieser Code Erwartungen hat: quaken soll sie können, und schwimmen, und wenn das gegeben ist, dann ist alles in bester Ordnung.

www.basta.net

Page 4: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 4

DOSSIER .NET Framework & C#

Datentypen wie die Unions in C und die varianten Re-cords in Pascal machten sich dies schamlos zunutze, und große Speichereffizienz und Performance konnte auf dieser Basis erzielt werden – wenn auch mit be-trächtlichem Risiko.

In strikt objektorientierten Sprachen, zunächst in erster Linie Java, wurde Typen ein ganz neuer Stellen-wert eingeräumt. Der Cast wurde vom Befehl zur An-frage degradiert, und ohne Zustimmung von Compiler und Runtime konnte der Programmierer nicht mehr beliebig mit Typen umgehen. Drei Schritte wurden hier gemacht, um Typsicherheit zu erzwingen. Erstens musste der Programmierer nun sorgsam spezifische Ty-pen definieren, und seinen Code so strukturieren, dass Typkonversionen außerhalb des Systems nicht mehr notwendig waren. Zweitens, und darauf aufbauend, konnten Compiler und Runtime die korrekte Verwen-dung dieser Typen erzwingen und so die gewünschte Si-cherheit herbeiführen. Drittens war es notwendig, das Verständnis des Programmierers immer wieder durch die klare Nennung der Typbezeichnungen im Code zu versichern – eigentlich ein erheblicher Aufwand, aber im Vergleich zu komplexen Zeigern in C oder C++ sa-hen die Typnamen auf den ersten Blick so übersichtlich aus, dass man damit zunächst ziemlich glücklich war.

In der .NET-Welt wurde die Idee der strikten Typi-sierung ebenfalls umgesetzt und tief in den Sprachen der Plattform verankert. C# unterstützt aus Kompa-tibilitätsgründen mit der Windows-Plattform auch Strukturen, die ein bestimmtes statisches Speicher-mapping anwenden. Mit dem var-Schlüsselwort in C# versuchte man, den Aufwand etwas zu mindern, der dem Programmierer durch die wiederholte Verwen-dung der Typnamen entsteht. Typherleitung ist in C# allerdings sehr rudimentär, und jeder verwendete Typ muss zumindest einmal beim Namen genannt werden. Mit einem anderen Schlüsselwort, dynamic, sollte eine Anbindung an dynamische Welten vereinfacht werden. Das machte C# zu einer relativ flexiblen statisch typi-sierten Sprache, schaffte aber auch Verwirrung bei An-wendern, die von der Microsoft-Welt klare Strategien erwarteten und gewohnt waren.

Typen ohne AufwandWie wäre es, wenn der Compiler mit Typen arbeiten und deren korrekte Verwendung garantieren könnte, ohne dass dem Programmierer dadurch syntaktischer Aufwand entsteht? Mithilfe extrem leistungsfähiger Typherleitung ist dies tatsächlich möglich, wie etwa das Typsystem der Sprache Haskell beweist. Wertzu-weisungen müssen in Haskell nur in Konfliktfällen mit Typannotationen versehen werden, sodass der Code im Allgemeinen ganz ohne die Erwähnung expliziter Typen auskommt – der Compiler leitet die Typen her und prüft sie, aber der Programmierer wird damit nicht belastet. Was Haskell allerdings zusätzlich besonders interessant macht, ist die Fähigkeit zur automatischen Verallgemeinerung. Dies funktioniert sogar auf der

Basis von implementiertem Verhalten: Der Compiler leitet etwa automatisch her, dass Typen, mit denen Ad-dition betrieben wird, zu einer gemeinsamen Typklasse gehören, die genau diese Fähigkeit verallgemeinert.

Auf der .NET-Plattform bietet F# sehr gute Unterstüt-zung für Typherleitung, die sich allerdings nicht in jedem Detail in den objektorientierten Bereich erstreckt und, mangels der entsprechenden Basis in der CLR, auch kei-ne Typklassen kennt. Somit bleibt generell die Tatsache bestehen, dass statische Sprachen Mehraufwand für den Programmierer erzeugen, da kontinuierlich sorgfältige Pflege der Typen und aller Anwendungspunkte betrie-ben werden muss. Natürlich soll damit nicht gesagt sein, dass dem kein positiver Wert gegenübersteht, aber die Arbeit zur Pflege von strikt typisiertem Code kann oft beträchtlich sein.

Dynamischer entwickelnTatsache ist, dass die Welt der Programmierung heut-zutage dynamischer ist denn je. Wenn vor einigen Jahren die Frage gestellt wurde, was für unmittelbare Vorteile die Verwendung dynamischer Sprachen vor-zuweisen habe, dann wurden als Antwort gewöhnlich bestimmte Anwendungsfälle beschrieben. Ich selbst habe immer gern ein Szenario einer Tabellenkalkulati-onssoftware beschrieben, die in Python implementiert wurde, und ein unglaublich einfach verwendbares API hatte, basierend auf der Tatsache, dass mittels dyna-mischer Mechanismen extrem einfache Syntax für Zelladressierung möglich war. MySheet.K5 liest sich eben besser als MySheet.Cells[„K5“], um ein simples Beispiel zu nennen. Auch im Bereich von Object-rela-tional Mapping gab es beeindruckende Beispiele von ähnlicher Natur. Heute hingegen ist Dynamik ein ele-mentarer Bestandteil von Applikationsarchitekturen und Entwicklungsmethoden, und ihr muss deshalb eine wesentlich größere Bedeutung beigemessen werden.

Line-of-Business-Apps mit der Univer-sal Windows Platform entwickelnThomas Claudius Huber

Zum Entwickeln von nativen Windows-An-wendungen stellt die Universal Windows Platform (UWP) nach der WPF die neuste Technologie dar. Doch inwiefern lässt sich

die UWP zum Entwickeln von Business-Apps einset-zen? Wie sieht es mit den typischen Anforderungen aus – Datenbindung und MVVM-Unterstützung, Vali-dierung etc.? In dieser Session erfahren Sie im Live-Coding, was die UWP heute zum Entwickeln einer klassischen Enterprise-App bietet und wohin die Rei-se geht.

Besuchen Sie auch folgende Session:

Page 5: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 5

DOSSIER .NET Framework & C#

Dynamische Sprachen im .NET-UmfeldIn verteilten Systemen kommt es immer wieder vor, dass APIs externalisiert werden, die in der Vergangen-heit elementarer Bestandteil des eigenen Systems waren. Das passiert zum Beispiel, wenn zur Datenablage oder zu anderen Zwecken Cloud-Dienste verwendet werden oder ähnliche Integrationen mit Diensten von Drittan-bietern stattfinden. REST-Zugriff und Datenübertra-gung im JSON-Format führen in solchen Fällen leicht zu sehr dynamischen APIs, die der Programmierer nicht selbst beeinflussen kann, mit denen der Programmcode aber möglichst flexibel arbeiten muss, um dauerhaft stabil zu bleiben.

Die Problematik ist bei der Nutzung von offensicht-lich externen Diensten nicht neu. Allerdings bringt die Idee der Microservices dieselben Umstände direkt ins eigene Haus. Modularisierung wird bei Anwendung dieses Schemas zur Disziplin, und eine große Anzahl eigenständiger Dienste wird unabhängig von meh-reren Teams gepflegt und sogar betrieben, um ein Anwendungssystem aufzubauen. Diese Dienste müs-sen sich natürlich austauschen, was gewöhnlich mit ähnlichen Mitteln geschieht wie bei der Einbindung externer Dienste. Daraus entsteht natürlich eine beein-druckende Konsistenz, aber auch die Notwendigkeit, dort dynamisch zu denken, wo es um die Schnittstel-len zwischen einzelnen Komponenten geht. Auch Sys-teme zur bidirektionalen Kommunikation oder zum Message-basierten Austausch von Informationen sind gewöhnlich in diesem Sinne dynamisch. Letztlich hal-ten dynamische Komponenten sogar im allgemeinen .NET-Umfeld Einzug, wie etwa in ASP.NET MVC, wo anonyme Objekte zur Konfiguration und zur Übertra-gung von Daten zwischen Views und Controllern sei-tens Microsoft favorisiert werden.

Die Historie dynamischer Sprachen ist ebenso lang und interessant wie die der statisch typisierten. Lisp, eine der ältesten Programmiersprachen, ist dynamisch und bildet die Basis vieler anderer Sprachen. Clojure ist eine Lisp-basierte Sprache, die im Java-Umfeld sehr beliebt ist und sich auch auf .NET verbreitet. In Ob-jective-C gibt es Message Passing in der Sprache selbst, wodurch Dynamik erreicht werden kann. Erlang ist dynamisch und wird immer wieder aufgrund seiner be-eindruckenden Fähigkeiten in der Parallelisierung als Beispiel herangezogen, und das Schema der Aktoren, das in Erlang von großer Bedeutung ist, kann mitt-lerweile auf allen wichtigen Plattformen angewandt werden. In Python wurden und werden große Anwen-dungssysteme entwickelt, und dann gibt es natürlich JavaScript, dessen Bedeutung heute kaum überschätzt werden kann.

Pflegeleichte TypsicherheitDer Vorteil dynamischer Sprachen in der beschriebenen dynamischen Umgebung besteht hauptsächlich darin, dass der Aufwand zur Pflege bestimmter Elemente einer Codebasis wesentlich niedriger ist als bei der Anwen-

dung statisch typisierter Sprachen. Wenn ein externes API geändert wird, kann der eigene Code womöglich ohne Änderungen weiter arbeiten, und wenn Änderun-gen notwendig werden, müssen sie an weniger Code-stellen durchgeführt werden.

Der Programmierer statisch typisierter Sprachen ver-lässt sich gern darauf, dass Compiler und Runtime einen Teil der Prüfungen durchführen, die sicherstellen, dass Programmcode in Hinsicht auf Datentypen korrekt ist. Daraus entsteht die größte Skepsis in Bezug auf dynami-sche Sprachen: Wie kann ein stabiles Ergebnis erzielt wer-den, wenn keine Typsicherheit besteht?

Auf diese Frage gibt es drei wesentliche Antwor-ten. Zunächst gibt es bei dynamischen APIs gewisse Patterns, die direkt der langfristigen Stabilität von Anwendungssystemen dienen. APIs können auf diese Weise versioniert werden, sodass Clients nicht sofort mit jeder Änderung kompatibel sein müssen. Natür-lich wollen solche parallelen Versionen eines API ge-pflegt werden, und Regeln etabliert, um gleichzeitig den Anwendern des API Sicherheit zu geben und den Pflegeaufwand überschaubar zu halten.

Zweitens verwenden auch dynamische Programmie-rer gern Softwarewerkzeuge, etwa zur statischen Code-analyse, deren Mechanismen oft ähnliche Resultate erzielen können wie ein Compiler einer statisch typi-sierten Sprache. In diesen Bereich fällt auch Microsofts TypeScript, das mithilfe einer eigenen Syntax und eines Compilers eine gewisse Typsicherheit für JavaScript herbeiführt.

Die dritte Antwort, und wohl die wichtigste, besteht aus der Erzeugung von automatisierten Tests. Aus Sta-tistiken geht hervor, dass die Anzahl von Tests in Pro-jekten auf Basis dynamischer Sprachen oft wesentlich größer ist als bei statisch typisierten. Im dynamischen

Enterprise-Apps mit Xamarin und den Azure App Services entwickeln

Jörg Neumann

Die Anforderungen an eine Business-App steigen stetig. Sie soll auf verschiedenen Plattformen laufen, auch von unterwegs Zu-

griff auf Unternehmensdaten bieten und natürlich off-linefähig sein. Für solche Aufgaben bieten die Azure App Services elegante Lösungen. Sie ermöglichen eine einfache Bereitstellung von Backend-Services, die Anbindung an unterschiedliche Datenquellen und eine Integration ins Unternehmensnetz. Zudem wer-den verschiedene Varianten der Authentifizierung und der Versand von Push-Benachrichtungen gebo-ten. Jörg Neumann zeigt Ihnen, wie Sie mit Xamarin und den Azure App Services Enterprise-taugliche Apps entwickeln und betreiben können.

Besuchen Sie auch folgende Session:

Page 6: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 6

DOSSIER .NET Framework & C#

und die Ideen der Dynamik akzeptieren. Sie haben die Wahl, welche Methoden zur Absicherung Sie einsetzen möchten, aber letztlich hilft vor allem eine Maßgabe, die in der Softwareentwicklung eine der ältesten ist: tes-ten, testen und nochmal testen!

Umfeld wird die Erzeugung von Tests gewissenhafter betrieben, und die Abdeckung bestimmter Szenarien ist größer, um Fehler aufgrund der falschen Verwendung von Typen auszuschließen.

Gerade dieser letzte Teil wird von manchen Pro-grammierern leider als Problem angesehen. Obwohl die Erzeugung einer möglichst vollständigen Testbasis seit Langem eine anerkannte Best Practice ist, hat die-se sich in vielen Projekten und Entwicklerteams bis-her nicht durchsetzen können. Oft wird als Grund der Aufwand genannt, der als zu hoch empfunden wird. Realistisch und korrekt ist dies meiner Erfahrung nach nicht. Selbst in bestehenden Projekten, die statische Ty-pisierung verwenden, lohnt sich die Einführung einer guten Testbasis gewöhnlich schnell, da die Stabilität des Codes steigt, Fehlerbehebung sich vereinfacht und Regression ausgeschlossen werden kann. In modernen Anwendungssystemen, die mit der zuvor beschriebe-nen Dynamik der umgebenden Welt umgehen müssen, steht dem Aufwand der Testerzeugung außerdem der Pflegeaufwand statischer Typsysteme gegenüber, der unter Umständen sehr erheblich sein kann.

FazitStatisch und dynamisch typisierte Sprachen haben Vor- und Nachteile, wie auch jede einzelne Sprache im Vergleich mit jeder anderen. In der heutigen Zeit können Sie deutlich Zeit gewinnen und konzeptionell leistungsfähigere Anwendungen bauen, wenn Sie sich die Fähigkeiten dynamischer Sprachen zunutze machen

TypeScript für .NET-EntwicklerChristian Wenz

Mit TypeScript macht sich Microsoft daran, das – für viele Entwickler aus dem eigenen Kosmos – ungewohnte JavaScript zugängli-

cher zu machen, indem beispielsweise statisches Ty-ping und bestimmte OOP-Features hinzugefügt wer-den. Nach einigen Anlaufschwierigkeiten hat TypeScript inzwischen auch außerhalb der Microsoft-Welt Traktion erhalten. Viel besser noch: Angular setzt auf TypeScript! Es ist also höchste Zeit, sich mit der Sprache zu beschäftigen. Diese Session stellt die Features von TypeScript vor und geht dabei auch auf die Toolunterstützung seitens Visual Studio und Co. ein. Damit sind Sie auch für die Entwicklung von Anwendungen auf Basis von Angular bestens ge-wappnet.

Besuchen Sie auch folgende Session:

Page 7: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 7

DOSSIER Agile & DevOps

von Thomas Claudius Huber

„Ich habe genug zu tun, da hab‘ ich doch nicht noch Zeit, um Unit-Tests zu schreiben“. Diese Aussage hört man nur allzu oft von Entwicklern, insbesondere von jenen, die noch nie in einem testgetriebenen Projekt mitgearbei-tet haben. Von Michael Feathers, Autor des erfolgreichen Buchs „Working with Legacy Code“, stammt folgendes Zitat: „Code ohne Tests ist schlechter Code“. Der Grund

HinweisIn diesem Artikel wird auf xUnit gesetzt, aber wenn Sie MSTest oder NUnit nutzen, ist das auch in Ord-nung. Wichtig ist allein die Tatsache, dass Sie über-haupt Unit-Tests schreiben, um wartbaren Code zu erhalten. Welches Unit-Testing-Framework Sie dafür einsetzen, ist zweitrangig.

Einer der zentralen Vorteile des Model-View-ViewModel-Patterns (MVVM) ist es, dass sich ViewModels automatisiert testen lassen. Das ViewModel ist unabhängig vom UI und enthält nur die UI-Logik; eine Tatsache, die es zu einem idealen Kandidaten für Unit-Tests macht. Doch um ein testbares ViewModel zu erhalten, muss ein Entwickler ein paar Punkte beachten, insbesondere beim Laden von Daten oder beim Anzeigen von Dialogen. Worauf es ankommt und wie ViewModels testgetrieben entwickelt wer-den, zeigt dieser Artikel. Dabei werden das Unit-Testing-Framework xUnit, die Mocking Library Moq und das Dependency-Injection-Framework Autofac eingesetzt.

Test-driven Development (TDD) in MVVM-Apps

ViewModels test-getrieben entwickeln

©S

&S

Med

ia, ©

iSto

ckp

ho

to.c

om

/dan

leap

Page 8: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Abb. 1: Test-driven Development

www.basta.net 8

DOSSIER Agile & DevOps

Unit-Test entweder rot oder grün sein muss, also keine manuellen Schritte enthalten darf. Muss der Entwickler beispielsweise nach dem Test manuell prüfen, ob eine bestimmte Datei erstellt wurde, dann ist es kein Unit-Test mehr, da er sich nicht selbst validiert.

So viel zu den Regeln. Sind die Unit-Tests für den eige-nen produktiven Code geschrieben, ergeben sich einige Vorteile:

• Es lassen sich Änderungen durchführen, ohne dassexistierende Logik zerstört wird.

• Gute Tests ergeben auch eine gute Dokumentation.• Das Schreiben von Unit-Tests kann das Design des

produktiven Codes positiv beeinflussen.• Das Schreiben von Unit-Tests erfordert, dass man

bereits vor der Implementierung stärker über dasProblem nachdenkt.

Doch läuft die Anwendung fehlerfrei und stabil, wenn alle Unit-Tests grün sind? Natürlich nicht, es braucht auch noch Integrationstests.

Unit-Test vs. IntegrationstestUnit-Tests allein sind kein Heilbringer. Viele grüne Unit-Tests bedeuten noch nicht, dass eine Anwendung danach keine Fehler mehr hat. Neben Unit-Tests, die nur ein Stück Code isoliert betrachten, gibt es viele andere Testarten. Eine sehr wichtige stellen die Inte-grationstests dar. Beim Integrationstest gibt es keine Mock-Objekte wie beim Unit-Test. Es wird beispiels-weise auf eine Datenbank zugegriffen, es wird der richtige Web Service aufgerufen etc. Das heißt, beim Integrationstest geht es darum, wie die einzelnen ge-testeten Einheiten (Units) dann tatsächlich zusammen-spielen.

Zum Schreiben von Integrationstests lässt sich auch ein Unit-Testing-Framework wie xUnit einsetzen. Dabei werden im Test dann eben keine Mock-Objekte, son-dern die richtigen Objekte verwendet – beispielsweise für den Datenbankzugriff.

ist folgender: Wenn es Tests gibt, kann ein Entwickler das Verhalten des Codes sehr schnell und verifizierbar ändern, da er nach dem Ändern des Codes die Unit-Tests laufen lässt, um zu sehen, ob er nicht aus Versehen beste-hende Logik zerstört hat. Ohne Tests kann der Entwick-ler nach dem Ändern des Codes niemals wissen, ob der Code jetzt besser oder schlechter ist.

In diesem Artikel wird nur „guter Code“ geschrieben. Nach einem Blick auf Unit-Tests, Test-driven Develop-ment [1] und das xUnit-Framework zeigt der Artikel, wie testbare ViewModels geschrieben werden und wie sie sich testen lassen.

Unit-TestsEin Unit-Test ist ein automatisierter Test, der bekannt-lich ein Stück produktiven Code testet. Ein Unit-Test erfüllt dabei verschiedene Eigenschaften, die unter dem Akronym F.I.R.S.T zusammengefasst sind  [2]. Unit-Tests sind schnell (Fast), unabhängig voneinander (Isolates), wiederholbar in einer beliebigen Umgebung (Repeatable), selbstvalidierend (Self-validating) und werden zeitlich mit oder sogar vor dem produktiven Code geschrieben (Timely).

Damit diese Eigenschaften eines Unit-Tests erfüllt sind, muss der produktive Code ein entsprechendes De-sign erfüllen. Beispielsweise muss es möglich sein, den Unit-Test in einer beliebigen Umgebung zu wiederholen (Repeatable): im Zug, zuhause, im Büro oder sogar auf dem Mond ohne Internet. Das bedeutet, dass beispiels-weise ein Datenbankzugriff nicht im produktiven Code enthalten sein darf; er muss mit einem Interface abstra-hiert werden. Dann wird im produktiven Code auf dieses Interface programmiert und eben nicht auf eine konkre-te Implementierung, die auf die Datenbank zugreift. Im Unit-Test kommt dann eine Testimplementierung des Interface zum Einsatz, ein so genanntes Mock-Objekt. Dieses Mock-Objekt hat die Aufgabe, den Datenbank-zugriff für den Test zu simulieren. Näheres dazu später beim Datenzugriff aus einem ViewModel.

Das S im F.I.R.S.T-Akronym sagt, dass ein Unit-Test auch selbstvalidierend sein muss. Das bedeutet, dass ein

Git-Grundlagen für EntwicklerThomas Claudius Huber

Git hat sich in den letzten Jahren zum Stan-dard für die Sourcecodeverwaltung und -versionierung entwickelt. Doch immer

noch fehlen Entwicklern die Grundlagen, um Git voll und ganz zu verstehen. Damit machen wir hier Schluss. In dieser Session lernen Sie die Basics von Git, um zu „fetchen“, „pullen“, „pushen“, „commit-ten“ und natürlich auch, um mit Tags und Branches umzugehen.

Besuchen Sie auch folgende Session:

Page 9: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 9

DOSSIER Agile & DevOps

erstellt. Ein Spike ist ein Durchstich und ein Experiment; er stellt sicher, dass der eingeschlagene Weg funktioniert. Der in einem Spike geschriebene Code sollte nach Sicher-stellung der Funktion laut Theorie wieder verworfen werden. Anschließend sollte es wieder TTD üblich nach Red/Green/Refactor weitergehen. In der Praxis sieht es meist so aus, dass erfahrene Entwickler Spikes schreiben, die danach auch zu 100 Prozent testbar sind, womit sie manchmal den klassischen TDD-Zyklus umgehen.

Das Testing-Framework xUnitEs gibt viele interessante Unit-Testing-Frameworks. Mit Visual Studio wird das von Microsoft bereitgestellte MSTest installiert. MSTest ist mittlerweile – insbeson-dere im .NET-Core-Zeitalter – etwas in die Jahre ge-kommen. Microsoft arbeitet aktuell an einer Version 2 von MSTest, die allerdings noch nicht fertiggestellt ist.

Neben MSTest ist das aus der Java-Welt von JUnit auf .NET portierte NUnit sehr beliebt. NUnit wurde mittlerweile komplett neu geschrieben, um die Vorteile von .NET auszunutzen. Einer der Entwickler von NUnit hat sich dazu entschlossen, xUnit zu erstellen, da ihm sowohl NUnit als auch MSTest zu schwergewichtig wa-ren. xUnit ist heute ebenfalls ein sehr beliebtes Frame-work und derzeit auch die erste Wahl des Autors. Einige Gründe dafür sind:

Test-driven DevelopmentTest-driven Development (TDD) ist eine Vorgehens-weise zum Schreiben von produktivem Code, die sich Unit-Testing zu Nutze macht. Dabei ist ein zentrales Prinzip, dass der Unit-Test vor und eben nicht mit oder nach dem produktiven Code geschrieben wird. Beim Einsatz von TDD befindet sich der Entwickler in einem ständigen Kreislauf der drei Phasen Red/Green/Refac-tor (Abb. 1), die in dieser Reihenfolge immer wieder durchlaufen werden:

1. Red: Es wird ein Unit-Test geschrieben, der fehl-schlägt, da der produktive Code noch nicht imple-mentiert wurde.

2. Green: Der produktive Code wird erstellt/angepasst.Und zwar lediglich so, damit der Unit-Test grün wird.

3. Refactor: Der produktive Code wird überarbeitetund strukturiert. Alle Unit-Tests müssen danachweiterhin grün sein. Ist der Entwickler zufrieden mitder Struktur, geht es weiter mit Schritt 1, indem dernächste Unit-Test geschrieben wird.

Test-driven Development hat neben den Vorteilen des klassischen Unit-Testings weitere Vorteile:

• Da der Test vor dem produktiven Code geschriebenwird, hat der produktive Code auf jeden Fall eintestbares Design.

• Das Schreiben des Tests vor dem produktiven Codeerfordert eine genaue Analyse der Anforderungen.

• Die eigene Logik kann bereits fertiggestellt werden,auch wenn beispielsweise der dazu benötigte Daten-bankserver noch nicht läuft.

Insbesondere der letzte Punkt ist spannend: In größeren Teams können Entwickler ihren Teil bereits fertigstel-len, auch wenn andere Teile der Software noch fehlen. Beispielsweise kann ein fehlender Datenbankzugriff in einem Unit-Test mit einem Mock-Objekt simuliert wer-den. Somit kann die Logik isoliert fertiggestellt werden, auch wenn der Datenbankzugriff noch nicht vorhanden ist. Es braucht dazu lediglich das entsprechende Inter-face, das später implementiert wird.

Spikes in TDDImmer mit einem roten Unit-Test zu starten, ist in man-chen Fällen etwas schwierig. Insbesondere, wenn sich ein Entwickler nicht sicher ist, ob der ein-geschlagene Weg für ein größeres Feature auch funktionieren wird – dann ist es bei TDD natürlich unsin-nig, zuerst mit einem Test zu starten. In ei-nem solchen Fall wird ein so genannter Spike

Listing 1

public class MainViewModelTests{ [Fact] public void ShouldLoadFriends() { }}

VS Team Services: Die fertige Teamum-gebung von Microsoft in der Cloud

Neno Loje

Visual Studio Team Services ist Microsofts offene Plattform für Ihre gesamte Soft-wareentwicklung. Und die ist so vielseitig

wie die Aufgabenstellungen, mit denen es Entwick-lungsteams heutzutage zu tu bekommen. So werden Builds unter Linux und iOS oder Projekte in Java oder für mobile Plattformen wie Android und iOS ebenso unterstützt wie das moderne .NET Core. Die fertige Teamumgebung als Cloud-Lösung wird u. a. in Westeuropa gehostet und ist für kleine Teams (bis fünf Benutzer), für Stakeholder sowie MSDN-Abon-nenten kostenlos. Der administrative Aufwand ist ge-ring und gibt Ihrem Team Zeit und Raum, sich auf die eigentliche Arbeit zu fokussieren. In diesem Vortrag begeben wir uns gemeinsam auf einen Rundgang durch VS Team Services, klären, welches die Unter-schiede zum klassischen Team Foundation Server (TFS) sind, welche Kosten entstehen und wie eine Migration vom TFS zu VSTS funktioniert. Wenn Sie den Aufwand für Ihre Entwicklungsumge-bung minimieren wollen, um sich auf die essenziellen Aufgaben konzentrieren zu können, sind Sie in die-sem Vortrag genau richtig.

Besuchen Sie auch folgende Session:

Page 10: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 10

DOSSIER Agile & DevOps

• xUnit ist sehr schnell.• xUnit benötigt im Gegensatz zu MSTest kein spezi-

fisches Testprojekt, stattdessen reicht eine einfacheKlassenbibliothek aus.

• xUnit und die für die Testausführung benötigtenKlassen lassen sich einfach via NuGet installieren.

• xUnit ist sehr leichtgewichtig: Die Testklassen selbstbenötigen keine Attribute, sondern nur die Testme-thoden.

• Testmethoden lassen sich einfach parametrisieren.

Ein Testprojekt mit xUnit anlegenUm mit xUnit ein Testprojekt anzulegen, wird eine Klas-senbibliothek erstellt und die beiden NuGet-Packages „xUnit“ und „xUnit.runner.visualstudio“ hinzugefügt (Abb. 2).

Wurden die NuGet-Packages hinzugefügt, wird noch eine Referenz auf das Projekt hinzugefügt, das die zu testenden Klassen enthält. In diesem Artikel

wird eine Referenz auf ein kleines WPF-Projekt na-mens FriendStorage.UI hinzugefügt, das eine Main­ViewModel-Klasse enthält.

Zum Testprojekt wird eine neue Klasse mit dem Namen MainViewModelTests hinzugefügt, in der die Testmethoden für diese MainViewModel-Klasse unter-gebracht werden.

Der erste Fall, der getestet werden soll, ist das Laden von Friend-Objekten. Somit wird eine ShouldLoad­Friends-Methode hinzugefügt. Damit diese als Testme-thode erkannt wird, wird sie mit dem Fact-Attribut aus dem Namespace XUnit markiert (Listing 1).

Auch wenn die Methode noch leer ist, lässt sie sich bereits ausführen. Dazu wird in Visual Studio über das Hauptmenü Test | Windows | Test Explorer der Test-Explorer aufgerufen. Nach einem Klick auf Run all wird die Testmethode ausgeführt und als grün im Test-Explorer angezeigt (Abb. 3).

Bei einem Blick auf den Test-Explorer fällt auf, dass der Testmethode der voll qualifizierte Klassenname vo-rangestellt wird. Das lässt sich anpassen, indem zum Testprojekt eine Konfigurationsdatei mit dem Namen xUnit.runner.json hinzugefügt wird. Diese Datei muss mit ins Ausgabeverzeichnis kopiert werden, um den Test-Runner von xUnit anzupassen. Mit dem folgenden Code werden im Test-Explorer die Testmethoden ohne den voll qualifizierten Klassennamen angezeigt:

{ "methodDisplay": "method"}

Testbare ViewModels schreibenEin eifriger Entwickler hat in der MainViewModel-Klas-se bereits etwas Code in der Load-Methode hinzugefügt, um Friend-Objekte mithilfe einer FriendDataService-Klasse zu laden und in einer ObservableCollection ab-zuspeichern (Listing 2).

Genau diese Load-Methode der MainViewModel-Klasse soll jetzt in der Testmethode ShouldLoadFriends getestet werden. Allerdings ist in Listing 2 ein Prob-lem zu erkennen. Die Load-Methode instanziert einen FriendDataService. Dieser FriendDataService könnte

Listing 2public class MainViewModel:ViewModelBase{ public MainViewModel() { Friends = new ObservableCollection<Friend>(); }

public void Load() { var dataService = new FriendDataService(); var friends = dataService.GetFriends();

foreach (var friend in friends) { Friends.Add(friend); } }

public ObservableCollection<Friend> Friends { get; private set; }}

Abb. 3: Die Testmethode wird im Test-Explorer ange-zeigt

Abb. 2: Die NuGet-Packages „xUnit“ und „xUnit.runner.visualstudio“ für ein Testprojekt mit xUnit

Page 11: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 11

DOSSIER Agile & DevOps

auf eine Datenbank, auf einen Web Service oder auf etwas anderes zugreifen. Das bedeutet, dass mit diesem FriendDataService ein Unit-Test nicht mehr in einer beliebigen Umgebung wiederholbar und es somit eben kein Unit-Test wäre. Und damit stellt sich die Frage, wie sich testbare ViewModels entwickeln lassen. Die Antwort ist recht banal: ViewModels werden testbar, indem Abhängigkeiten abstrahiert und ausgelagert werden. Diese Antwort gilt nicht nur für ViewModels, sondern auch für jede andere Klasse, die testbar sein soll. Im Fall des MainViewModels aus Listing 2 stellt der FriendDataService eine Abhängigkeit dar, die das Testen der Load-Methode nicht erlaubt. Woher soll ein Entwickler im Unit-Test wissen, wie viele Friend-Objekte dieser FriendDataService zurückgibt? Wie soll das Ganze in einer beliebigen Umgebung auch ohne Datenzugriff getestet werden können? Es gibt nur eine Lösung: Der Datenzugriff muss aus der ViewModel-Klasse abstrahiert werden.

Den Datenzugriff abstrahierenUm den Datenzugriff aus der MainViewModel-Klasse zu abstrahieren, wird ein IFriendDataProvider-Interface eingeführt, das eine Methode LoadFriends wie folgt dar-gestellt definiert:

public interface IFriendDataProvider{ IEnumerable<Friend> LoadFriends();}

Das MainViewModel selbst wird angepasst. Im Konst-ruktor wird ein IFriendDataProvider-Objekt entgegen-genommen und in der Instanzvariablen _dataProvider gespeichert. Anstelle der FriendDataAccess-Klasse wird in der Load-Methode des MainViewModels das IFriend­Data Provider-Objekt verwendet (Listing 3).

Mit dem Verwenden des IFriendDataProvider-Inter-face ist die Abhängigkeit zur FriendDataAccess-Klasse aus dem MainViewModel verschwunden. Die Load-Me-thode lässt sich jetzt einfach testen, indem der IFriend­DataProvider gemockt wird.

Den Datenzugriff von Hand „mocken“Um die Load-Methode des MainViewModels zu testen, wird im Testprojekt eine neue Klasse namens FriendDa­taProviderMock hinzugefügt, die das Interface IFriend­DataProvider implementiert (Listing 4).

Mit der FriendDataProviderMock-Klasse lässt sich in der ShouldLoadFriends-Methode eine neue Main­ViewModel-Instanz erstellen (Listing 5). Anschließend wird auf dem MainViewModel die Load-Methode auf-gerufen und mit der Assert-Klasse angenommen, dass die Friends Property des MainViewModels genau zwei Friend-Instanzen enthält. Das sind die zwei Instanzen, die vom FriendDataProviderMock-Objekt aus Listing 4 zurückgegeben werden.

Wird der Test ausgeführt, ist er grün. Die händische Implementierung von IFriendDataProvider für den Test kann natürlich etwas aufwändig werden, wenn es vie-

Listing 4

public class FriendDataProviderMock : IFriendDataProvider{ public IEnumerable<Friend> LoadFriends() { yield return new Friend { Id = 1, FirstName = "Thomas" }; yield return new Friend { Id = 2, FirstName = "Julia" }; }}

Listing 5

public class MainViewModelTests{ [Fact] public void ShouldLoadFriends() { var viewModel = new MainViewModel(new FriendDataProviderMock());

viewModel.Load();

Assert.Equal(2, viewModel.Friends.Count); }}

Listing 3

public class MainViewModel:ViewModelBase{ private IFriendDataProvider _dataProvider;

public MainViewModel(IFriendDataProvider dataProvider) { _dataProvider = dataProvider; Friends = new ObservableCollection<Friend>(); }

public void Load() { var friends = _dataProvider.LoadFriends();

foreach (var friend in friends) { Friends.Add(friend); } }

public ObservableCollection<Friend> Friends { get; private set; }}

Page 12: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 12

DOSSIER Agile & DevOps

le Interfaces und viele Tests gibt. In einem solchen Fall lohnt sich der Einsatz einer Mocking Library wie bei-spielsweise Moq.

Moq einsetzenIn Listing 4 wurde händisch eine FriendDataProvider­Mock-Klasse für den Test erstellt. Diese Klasse kann sich ein Entwickler auch sparen, indem er die Moq Library einsetzt. Dazu einfach das NuGet-Package „Moq“ zum Testprojekt hinzufügen und wie in Lis-ting  6 eine Instanz der generischen Mock-Klasse er-zeugen. Als generischer Typ-Parameter wird dabei das Interface IFriendData Provider angegeben. Auf der Mock-Instanz wird mit der Setup-Methode die LoadFriends-Methode des IFriendDataProvider auf-gesetzt. Es wird eine Liste mit zwei Friend-Objekten als Return-Wert festgelegt. Hinter den Kulissen er-zeugt die Mock-Klasse einen dynamischen Proxy für das IFriendDataProvider-Interface. Dieser dynamische Proxy wird mithilfe des Castle-Frameworks erstellt. Über die Object Property der Mock-Instanz lässt sich der dynamische Proxy abgreifen, der in Listing 6 eine IFriendDataProvider-Instanz darstellt – und diese lässt sich jetzt wunderbar zum Testen des MainViewModels verwenden.

Dependency Injection mit AutofacNachdem die Load-Methode des MainViewModels fertiggestellt und getestet ist, wird eine IFriendData­Pro vider-Implementierung erstellt, die zur Laufzeit der Anwendung genutzt wird. Sie greift intern in der LoadFriends-Methode beispielsweise auf den Friend­DataService zu, der vorher direkt im MainViewModel verwendet wurde (Listing 7).

Ist der FriendDataProvider fertig, wird noch der Konstruktor des MainWindow angepasst, damit dieser ein MainViewModel entgegennimmt und es im Data­Context speichert. Findet das Loaded-Event auf dem MainWindow statt, wird auf dem MainViewModel die Load-Methode aufgerufen (Listing 8).

Die WPF kann das MainWindow jetzt nicht mehr selbst instanziieren, da es keinen parameterlosen Konst-ruktor gibt. Daher wird in der Datei App.xaml die Start­up Uri Property entfernt und in der Codebehind-Datei die OnStartup-Methode überschrieben. Darin wird eine MainWindow-Instanz erstellt, indem dem Konstruktor ein MainViewModel übergeben wird. Dem Konstruk-tor des MainViewModels wiederum wird eine Friend­

Listing 9

public partial class App : Application{ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var mainWindow = new MainWindow( new MainViewModel( new FriendDataProvider())); mainWindow.Show(); }}

Listing 6

public void ShouldLoadFriends(){ var dataProviderMock = new Mock<IFriendDataProvider>();

dataProviderMock.Setup(dp => dp.LoadFriends()) .Returns(new List<Friend> { new Friend {Id = 1, FirstName = "Thomas"}, new Friend {Id = 1, FirstName = "Julia"} }); IFriendDataProvider dataProvider = dataProviderMock.Object; var viewModel = new MainViewModel(dataProvider);

viewModel.Load();

Assert.Equal(2, viewModel.Friends.Count);}

Listing 7public class FriendDataProvider : IFriendDataProvider{ public IEnumerable<Friend> LoadFriends() { var dataService = new FriendDataService(); return dataService.GetFriends(); }}

Listing 8

public partial class MainWindow : Window{ private readonly MainViewModel _viewModel;

public MainWindow(MainViewModel viewModel) { _viewModel = viewModel; InitializeComponent(); this.Loaded += MainWindow_Loaded; DataContext = _viewModel; }

private void MainWindow_Loaded(object sender, RoutedEventArgs e) { _viewModel.Load(); }}

Page 13: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 13

DOSSIER Agile & DevOps

DataProvider-Instanz übergeben (Listing 9). Schließlich wird am Ende die Show-Methode aufgerufen, um das Fenster anzuzeigen.

Der Code aus Listing 9 hat jetzt ein kleines Problem: Jedes Mal, wenn sich der Konstruktor des MainWin­dow, des MainViewModels oder des FriendDataProvi­ders ändert, muss diese Stelle entsprechend angepasst werden. Das lässt sich mit einem Dependency-Injec-tion-Framework verhindern. Denn genau das passiert in Listing 9: In das MainWindow wird eine MainView­Model-Abhängigkeit injiziert, in das MainViewModel wird ein IFriendDataProvider injiziert.

Ein Dependency-Injection-Framework kann diese Injektionen selbst vornehmen, indem ihm mittgeteilt wird, welche konkreten Typen für welche abstrakten Typen injiziert werden sollen.

Auf dem Markt gibt es zahlreiche dieser Frame-works, ein sehr beliebtes ist Autofac. Um es einzuset-zen, wird im Projekt eine Bootstrapper-Klasse erstellt, die eine Bootstrap-Methode enthält, die wiederum einen IContainer zurückgibt. Der IContainer hat die Informationen über die zu erstellenden Typen. Zum Erstellen des IContainer kommt bei Autofac die in Lis-ting 10 verwendete ContainerBuilder-Klasse zum Ein-

satz. Auf einer ContainerBuilder-Instanz werden mit RegisterType verschiedene Typen registriert, am Ende wird mit der Build-Methode ein IContainer erstellt. Listing 10 zeigt, wie das MainWindow und das Main­ViewModel mit der Methode AsSelf registriert werden. Das bedeutet, wo auch immer diese konkreten Typen benötigt werden, kann Autofac sie erstellen und/oder injizieren. Der Typ FriendDataProvider wird ebenfalls registriert. Mit der As-Methode wird festgelegt, dass eine FriendDataProvider-Instanz immer dann verwen-det wird, wenn eine IFriendDataProvider-Instanz be-nötigt wird.

Der erstellte Bootstrapper ist fertig und kann nun in der OnStartup-Methode der App-Klasse genutzt wer-den, um den IContainer zu erstellen und schließlich das MainWindow zu erzeugen (Listing 11). Was dabei jetzt auffällt, ist, dass kein Konstruktor mehr aufgerufen wird, um das MainWindow zu erzeugen. Stattdessen wird die Resolve-Methode verwendet – eine Extension-Methode für den IContainer, die im Namespace Auto-fac untergebracht ist. Mit der Resolve-Methode wird eine neue MainWindow-Instanz erzeugt; wenn sich jetzt der Konstruktor ändert, müssen die Abhängig-keiten lediglich im Container registriert werden – was in der Bootstrapper-Klasse passiert. Eine Anpassung der OnStartup-Methode ist nicht mehr notwendig, da das Dependency-Injection-Framework den Rest über-nimmt.

Methodenaufrufe testenWird das ViewModel erweitert, kommt man schnell an den Punkt, an dem Methodenaufrufe getestet

Listing 10

public class Bootstrapper{ public IContainer Bootstrap() { var builder = new ContainerBuilder();

builder.RegisterType<MainWindow>().AsSelf(); builder.RegisterType<MainViewModel>().AsSelf(); builder.RegisterType<FriendDataProvider>().As<IFriendDataProvider>();

return builder.Build(); }}

Listing 11

public partial class App : Application{ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e);

var bootstrapper = new Bootstrapper(); var container = bootstrapper.Bootstrap();

var mainWindow = container.Resolve<MainWindow>(); mainWindow.Show(); }}

Listing 12

public class MainViewModel:ViewModelBase{ public MainViewModel(IFriendDataProvider dataProvider) { ... DeleteCommand = new DelegateCommand(OnDeleteExecute,

OnDeleteCanExecute); }

public ICommand DeleteCommand { get; }

private void OnDeleteExecute(object obj) { var friendToDelete = SelectedFriend;

_dataProvider.DeleteFriend(friendToDelete.Id); Friends.Remove(friendToDelete); SelectedFriend = null; } ...}

Page 14: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 14

DOSSIER Agile & DevOps

werden müssen. Beispielsweise zeigt Listing  12 ein DeleteCommand im MainViewModel. In der OnDe­lete Exe cute-Methode wird der selektierte Freund ver-wendet und dessen ID an die DeleteFriend-Methode des DataProviders übergeben. Anschließend wird das Friend-Objekt aus der Friends Collection entfernt und

Listing 15public class MainViewModel:ViewModelBase{ private readonly IMessageBoxService _messageBoxService; ...

public MainViewModel(IFriendDataProvider dataProvider, IMessageBoxService messageBoxService) { _messageBoxService = messageBoxService; ... }

private void OnDeleteExecute(object obj) { var result = _messageBoxService.Show( "Möchten Sie den Freund wirklich löschen?", "Löschen", MessageBoxButton.OKCancel);

if (result == MessageBoxResult.OK) { ... } }

die SelectedFriend Property auf null gesetzt. Doch wie lässt sich all das testen?

Listing  13 zeigt eine Testmethode, die genau den Inhalt der OnDeleteExecute-Methode aus Listing 12 testet. Interessant sind dabei die letzten drei Anwei-sungen, nachdem das DeleteCommand mit der Exe­cute-Methode ausgeführt wurde. Zuerst wird auf der in der Variablen dataProviderMock gespeicherten Mock-Instanz mithilfe der Verify-Methode geprüft, ob die DeleteFriend-Methode genau einmal aufgerufen wurde – und zwar genau mit der ID des zu löschen-den Freundes (friendToDelete). Anschließend wird geprüft, ob der zu löschende Freund aus der Friends Collection entfernt und die SelectedFriend Property wieder auf null gesetzt wurde.

Dialoge anzeigenWas beim Laden der Daten galt, um das ViewModel testbar zu gestalten, gilt auch für das Anzeigen von Dialogen: Abhängigkeiten müssen abstrahiert und aus dem ViewModel entfernt werden. Ein typischer Fall ist das Anzeigen einer Ok Cancel MessageBox beim Löschen eines Freundes. In Listing 12 wurde die OnDeleteExe cute-Methode gezeigt. In diese Methode lässt sich eine MessageBox wie in Listing 14 integrie-ren; die Anwendung wird wunderbar funktionieren. Das Problem ist nur, dass die MessageBox auch in je-dem Unit-Test angezeigt wird, in dem das DeleteCom­mand ausgeführt wird; beispielsweise in dem Unit-Test aus Listing 13. Der Unit-Test ist somit blockiert – Vi-sual Studio zeigt beim Ausführen des Unit-Tests die

Listing 13

[Fact]public void ShouldDeleteAndRemoveFriendWhenDeleteCommandIsExecuted(){ var dataProviderMock = new Mock<IFriendDataProvider>();

dataProviderMock.Setup(dp => dp.LoadFriends()) .Returns(new List<Friend> { new Friend {Id = 1, FirstName = "Thomas"}, new Friend {Id = 1, FirstName = "Julia"} }); IFriendDataProvider dataProvider = dataProviderMock.Object; var viewModel = new MainViewModel(dataProvider);

viewModel.Load();

var friendToDelete = viewModel.Friends.First(); viewModel.SelectedFriend = friendToDelete;

viewModel.DeleteCommand.Execute(null);

dataProviderMock.Verify(dp => dp.DeleteFriend(friendToDelete.Id),Times. Once); Assert.False(viewModel.Friends.Contains(friendToDelete)); Assert.Null(viewModel.SelectedFriend);}

Listing 14

private void OnDeleteExecute(object obj){ var result = MessageBox.Show( "Möchten Sie den Freund wirklich löschen?", "Löschen", MessageBoxButton.OKCancel);

if (result == MessageBoxResult.OK) { var friendToDelete = SelectedFriend;

_dataProvider.DeleteFriend(friendToDelete.Id); Friends.Remove(friendToDelete); SelectedFriend = null; }}

Page 15: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 15

DOSSIER Agile & DevOps

MessageBox an. Ein Build-Server, der die Unit-Tests ausführt, wird einfach blockiert sein. Somit gilt auch an dieser Stelle: Die MessageBox muss aus dem View-Model abstrahiert werden.

Um die MessageBox zu abstrahieren, wird das Inter-face IMessageBoxService eingeführt:

public interface IMessageBoxService{ MessageBoxResult Show(string message, string title, MessageBoxButton buttons);}

Das MainViewModel wird erweitert, ein neuer Konst-ruktorparameter vom Typ IMessageBoxService kommt hinzu. Die erhaltene Instanz wird in einer Instanzvaria-blen gespeichert und in der OnDeleteExecute-Methode verwendet, was in Listing 15 zu sehen ist.

Mit der Abstraktion der MessageBox in einen IMes­sageBoxService lässt sich dieser IMessageBoxService im Unit-Test jetzt wunderbar „mocken“. Beispielswei-se zeigt das folgende Codebeispiel, wie das MainView­Model mit einem IMessageBoxService erstellt wird. Dabei wird die Methode Show des IMessageBoxSer­vice aufgesetzt:

var messageBoxServiceMock = new Mock<IMessageBoxService>();messageBoxServiceMock.Setup(m => m.Show(It.IsAny<string>(), It.IsAny<string>(), MessageBoxButton.OKCancel)) .Returns(MessageBoxResult.OK);var viewModel = new MainViewModel(dataProvider, messageBoxServiceMock. Object);

Zu beachten ist der Einsatz der Klasse It, die auch zur Moq Library gehört. Mit der generischen It.IsAny-Methode wird festgelegt, dass dieses Set-up der IMes­sageBoxService.Show-Methode für einen beliebigen String als ersten Parameter und einen beliebigen String als zweiten Parameter gilt. Als dritter Parameter muss MessageBoxButton.OKCancel angegeben werden,

und dann wird das Ergebnis MessageBoxResult.OK zurückgegeben.

Mit diesem Code wird im Unit-Test jetzt keine Mes­sageBox mehr angezeigt. Für den produktiven Code ist noch eine Implementierung von ImessageBoxService notwendig. Das kann einfach wie folgt aussehen:

public class MessageBoxService : IMessageBoxService{ public MessageBoxResult Show(string message, string title, MessageBoxButton buttons) { return MessageBox.Show(message, title, buttons); }}

Damit die Anwendung läuft, ist der MessageBoxSer­vice lediglich noch im Container von Autofac zu re-gistrieren, damit dieser als IMessageBoxService in das MainViewModel injiziert wird. Dazu wird in der Boots-trap-Methode des Bootstrappers RegisterType auf dem ContainerBuilder aufgerufen (Listing 16).

Einfach F5 drücken, und die Anwendung startet und läuft ohne weitere Anpassungen mit dem neuen Mes­sageBoxService.

FazitDieser Artikel hat gezeigt, wie sich testbare ViewMo-dels erstellen lassen. Dabei ist das Prinzip ganz ein-fach: Abhängigkeiten werden mithilfe von Interfaces abstrahiert und als Konstruktorparameter definiert. Dadurch lassen sich ViewModels mit 100  Prozent Test-Coverage entwickeln. Allerdings sollte man dazu sagen, dass sich Entwickler auf die komplexen Teile konzentrieren sollten. Das Laden von Freunden und das Anzeigen einer Mes sage Box wurde in diesem Ar-tikel beispielhaft aufgeführt. In der Praxis empfehlen sich immer diese Stellen für Unit-Tests, an denen man als Entwickler darüber nachdenkt, doch den einen oder anderen Kommentar zu schreiben. Das Offensichtliche zu testen, ist nicht immer lohnenswert, aber das Herz-stück zu testen, ist sehr zu empfehlen.

Thomas Claudius Huber arbeitet als Principal Consultant in Basel bei der Trivadis AG. Er ist Microsoft MVP für Windows Development und Autor zahlreicher Bücher, Artikel und Pluralsight-Videos. Er spricht regelmäßig auf großen Entwicklerkonferenzen wie der BASTA! und freut sich immer über Feedback.

www.thomasclaudiushuber.com

Listing 16

public IContainer Bootstrap(){ var builder = new ContainerBuilder();

builder.RegisterType<MessageBoxService>().As<IMessageBoxService>();

builder.RegisterType<MainWindow>().AsSelf(); builder.RegisterType<MainViewModel>().AsSelf(); builder.RegisterType<FriendDataProvider>().As<IFriendDataProvider>();

return builder.Build();}

Links & Literatur

[1] TDD: https://de.wikipedia.org/wiki/Testgetriebene_Entwicklung

[2] F.I.R.S.T.: http://agileinaflash.blogspot.de/2009/02/first.html

[3] TDD und MVVM: https://www.pluralsight.com/courses/wpf-mvvm-test-driven-development-viewmodels

Page 16: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 16

DOSSIER Agile & DevOps

von Marc Müller

Ein anpassbares und erweiterbares ALM-Tool zu haben, ist heutzutage für den Erfolg von wesentlicher Bedeu-tung. 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 Inte-grationspunkte zur Verfügung stehen.

Wie oft hatte man sich in der Vergangenheit ge-wünscht, an einer bestimmten Stelle einen zusätzlichen Button hinzuzufügen oder gar eigene Views einzublen-den. 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 imple-mentiert. Eigene Backend-Services können damit aber nicht integriert werden. Sobald Servicelogik erforderlich ist, kann dies nur als externer Service implementiert wer-den, also z. B. als eigenständige Webapplikation.

Verschiedene Erweiterungs- und Anpassungsmöglichkeiten

VSTS/TFS – ganz nach meinem Geschmack 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 Funk-tionsumfang in Sachen Integration und Erweiterbarkeit stark vergrößert. Seit Kurzem kann auch die Web oberflä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.

©iS

tock

ph

oto

.co

m/s

task

Page 17: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 17

DOSSIER Agile & DevOps

Diese Applikationen können über die REST-Schnittstelle mit dem VSTS bzw. TFS interagieren. Zur Benach-richtigung 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 ein-zelnen Erweiterungspunkten sind auf der Extensibility-Webseite von VSTS  [1] zu finden. Neben einer detaillierten Auflistung werden ent-sprechende Beispiele  [2] zur Verfü-gung gestellt.

Eigenes Dashboard-Widget erstellenIn diesem Artikel wird die Extensibility-Schnittstelle an-hand 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 erfra-gen. Da wir für verschiedene Build-Definitionen verschie-dene 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.

UI-Extensions bestehen primär aus HTML und Ja-vaScript 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 Er-weiterung. 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ötz-lich mehr Berechtigungen als einmal bestätigt eingefor-dert werden können.

Generell wird die Sicherheit großgeschrieben. Eine UI-Erweiterung läuft immer „sandboxed“ in einem de-dizierten iFrame und erhält über ein SDK Zugriff auf den

Listing 1: Auszug aus dem Manifest{ "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"}}]}

Abb. 1: Übersicht über die Integrationspunkte von Extension in VSTS/TFS

Abb. 2: Build-Traffic-Lights-Widget als Beispiel für diesen Artikel

Page 18: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 18

DOSSIER Agile & DevOps

VSTS/TFS – samt Services und Events. Sorgen wegen der Authentifizierung muss man sich bei der Verwendung des SDK keine machen, da der aktuelle Nutzerkontext über-nommen wird. Jeder API-Call wird gegenüber den defi-nierten und durch den Nutzer bestätigten Scopes geprüft und bei einem Fehlverhalten blockiert. Damit alle Arte-fakte sowie das Manifest ausgeliefert und installiert wer-den können, erstellen wir als Artefakt ein Paket in Form einer VSIX-Datei. Diese kann manuell auf den TFS gela-den oder über den VSTS Marketplace vertrieben werden.

Listing 2: „TrafficLightsWidget.html“<body> <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> <div class="widget" > <h2 class="title" id="buildDefinitionTitle"></h2> <div class="content" id="content"> </div> </div></body>

Listing 4: „TrafficLightsCollection.ts“/// <reference path='../node_modules/vss-web-extension-sdk/typings/ /// VSS.d.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(); }); }}

Listing 3: „TrafficLightsWidget.ts“/// <reference path='../node_modules/vss-web-extension-sdk/typings/ /// VSS.d.ts' />/// <reference path="trafficlightscollection.ts" />import TrafficLights = require("scripts/TrafficLightsCollection");

function GetSettings(widgetSettings) {...}

function RenderTrafficLights(WidgetHelpers, widgetSettings) { var numberOfBuilds = <number>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();});

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 Pa-ckage Manager (npm). Damit wir die Paketerstellung au-tomatisieren 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  [3] installieren. Hiermit verfügt Vi-sual Studio über einen neuen Projekttyp, der das Erweite-rungsprojekt mit allen Abhängigkeiten und Tools korrekt aufsetzt. Zudem möchten wir in unserem Beispiel nicht direkt JavaScript-Code schreiben, sondern TypeScript

Page 19: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Abb. 3: Projektstruktur des Build-Traffic-Lights-Widgets

www.basta.net 19

DOSSIER Agile & DevOps

zur Implementierung verwenden. Abbildung 3 zeigt die Projektstruktur in Visual Studio für unsere Erweiterung.

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.

Aufgrund des Manifests wird unsere Erweiterung richtig im Widgetkatalog aufgeführt, und aufgrund der Contribution Points werden die richtigen HTML-Da-teien für die UI-Erweiterungen angezogen. Die Imple-mentierung der Views ist nichts anderes als klassische Webentwicklung. Jedoch ist die Integration der Kom-ponente über das zur Verfügung gestellte SDK von ent-scheidender Bedeutung.

Das SDK liegt uns in Form einer JavaScript-Datei (VSS.SDK.js) in unserem Projekt vor. Da wir mit Ty-peScript arbeiten, laden wir ebenso die dazugehörigen Typinformationen. Nach der Referenzierung der Skript-datei 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 Ja-vaScript-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).

Damit unser Widget und seine Controls nicht vom Look and Feel von VSTS/FS abweichen, gibt uns das SDK die Möglichkeit, über WidgetHelpers.Include­WidgetStyles() 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.

Um nun mit dem REST-API von VSTS/TFS zu kommu-nizieren, 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 automa-tisch im Sicherheitskontext des aktuellen Nutzers. In Lis-ting 4 ist der entsprechende Auszug zu sehen.

Was jetzt noch fehlt, ist die Möglichkeit, unser Wid-get 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 Life-cycle-Events überschrieben werden: load und onSave(). Hier können wir über das SDK unsere spezifischen Ein-stellungswerte als JSON-Definition laden und speichern. Damit die Livepreview im Konfigurationsmodus funktio-niert, müssen entsprechende Änderungsevents publiziert werden. Die aktuellen Konfigurationswerte werden mit-tels TypeScript mit den UI-Controls synchron gehalten.

Ein komplettes Code-Listing wäre für diesen Artikel zu umfangreich. Entsprechend steht der gesamte Code dieser Beispiel-Extension auf GitHub [4] zur Verfügung.

TFS/VSTS mit Extensions an die eige-nen Bedürfnisse anpassen

Marc Müller

Seit der anfänglichen Anpassbarkeit von Work-Item-Typen wurde der Funktionsum-fang in Sachen Integration und Erweiter-

barkeit für TFS und VSTS stark vergrößert. TFS bzw. VSTS können über Extension in den Bereichen Web UI, Dashboard Widgets und Build Tasks einfach er-weitert werden. Nebst den Extensions bieten REST-API und Service Hooks ideale Möglichkeiten, um Drittkomponenten einfach in das ALM-Tool zu integ-rieren. In unserem Vortrag zeigen wir Ihnen, wie ei-gene Extensions erstellt werden und was der TFS in Sachen Erweiterbarkeit und Integration zu bieten hat.

Besuchen Sie auch folgende Session:

Page 20: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Entwicklungsworkflow von ExtensionsWer Extensions entwickelt, setzt sich primär mit klas-sischen 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 er-stellen. 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 ent-sprechende Package.

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ünsch-ten Team Project Collection installiert. Soll die Exten-sion in VSTS getestet werden, ist das Publizieren im Marketplace notwendig. Keine Angst, auch hier kön-nen 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-Ac-counts 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 De-bugger analysiert werden.

Einbindung von externen Services mit REST-API und WebHooksDie 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 betrie-ben bzw. gehostet werden. VSTS und TFS stellen ein

Abb. 4: Grunt-Task zur Erstellung des Pakets

Listing 5: Widget-Konfiguration

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();});

www.basta.net 20

DOSSIER Agile & DevOps

Page 21: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

REST-API  [5] zur Verfügung, mit dem fast alle Ak-tionen getätigt werden können. Entwickelt man mit .NET, so stehen entsprechende NuGet-Pakete zur Ver-fü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. Ent-sprechend wurde in VSTS/TFS das Konzept von Web-Hooks 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 rela-tiv einfach zu implementieren. VSTS/TFS führt einen POST-Request an den definierten Empfänger durch, und der Body beinhaltet alle Eventdaten, wie zum Bei-spiel die Work-Item-Daten des geänderten Work Items bei einem Work-Item-Update-Event. Einen POST-Re-quest 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

Abb. 5: Registrierung des WebHook-Receivers in VSTS

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); }}

unter die Arme. Für ASP.NET gibt es ein WebHook-Framework [6], 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 ei-nes Web-API-Projekts muss lediglich das NuGet-Paket Microsoft.AspNet.WebHooks.Receivers.VSTS  [7] 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 ge-wünschte Methode überschrieben werden (Listing 6).

Das WebHooks-Framework generiert über die Konfi-guration 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 hin-terlegt werden.

FazitDie hier gezeigte UI-Integration bietet weitere Möglich-keiten, den eigenen Prozess bis in das Standard-Tooling zu integrieren. Somit sind nahezu grenzenlose Möglich-keiten für eine optimale Usability und Unterstützung der Nutzer gegeben. Wer sich mit HTML, CSS und Ja-vaScript/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.

Marc Müller arbeitet als Principal Consultant für Microsoft ALM sowie .NET-/Windows-Azure-Lösungen bei der 4tecture GmbH und wurde von Microsoft als Most Valuable Professional (MVP) für Visual Studio ALM ausgezeichnet. Sein ALM-Fachwissen sowie Know-how für Enterprise-Architekturen und komponentenbasier-

te verteilte Systeme konnte er in den letzten Jahren in viele Projekte einbrin-gen. Als Trainer und Referent zählen die Ausbildung und das Coaching von ALM- und .NET-Projektteams zu seinen Schwerpunkten.

Links & Literatur

[1] https://www.visualstudio.com/en-us/docs/integrate/extensions/overview

[2] https://github.com/Microsoft/vsts-extension-samples

[3] https://visualstudiogallery.msdn.microsoft.com/a00f6cfc-4dbb-4b9a-a1b8-4d24bf46770b?SRC=VSIDE

[4] https://github.com/4tecture/BuildTrafficLights

[5] https://www.visualstudio.com/en-us/docs/integrate/api/overview

[6] https://github.com/aspnet/WebHooks

[7] https://www.nuget.org/packages/Microsoft.AspNet.WebHooks.Receivers.VSTS

www.basta.net 21

DOSSIER Agile & DevOps

Page 22: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 22

DOSSIER Web Development

von Manuel Rauber

In der heutigen Zeit haben wir alle sicher schon mal ein Real-Time-Collaboration-Tool genutzt, ein Werkzeug, um in Echtzeit gemeinsamen mit anderen Personen ein Dokument zu editieren. Als Paradebeispiel sei hier Google Docs genannt, aber mittlerweile auch Office-Web-Apps wie Word Online. Je mehr Leute dabei sind, umso mehr bunte Marker springen munter in einem Do-kument herum, fügen neue Texte ein oder ändern beste-hende. Das alles passiert in Echtzeit.

Prinzipiell gibt es verschiedene Möglichkeiten, das ge-meinsame Editieren von Dokumenten zu ermöglichen. Die erste Möglichkeit ist ein typisches Intervallpolling, also das Senden einer Anfrage mit der Bitte um Aktu-alisierung zum Server in einem bestimmten Intervall. Das heißt, dass wir beispielsweise alle 10 Sekunden eine HTTP- Anfrage zum Server schicken und als Antwort die Änderungen oder das komplette Dokument bekom-men. Wirklich Echtzeit ist dies aber nicht, da wir bis zu 10 Sekunden (plus die Zeit zum Verbindungsaufbau und Übertragen der Daten) warten müssen, um neue

Nutzung von echtem Server-Push mit dem WebSocket-Protokoll

Echtzeitchat mit Node.js und Socket.IO

Was wäre heute eine Anwendung ohne die bekannten In-App Notifications? Neue Chatnachricht, neuer Artikel oder im Allgemeinen eine neue Benachrichtigung – oft symbolisiert durch ein kleines Icon mit einer Zahl zur Anzeige, wie viele neue Nach-richten uns erwarten. Kaum mehr wegzudenken und ständig verfügbar, auch ohne dass man eine Seite neu laden muss. Und das Ganze natürlich in Echtzeit. Wie das geht? WebSocket für Echtzeitkommunikation lautet das Zauberwort.

© S&S Media

Page 23: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 23

DOSSIER Web Development

Änderungen zu erhalten. Das Chaos, das hier in einem gemeinsamen Dokument entstehen würde, kann man sich sicherlich denken. Spaß sieht anders aus.

Die zweite Möglichkeit ist die Nutzung von HTTP Long Polling. Hier schickt der Client eine Anfrage zum Server. Der Server hält die Verbindung so lange offen, bis tatsächlich eine Nachricht eintrifft, und beantwor-tet die Anfrage. Ist dies geschehen, baut der Client so-fort wieder eine neue Verbindung zum Server auf, bis die nächste Nachricht zur Verfügung steht. Die Nach-richten stehen so schneller als in der ersten Variante zur Verfügung, das Ganze ist aber ineffizient, da mit jeder Anfrage und Antwort HTTP-Header übertragen werden. Auch hier kann man sich vorstellen, dass das für das Editieren eines Dokuments nicht wirklich sinn-voll geeignet ist, wenn viele Änderungen vorgenommen werden. Mit großer Wahrscheinlichkeit sind die eigent-lichen Änderungen kleiner als der Overhead durch die HTTP-Header.

Die dritte Möglichkeit ist die Nutzung des WebSo-cket-Protokolls. Dabei handelt es sich um ein Netz-werkprotokoll, das auf TCP aufsetzt, um eine bidirektionale Verbindung zwischen Client und Server aufzubauen. Client und Server können sich so jederzeit über eine Verbindung Daten schicken. Hierbei handelt

es sich allerdings nicht um HTTP-Anfragen, d. h. bei der Übermittlung der Daten müssen keine zusätzlichen Header mitgesendet werden. Die Datenpakete sind daher deutlich kleiner und beinhalten genau das, was gesendet wird, also genau unsere Änderungen im ge-meinsamen Dokument.

Darf ich Ihnen die Hand geben?Möchte ein Client eine WebSocket-Verbindung zu ei-nem Server aufbauen, schickt der Client eine normale HTTP-Anfrage und sendet zusätzliche HTTP-Header mit (Listing 1), mit der Bitte um ein Upgrade auf das WebSocket-Protokoll.

Unterstützt der Server am Endpunkt /chat das WebSo-cket-Protokoll, antwortet er mit dem HTTP-Statuscode 101 Switching Protocols (Listing 2). Jetzt können Client und Server über die entstandene WebSocket-Verbindung bidirektional kommunizieren. Dieser Prozess nennt sich auch „WebSocket Handshake“.

Doch aufgepasst: Sobald eine WebSocket-Verbin-dung aufgebaut wurde, passiert – solange niemand Daten übermittelt – auf dieser Verbindung tatsächlich nichts mehr. Das Protokoll beinhaltet kein Keep­Alive. Vielmehr gehen Client und Server davon aus, dass nach dem Verbindungsaufbau die Verbindung steht, d. h. dass in der Zwischenzeit beispielsweise die WLAN-Verbindung schlafen gelegt wird, da die Verbindung keine Aktivität aufweist. Erst wenn einer der Teilneh-mer versucht Daten zu senden, kann erkannt werden, dass die Verbindung nicht mehr existiert und neu auf-gebaut werden muss. Vorteilhaft ist dies natürlich für mobile Endgeräte. Eine Verbindung, die keine Aktivität hat, verbraucht keine CPU-Kapazitäten und leert auch nicht den Akku.

Listing 1: Request-Header für ein Upgrade auf das WebSocket-Protokoll

GET /chat HTTP/1.1Host: beispiel-seite.deConnection: Upgrade Upgrade: websocketSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://beispiel-seite.deSec-WebSocket-Protocol: chatSec-WebSocket-Version: 13

Listing 2: Response-Header für das Upgrade auf das WebSocket-Protokoll

HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat

Video-Link:Chris Heilmann im Interview zum Edge-Browser auf der BASTA! Spring

Keyboards? Where we’re going, we don’t need keyboards!

Don Wibier

One of the cornerstones in Microsoft’s digital assistant Cortana are cognitive services. Instead of the traditional Screen/Keyboard/

Mouse combination for user interaction with your ap-plication, it offers different ways of handling user in-put. Think about vision, speech and language – the new way of communicating with your devices – but also how to analyze and structure these kinds of user input. This session will give you an introduction on the Cog-nitive Services Platform – show how it can help your end-users – and with live coding examples you will experience how easy it is to start using this incredibly cool API.

Besuchen Sie auch folgende Session:

Page 24: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 24

DOSSIER Web Development

Bevor wir nun anfangen, einen Keep-Alive-Mecha-nismus oder den Reconnect selbst zu implementieren, existieren in der freien Wildbahn einige Bibliotheken, die uns diese Arbeit abnehmen und ein paar Schmankerl zu-sätzlich bieten. In der Node.js-Welt ist Socket. IO [1] eine sehr beliebte Bibliothek zur Umsetzung von Echtzeitda-tenübertragung. Die Implementierung ist sehr einfach, aber auch sehr mächtig. Mit Socket.IO kann man von einfachen Chats bis hin zum gemeinsamen Editieren von Dokumenten alles implementieren. Socket.IO abstra-hiert dabei das WebSocket-Protokoll und implementiert zusätzliche Features wie Keep-Alive zum Erkennen von Verbindungsabbrüchen mit automatischem Reconnect. Kann Socket.IO keine Verbindung über das WebSocket-Protokoll aufbauen, fällt es auf die Nutzung von HTTP Long Polling zurück. Das ist natürlich, wie eingangs be-schrieben, nicht mehr so effizient, aber wir müssen diesen Fallback nicht selbst implementieren, und unser Code funktioniert weiterhin.

Wie wäre es mit einem Plausch?Im Rahmen des Artikels wollen wir gemeinsam einen kleinen Chat entwickeln, in dem man in Echtzeit kom-munizieren kann. Dabei soll der Chat verschiedene Räu-me anbieten. Jeder Raum kann beliebig viele Chatter aufnehmen. Nachrichten, die in einem Raum geschickt werden, werden an alle im Raum befindlichen Teilneh-mer geschickt. Zudem soll dem Chatter eine Liste aller Personen im Raum angezeigt werden. Das Aussehen der Oberfläche ist in Abbildung 1 zu sehen. Das fertige Bei-spiel ist auf GitHub [2] zu finden.

Als Voraussetzung wird Node.js [3] in einer aktuellen Version benötigt. Zudem erzeugen wir einen Ordner chatSample, in dem wir alle Sources ablegen. In diesem Ordner öffnen wir nun auch eine Kommandozeile und geben folgenden Befehl ein:

npm init -y

Damit wird eine package.json-Datei erstellt, in der wir unsere Abhängigkeiten und Startskripte abspeichern.

Als Nächstes benötigen wir folgenden Befehl zum In-stallieren der benötigten Abhängigkeiten:

npm i --save socket.io [email protected] concurrently node-static nodemon

Folgende Abhängigkeiten werden installiert:

• socket.io beinhaltet die zuvor angesprochene Biblio-thek zur Abstraktion des WebSocket-Protokolls.

• bootstrap beinhaltet die nötigen CSS-Dateien vonBootstrap für das entwicklerhübsche Frontend.Wichtig ist hier die Angabe der Version @4.0.0­al­pha.3, sodass wir die aktuellste Version installieren.

• concurrently nutzen wir, um parallele Kommandozei-lenbefehle auszuführen.

• node­static beinhaltet einen kleinen Webserver zum Ausliefern von statischen Dateien. Mit ihm sehen wir das Frontend im Browser.

• nodemon dient zum Starten unserer Server inklusi-ve einem Live-Reloading-Mechanismus, sobald derServerquelltext sich ändert.

Der nächste Vorbereitungsschritt fügt in unserer pack­age.json-Datei ein neues Skript ein. Dazu öffnen wir die package.json-Datei und ersetzen den vorhandenen Ein-trag scripts mit Folgendem:

"scripts": { "start": "concurrently \"nodemon src/server/chatServer.js\" \"static

.\" \"open http://localhost:8080/src/client/index.html\""}

Abb. 1: Darstellung des Frontends – noch ohne Funktion

User Experience: Golden Rules and Common Problems of Web Views

Dino Esposito

You don’t have to be a UX designer to spot out some basic and common problems in the overall UX delivered by too many web

views out there. And if you’re a UX designer, chances are that you miss some key points especially in the area of mobile devices and overall performance. This talk is about seven golden rules of UX applied to web pages – including drop-down content, input of dates, picking lists, images. Concretely the talk discusses patterns and JavaScript frameworks (Image Engine, WURFL.js, typeahead.js, various Bootstrap and jQue-ry plugins) that can be helpful in your everyday fron-tend development. The talk is agnostic of any “large” development framework like Angular or React, so no one will be left behind!

Besuchen Sie auch folgende Session:

Page 25: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 25

DOSSIER Web Development

Wir nutzen concurrently, um drei Kommandozeilen-befehle gleichzeitig auszuführen (alternativ wären die drei Befehle in drei Kommandozeilen auszufüh-ren). Der erste Befehl startet nodemon mit dem Pa-rameter src/server/chatServer.js. Der Parameter ist die Datei, die von nodemon gestartet und überwacht werden soll. Jegliche Änderung dieser Datei führt zum Neustart des Node.js-Prozesses, sodass unsere Änderungen sofort zur Verfügung stehen. Das spart das Beenden und Neustarten der Kommandozeile. Da wir auf aufwendige Build-Prozesse verzichten, nutzen wir den zweiten Befehl, um einen Webserver mit node­static im gleichen Verzeichnis zu starten, in dem die package.json-Datei liegt.

Der dritte Befehl öffnet das Frontend im Browser. Da node­static per Standard einen Webserver auf Port 8080 öffnet und unser Frontend unter src/client/

index.html zu erreichen ist, ergibt sich der im Skript angezeigte URL http://localhost:8080/src/client/index.html.

Bevor wir das Skript starten können, müssen wir die benötigten Dateien anlegen. Das heißt, wir erstellen ei-nen Ordner src und darin einen Ordner client und einen Ordner server. In den Ordner client legen wir eine index.html-Datei und in den Ordner server eine chatServer.js-Datei. Die Dateien können ohne Inhalt angelegt werden, sodass wir sie gemeinsam mit Leben füllen können. Sind die Dateien angelegt, kann auf der Kommandozeile der folgende Befehl ausgeführt werden:

npm start

Damit wird nun der Server gestartet und das Frontend öffnet sich. Selbstverständlich wird weder im Server et-

Listing 3: „index.html“-Datei

<!DOCTYPE html><html><head> <title>Sample Socket.io Chat</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1,

shrink-to-fit=no">

<link href="../../node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet"/>

</head><body> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="jumbotron"> <h1 class="display-3">Socket.io Sample</h1> <p class="lead">This is a sample application to show the usage of

Node.js and Socket.io based on a chat application.</p> <hr class="m-y-1"> <form class="form-inline" id="name-form"> <div class="form-group">

<label for="name" class="form-control-label">Please tell me your name:</label>

<input type="text" class="form-control" id="name" name="name" value="Bob">

</div> </form> </div> </div> </div> <div class="row"> <div class="col-md-3"> <form id="new-room-form" action="javascript:"> <div class="form-group">

<input type="text" class="form-control" placeholder="New room name..." id="new-room-name">

</div> </form>

<ul class="list-group" id="rooms">

</ul> </div> <div class="col-md-6"> <form id="add-message-form"> <div class="form-group"> <input type="text" class="form-control" id="message-text"

placeholder= "If you've joined a room, type in a message and press enter to send it...">

</div> </form>

<ul class="list-group" id="message-log"> <li class="list-group-item">You have to join a room in order to chat

with someone.</li> </ul> </div> <div class="col-md-3"> <h3>Chatters</h3> <ul class="list-group" id="chatters">

</ul> </div> </div> </div>

<script src="../../node_modules/socket.io-client/socket.io.js"></script> <script src="app.js"></script></body></html>

Page 26: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 26

DOSSIER Web Development

was passieren noch werden wir im Frontend etwas se-hen, da die Dateien leer sind. Aber sie dienen für unser Skript als Grundlage, mit der wir nun arbeiten können. Einzig und allein im Browser müssen wir manuell per F5 bei Änderungen einen Refresh durchführen. Der Server wird durch nodemon automatisch neu starten.

Vorne fangen wir an ...Der Inhalt der index.html-Datei ist in Listing 3 zu fin-den. Die wichtigsten Stellen sind einmal am Ende des <head>- und <body>-Bereichs. In <head> wird das CSS von Bootstrap geladen, während in <body> einmal die

Socket.IO-Bibliothek geladen wird und eine app.js-Da-tei, in der wir zugleich unseren Client implementieren werden. Daher legen wir nun im Ordner src/client eine neue Datei mit dem Namen app.js an. Dazwischen ist ein HTML-Template zur Definition des Frontends ge-mäß der Abbildung 1, das wir nun auch sehen, wenn wir den Browser refreshen.

... und wechseln direkt nach hinten ...Schauen wir uns zunächst die Implementierung vom Server an. Dazu öffnen wir die chatServer.js und fügen den Inhalt aus Listing 4 ein.

Zu Beginn laden wir die Socket.IO-Bibliothek und de-finieren die neue Klasse ChatServer. Der Konstruktor der Klasse benötigt einen Port, auf dem der Server gestartet werden soll. Die einzige öffentliche Methode start() initi-alisiert Socket.IO und startet damit den Server.

Dazu übergeben wir der geladenen Bibliothek unseren Port, und schon haben wir einen WebSocket-Server mit-hilfe von Socket.IO am Laufen. Einfacher gehts nicht!

Socket.IO arbeitet eventbasiert. Daher harmoniert es sehr gut mit Node.js und kann die Stärken von non-blo-cking I/O ausnutzen. So ist ein einzelner Prozess in der Lage, mehrere tausend oder gar hunderttausend Verbin-dungen gleichzeitig zu verwalten.

Generell können wir bei Socket.IO mit der Metho-de on() auf ein Event reagieren. Das erste Event, das uns interessiert, ist das connection-Event. Es findet im-mer dann statt, wenn ein neuer Client sich verbindet. Im Callback erhalten wir den Client (Socket), der sich verbindet und auf dem wir arbeiten können. Als ersten Schritt initialisieren wir den Raum und den Namen des

Listing 5: Die Methode „_joinRoom()“

_joinRoom(socket, roomName) { if (socket.room) { socket.leave(socket.room, () => { this._io.in(socket.room).emit('chatter-left', socket.name); this._internalJoinRoom(socket, roomName); });

return; }

this._internalJoinRoom(socket, roomName)}

_internalJoinRoom(socket, roomName) { socket.join(roomName, () => { socket.room = roomName; this._io.in(socket.room).emit('chatter-joined', socket.name); this._io.sockets.emit('rooms', this._getRooms()); this._sendAllChattersInRoom(socket.room); });}

Listing 4: „chatServer.js“

'use strict';

const socketIo = require('socket.io');

class ChatServer { constructor(port) { this._port = port; }

start() { this._initializeSocketIo();

console.log('Server is up and running'); }

_initializeSocketIo() { this._io = socketIo(this._port);

this._io.on('connection', socket => { socket.room = ''; socket.name = '';

socket.emit('rooms', this._getRooms());

socket.on('join-room', msg => this._joinRoom(socket, msg)); socket.on('change-name', name => { socket.name = name; this._sendAllChattersInRoom(socket.room); });

socket.on('message', message => this._ io.in(socket.room).emit('message', { from: socket.name, message: message })); }); }}

const server = new ChatServer(8081);server.start();

Page 27: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 27

DOSSIER Web Development

Sockets als leeren String. Diese Daten werden wir im Anschluss vom Client erhalten.

Als Nächstes können wir mit socket.emit() Daten an genau diesen einen Client übermitteln. Der erste Parameter ist ein Schlüssel zur Identifikation der Da-ten. Dieser Schlüssel wird auf der Clientseite zu einem Event, das wir wieder per on()-Methode fangen kön-nen. Der zweite Parameter sind die eigentlichen Daten. Als Erstes wollen wir also dem Client alle vorhandenen Räume übermitteln. Die Methode _getRooms() (und

weitere, bisher nicht definierte Methoden) werden wir im Anschluss entwickeln.

Anschließend implementieren wir mehrere socket.on(), um auf Daten zu reagieren, die uns vom Client ge-schickt wurden. Aufgepasst: Nutzen wir on() oder emit() auf der socket-Ebene, horchen bzw. kommunizieren wir auch nur genau mit diesem einen Client. Nutzen wir die Methoden stattdessen auf this._io-Ebene, horchen wir auf alle Clients oder senden allen Clients unsere Daten.

Die socket.on()-Methode lauscht auf das Event join­room, das empfangen wird, wenn der Client den Raum wechselt. Das Event change­name tritt ein, wenn der Client seinen Benutzernamen wechselt. Dies vermer-ken wir in unserem Socket und senden die Namens-änderung an alle anderen Clients. Das dritte Event ist das message-Event. Es tritt ein, wenn der Client eine Nachricht schickt, die im Chatraum dargestellt wer-den soll. Erhalten wir dies am Server, möchten wir diese Nachricht an alle Clients im gleichen Raum schi-cken.

Socket.IO kennt das Konzept von Räumen und bietet daher die Methode in() an, um eine Nachricht nur an Clients in einem bestimmten Raum zu schicken. Das ist natürlich der Raum, in dem der Client die Nachricht geschrieben hat. Per emit() schicken wir die Nachricht zusammen mit dem Namen des Clients an alle im Raum befindlichen Chatter.

Listing 7: „app.js“

!function () {'use strict';

var controls, socket;

function initialize() { initializeControls(); initializeSocketIo(); }

function initializeControls() { controls = { name: document.querySelector('#name'), newRoomName: document.querySelector('#new-room-name'), chatters: document.querySelector('#chatters'), rooms: document.querySelector('#rooms'), messageLog: document.querySelector('#message-log'), messageText: document.querySelector('#message-text'),

nameForm: document.querySelector('#name-form'), newRoomForm: document.querySelector('#new-room-form'), addMessageForm: document.querySelector('#add-message-form') };

controls.nameForm.addEventListener('submit', function (event) { event.preventDefault();

changeName(); });

controls.newRoomForm.addEventListener('submit', function (event) { event.preventDefault(); createRoom(); });

controls.addMessageForm.addEventListener('submit', function (event) { event.preventDefault(); sendMessage(); }); }

function initializeSocketIo() { socket = io('http://localhost:8081');

socket.on('connect', changeName); socket.on('rooms', refreshRooms); socket.on('message', function (message) { addMessage(message.message, message.from); }); }

window.addEventListener('load', initialize);}();

Listing 6: Die Methode „_getRooms()“

_getRooms() { const rooms = {};

Object.keys(this._io.sockets.connected).forEach(socketId => { const socket = this._io.sockets.connected[socketId];

if (socket.room) { rooms[socket.room] = ''; } });

return Object.keys(rooms);}

Page 28: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 28

DOSSIER Web Development

Schauen wir uns als Nächstes die Methode _join­Room() an, die ausgeführt wird, wenn der Client den Raum wechselt. Der Inhalt ist in Listing 5 zu finden.

In _joinRoom() prüfen wir, ob der Socket sich in einem Raum befindet. Falls ja, nutzen wir die leave()-Methode, um den Raum zu verlassen. Der Callback wird ausgeführt, sobald der Client diesen Raum tat-sächlich verlassen hat. Das bedeutet, dass der Socket.

IO-Server dem Cli-ent das Verlassen des Raums mitteilt und dieser eine Bestäti-gung schickt. Ist das passiert, schicken wir an alle Clients im alten Raum eine Nachricht, dass ein Client diesen Raum verlassen hat. Danach können wir in den neuen Raum durch den Aufruf der Methode _internal­JoinRoom() wechseln.

Das Betreten des Raums passiert nun mit der join()-Me-thode. Auch wird der

Callback ausgeführt, sobald der Client den Raum tatsächlich betreten hat. Dann können wir den aktuellen Raum speichern und allen Clients im Raum eine Meldung schicken, dass der Client den Raum betreten hat. Zusätzlich senden wir allen Clients die aktuellen Räume und welche Chatter sich im aktuellen Raum befinden.

Als Letztes müssen wir noch die Methode _get­Rooms() implementieren. Ihre Implementierung ist in

Abb. 2: Das Beispiel in Aktion

Listing 8: Weitere Methoden in der „app.js“

function changeName() { var newName = controls.name.value; socket.emit('change-name', newName);}

function createRoom() { var newRoom = controls.newRoomName.value; changeRoom(newRoom);}

function changeRoom(roomName) { socket.emit('join-room', roomName); addMessage('You are now in room ' + roomName);}

function refreshRooms(allRooms) { while (controls.rooms.firstChild) {

controls.rooms.removeChild(controls.rooms.firstChild); }

allRooms.forEach(function (room) { var item = createListItem(room);

item.addEventListener('click', function () {

changeRoom(room); });

controls.rooms.appendChild(item); });}

function addMessage(message, from) { var text = from ? '<strong>' + from + ':</strong> ' + message : message; controls.messageLog.appendChild(createListItem(text));}

function sendMessage() { var message = controls.messageText.value; socket.emit('message', message); controls.messageText.value = '';}

function createListItem(text) { var item = document.createElement('li'); item.classList.add('list-group-item'); item.innerHTML = text;

return item;}

Page 29: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 29

DOSSIER Web Development

Listing 6 zu finden. Die Methode _sendAllChattersIn­Room() ist zur Übung dem Leser überlassen. Hier er-zeugen wir uns eine Hashmap mit allen Räumen, indem wir über die verbundenen Sockets iterieren. Per Object.keys() wandeln wir die Hashmap in ein String-Array um. Damit sind die Arbeiten am Server abgeschlossen. Schauen wir uns nun das Frontend an.

... und wieder ab nach vorneFür das Frontend öffnen wir die Datei src/clients/app.js. Der Inhalt ist in Listing 7 zu finden.

Zuerst initialisieren wir ein paar Controls, die auf unserer HTML-Seite liegen und essenziell für die Chat-nutzung sind. Zudem hängen wir an die drei Formulare der Input-Elemente einen submit Event Listener, sodass wir auf deren Eingaben reagieren können. Wie auch auf der Serverseite werden weitere Methoden im Anschluss implementiert.

Danach folgt die Initialisierung von Socket.IO. Durch die Einbindung der Bibliothek in der HTML-Datei steht uns die globale Funktion io() zur Verfü-gung. Als Parameter bekommt sie den Endpunkt von unserem Server.

Wie beim Server können wir mit der on()-Methode auf Events reagieren. Das Event connect wird von Socket.IO emittiert, sobald der Client mit dem Server verbunden ist. Ist dies geschehen, schicken wir über die Methode changeName() unseren Namen zum Server.

Das Event rooms haben wir auf Serverseite bereits emittiert. Empfangen wir es hier am Client, wollen wir die Darstellung der Räume aktualisieren.

Erhalten wir das Event message fügen wir die Nach-richt in unserer Nachrichtenliste ein, indem wir die Methode addMessage() nutzen und ihr die benötigten Parameter übergeben.

Zuletzt hängen wir uns an das load-Event vom Brow-ser, das gefeuert wird, wenn die Seite fertig geladen ist, sodass wir Zugriff auf alle Elemente haben.

Schauen wir uns noch die restlichen Methoden an, die wir in Listing 7 benutzen, aber noch nicht definiert haben. Ein Blick in Listing 8 zeigt deren Implementie-rungen.

Die Methoden createRoom(), changeName() und sendMessage() funktionieren alle nach dem gleichen Prinzip. Sie lesen aus dem <input>-Element den Wert aus und nutzen socket.emit() zum Versenden an den Server, worauf im Server die passende on()-Methode aktiv wird.

Die Methoden refreshRooms() und addMessage() sind der Gegenpart zum Empfangen der Servernach-richten und dienen zum Aktualisieren der Räume und Empfangen von Nachrichten.

Damit sind wir tatsächlich mit der Implementierung schon fertig und haben einen vollständigen Chat, der mit WebSocket in Echtzeit kommuniziert und Räume unterstützt. Abbildung 2 zeigt, wie der Chat in Benut-zung und vollständiger Implementierung aussieht.

Zur Übung sind dem Leser die Implementierungen

des Empfangs der Events chatter­left, chatter­joined und refresh­chatters überlassen. Ein Blick in das Git-Hub Repository bietet eine Hilfestellung bei der Im-plementierung.

FazitSchaut man sich den Code an, der für die Kommu-nikation zuständig ist, sieht man, dass erstaunlich wenig dafür nötig war. Viel Code drumherum dient zum Aktualisieren der Oberfläche oder Helfermetho-den zum Sammeln von Daten. Socket.IO bietet eine sehr einfache Abstraktion des WebSocket-Protokolls, bleibt aber dennoch sehr mächtig und unterstützt uns mit sinnvollen Keep-Alive-Mechanismen mit automa-tischem Reconnect. Es obliegt vielmehr der kreativen Nutzung, um interessante Echtzeitszenarien umzuset-zen. Übrigens, es stehen auch Clientbibliotheken für C# zur Verfügung [4], um mit Socket.IO zu kommu-nizieren. Viel Spaß beim Implementieren von Echt-zeitszenarien!

Seitdem mit HTML5 und JavaScript moderne Cross-Plattform-Lösungen entwickelt werden können, begeistert sich Manuel Rauber für die Umsetzung großer Applikationen auf mobilen End-geräten aller Art. Als Software Developer bei der Thinktecture AG unterstützt er die Entwicklung mobiler Cross-Plattform-Lösungen

mit Angular 2, Cordova, Electron und Node.js oder .NET im Backend.

Links & Literatur

[1] http://socket.io/

[2] https://github.com/thinktecture/windows-developer-nodejs-socketio

[3] https://nodejs.org

[4] https://github.com/Quobject/SocketIoClientDotNet

ASP.NET-Core- und MVC-Sicherheit – eine Übersicht

Dominick Baier

ASP.NET Core ist Microsofts nagelneue Cross-Plattform-Runtime zum Entwickeln von serverseitigen .NET-Anwendungen.

MVC Core ist das dazugehörige Framework für Web-anwendungen und APIs. Entsprechend neu sind auch die Konzepte für das Absichern von Anwen-dungen: Authentifizierung, Autorisierung, Daten-schutz – es gibt eine Menge zu lernen.

Besuchen Sie auch folgende Session:

Page 30: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

von Thomas Claudius Huber

Microsoft hat mit der „Cloud-first and Mobile-first“-Strategie richtig an Fahrt aufgenommen. Was heute mit Microsoft Azure für jeden Entwickler in einfachen Schritten möglich ist, war vor ein paar Jahren noch nahezu undenkbar. So lässt sich mit etwas Know-how bereits ein hochskalierendes Backend zum Verarbeiten von Millionen von Events konfigurieren. Wie das geht, wird in diesem Artikel beschrieben. Los geht’s mit einem Blick auf das „Big Picture“.

Das „Big Picture“Die in diesem Artikel erstellte Architektur ist in Abbil-dung 1 dargestellt: Endgeräte senden Events an einen Azure Event Hub. Ein Stream Analytics Job liest den Event Stream aus dem Event Hub aus und aggregiert gegebenenfalls die Daten mit einer SQL-ähnlichen Ab-fragesprache. Der Stream Analytics Job kann die Daten in verschiedene Outputs schieben – in eine Azure-SQL-DB, in einen Table Storage, in einen weiteren Event Hub usw. In diesem Artikel ist der Output des Stream Ana-lytics Jobs, wie in Abbildung 1 dargestellt, Power BI. In Power BI lässt sich ein Live-Dashboard erstellen, das die aktuellen Daten nahezu in Echtzeit darstellt.

In Abbildung 1 sind die Event-Produzenten Raspberry Pis. Doch das sendende Gerät ist beliebig: Es kann ein Kühlschrank, ein Toaster, ein Windrad, ein Motor, ein Smartphone oder beispielsweise auch ein ganz normaler PC sein. Letzten Endes ist es einfach ein Stück Software,

das auf irgendeiner Art von Gerät oder sogar ebenfalls als Job in der Cloud läuft. Und diese Software sendet Events an den Event Hub.

Szenarien für eine solche Event-getriebene Archi-tektur gibt es viele. So könnte eine gewöhnliche An-wendung Telemetriedaten an einen Event Hub senden. Die Telemetriedaten ermöglichen Informationen über die Nutzung der Anwendung. Ebenso ist es denkbar, dass Geräte mit Sensoren aktuelle Sensordaten an den Event Hub liefern. Beispielsweise könnten Windräder die aktuell produzierten Kilowatt pro Stunde an den Energiedienstleister in Echtzeit überliefern. Es gibt kei-ne Grenzen.

In diesem Artikel werden keine Raspberry Pis wie in Abbildung 1 eingesetzt, da dies den Umfang des Arti-kels hinsichtlich Hardwareintegration sprengen würde. Stattdessen wird eine kleine WPF-Applikation [1] ver-wendet, die den Event Hub mit Events füttert. Damit lässt sich auf einfache Weise das ganze Szenario vom Client über den Event Hub über den Stream Analytics Job zum Power-BI-Live-Dashboard durchspielen. Mehr zu der verwendeten WPF-Applikation als Event-Sender erfahren Sie nach dem Erstellen des Event Hubs.

Azure Event HubsÜber das Azure-Portal [2] lässt sich auf einfache Wei-se ein Event Hub erstellen. Nach dem Einloggen in das Azure-Portal wird über das + eine neue Ressource ange-legt. Unter der Kategorie Internet of Things (IoT) befin-det sich das Element „Event Hubs“. Damit lässt sich ein

Vom Gerät zum Live-Dashboard

Mit Azure zur IoT-Infrastruktur Mit den Azure Event Hubs hat Microsoft einen Dienst im Portfolio, mit dem sich Millionen von Events per Sekunde erfassen lassen. Anwendungsszenarien gibt es viele: Beispiels-weise könnte eine gewöhnliche Anwendung Telemetriedaten an den Event Hub senden. Ebenso ist es denkbar, dass Windkrafträder, Kühlschränke oder Raspberry Pis Sensorda-ten an einen Event Hub übermitteln – typischerweise das, was man sich unter IoT vorstellt. Mit einem Stream Analytics Job lässt sich der Event Stream aus dem Event Hub auslesen und für die Weiterverarbeitung mit einer SQL-ähnlichen Syntax aggregieren. Die aggre-gierten Daten lassen sich dann bspw. in Echtzeit in einem Live-Dashboard in Power BI an-zeigen. Genau dieses Szenario wird in diesem Artikel beschrieben – von der Anwendung über einen Event Hub über Stream Analytics hin zum Power-BI-Live-Dashboard.

www.basta.net 35

DOSSIER Web Development

Page 31: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

neuer Name space für Event Hubs erstellen. Ein Event Hub als solcher gehört immer zu einem Event Hub Na-mespace. Dieser wiederum kann bis zu zehn Event Hubs enthalten. Wird im Azure-Portal auf das Element Event Hubs geklickt, öffnet sich der „Blade“ – so werden die einzelnen, horizontal nebeneinander geöffneten „Fens-ter“ im Azure-Portal genannt – zum Erstellen des Event Hub Namespace. Nach der Eingabe eines Namespace-Namens  – in unserem Beispiel „WinDeveloper-EH-NS“ – und der Angabe einer Ressourcengruppe lässt sich der Event Hub Namespace durch einen Klick auf den Cre ate-Button anlegen (mehr Infos zu Ressourcen-gruppen finden sich im zugehörigen Infokasten).

Den Event Hub erstellenUm den eigentlichen Event Hub zu erstellen, wird im Azure-Portal über die neu angelegte Ressourcengruppe „WinDe-veloperResGroup“ zum Event Hub Namespace navigiert. Auf dem Blade für den Event Hub Namespace befindet sich ein Button + Event Hub. Ein Klick darauf öffnet ein weite-res Blade zum Erstellen eines Event Hubs. In Abbildung 2 wurde in diesem Blade bereits der Name für den zu erstel-lenden Event Hub eingegeben. Ein Klick auf den Create-Button genügt, und der Event Hub ist angelegt.

Neben dem Namen lässt sich auch die in Abbildung 2 zu sehende Message Retention einstellen, die festlegt, wie lange die Events im Event Hub verfügbar sind. Der

Defaultwert ist ein Tag, der Maximalwert beträgt sieben Tage. Neben der Message Retention lassen sich auch die Partitionen einstellen, die für den Event Hub verwendet werden. Dabei kann eine Zahl zwischen 2 und 32 an-gegeben werden. Die Partitionen eines Event Hubs sind wichtig für die Skalierung. Obwohl im Portal der ma-ximal einstellbare Wert 32 Partitionen sind, lassen sich durch Supporttickets auch Event Hubs mit 1 024 oder mehr Partitionen erstellen. Doch was genau hat es mit der Skalierung und den Partitionen auf sich? Um diese Frage zu klären, muss eine wichtige Größe beachtet werden, die so genannte Throughput Unit (Durchsatzeinheit).

Abb. 1: Klassische Architektur mit Event Hub, Stream Analytics und Power BI

Über Ressourcengruppen

Mit dem neuen Azure-Portal [2] hat Microsoft das Konzept der Ressourcengruppen eingeführt, das es unter dem alten Azure-Portal [3] noch nicht gab. Ein Azure-Dienst, wie ein Event Hub Namespace oder ein Stream Analytics Job, wird immer einer Ressourcengruppe zugeordnet. Die Dienste einer Ressourcengruppe haben gemeinsame Berechtigungen und einen gemeinsamen Lebenszyklus. So lässt sich z. B. nicht nur ein einzelner Dienst entfernen, sondern bei Bedarf auch die ganze Ressourcengruppe. Zum Testen und Ausprobie-ren von Azure-Diensten empfiehlt es sich somit, eine neue Ressourcengruppe als „Spielplatz“ zu erstellen, wie dies auch beim Anlegen des Event Hub Namespace gemacht wird. Dann lässt sich nach dem Testen und Ausprobieren die komplette Ressourcengruppe mit einem Schwung entfernen.

Abb. 2: Einen Event Hub erstellen

Enterprise-Apps mit Xamarin und den Azure App Services entwickeln

Jörg Neumann

Die Anforderungen an eine Business-App steigen stetig. Sie soll auf verschiedenen Plattformen laufen, auch von unterwegs Zu-

griff auf Unternehmensdaten bieten und natürlich off-linefähig sein. Für solche Aufgaben bieten die Azure App Services elegante Lösungen. Sie ermöglichen eine einfache Bereitstellung von Backend-Services, die Anbindung an unterschiedliche Datenquellen und eine Integration ins Unternehmensnetz. Zudem wer-den verschiedene Varianten der Authentifizierung und der Versand von Push-Benachrichtungen geboten. Jörg Neumann zeigt Ihnen, wie Sie mit Xamarin und den Azure App Services Enterprise-taugliche Apps entwickeln und betreiben können.

Besuchen Sie auch folgende Session:

www.basta.net 36

DOSSIER Web Development

Page 32: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Die Throughput UnitAuf dem Blade des Event Hub Namespace lässt sich unter der Einstellung Scale die Anzahl der Throughput Units angeben – das ist der Durchsatz, den die Event Hubs leisten können. Der Defaultwert ist 1, der im Por-tal verfügbare Maximalwert 20. Eine Throughput Unit bedeutet, dass sich 1 MB und maximal 1 000 Events pro Sekunde in den Event Hub schreiben lassen. Zum Lesen aus dem Event Hub unterstützt eine Throughput Unit 2 MB pro Sekunde. Die eingestellten Throughput Units sind also von enormer Bedeutung für die Skalierung. Müssen mehr als 1 000 Events pro Sekunde verarbeitet werden, so muss die Anzahl der Throughput Units ent-sprechend erhöht werden.

Und jetzt kommt das Zusammenspiel der Through-put Units und der Partitionen eines Event Hubs zum Vorschein. Die Partitionen beschreiben, wie die Daten physikalisch abgelegt und verteilt sind. Eine Einstellung von zwanzig Throughput Units und lediglich zwei Par-titionen auf einen Event Hub wird nicht funktionieren: Das physikalische Schreiben in einen Event Hub mit nur zwei Partitionen bildet einen Flaschenhals bei zwanzig Throughput Units. Daher gilt die Grundsatzformel, dass die Anzahl der gewählten Partitionen in einem Event Hub größer oder zumindest gleich groß wie die Anzahl der Throughput Units des Event Hub Namespace ist.

Die Kosten des Event HubsDie Throughput Units spielen neben der Skalierung die zentrale Rolle für die Kosten eines Event Hubs. Die Kos-ten setzen sich prinzipiell aus zwei Größen zusammen, die im Basisprofil wie folgt aussehen:

1. Pro Million eingegangener Events sind 0,0236 Euro fällig

2. Pro Throughput Unit sind 0,0126 Euro pro Stunde fällig, was in etwa 9 Euro pro Monat entspricht

Jetzt lässt sich leicht ausrechnen, dass sich mit einer Throughput Unit, die ja 1 000 Events pro Sekunde ver-arbeiten kann, pro Tag theoretisch 86 400 000 Events verarbeiten lassen (1000 * 60 * 60 * 24). Fallen diese Events an, sind die Kosten wie folgt: Der Tagespreis für eine Throughput Unit liegt bei 24 * 0,0126  Euro, was 0,3024 Euro entspricht. Dazu kommen die Kosten für eine Million Events von 0,0126 Euro. Bei 86 400 000 Events pro Tag wären die Kosten somit 86,4 * 0,0126  Euro, was 1,08864  Euro pro Tag ergibt. Die gesamten Ta-

geskosten sind folglich 1,08864 Euro + 0,3024 Euro = 1,3910 Euro. Bei 31 Tagen laufen somit Monatskosten von 1,3910 Euro * 31  =  43,1224 Euro auf. Eine erstaun-lich kleine Zahl, wenn berücksichtigt wird, dass sich da-mit 86 Millionen Events pro Tag verarbeiten lassen.

Event Hub vs. IoT HubWer sich bereits mit Azure beschäftigt hat, der weiß, dass es neben dem Event Hub auch einen IoT Hub gibt. Beide scheinen ähnliche Funktionalität zu bieten, denn auch der IoT Hub unterstützt Events wie der Event Hub. Doch welcher Hub ist wann zu verwenden?

Es gibt einige Unterschiede zwischen einem Event Hub und einem IoT Hub  [4] (Kasten „IoT Hub und Event Hub zusammen“). Die zentralen Unterschiede sind in Tabelle 1 dargestellt.

In der offiziellen Azure-Dokumentation [5] sind noch weitere Unterschiede zwischen IoT Hub und Event Hub beschrieben. Ein sehr spannender Unterschied sind je-doch die Kosten. Im vorigen Abschnitt wurde berech-net, dass mit einem Event Hub 86 400 000 eingehende Events pro Tag für knapp 45 Euro im Monat möglich sind. Um mit dem IoT Hub diese 86 400 000 eingehen-den Events pro Tag zu verarbeiten, ist die höchste Edi-tion des IoT Hubs namens S3 erforderlich. Doch diese S3-Edition kostet sage und schreibe 4 216,50 Euro pro Monat. Folglich sollte dieser Kostenpunkt in einer Ar-chitekturentscheidung nicht unberücksichtigt bleiben. In diesem Artikel wird der Event Hub verwendet, der für eine reine Device-to-Cloud-Kommunikation eine feine Sache ist.

Daten in einen Event Hub schreibenUm Daten in einen Event Hub zu schreiben, gibt es zwei Möglichkeiten:

1. Via Advanced Message Queuing Protocol (AQMP)2. Via HTTP und REST-Schnittstelle

Event Hub IoT Hub

Kommunikation Device zur Cloud Device zur CloudCloud zum Device

Security Event-Hub-weite Shared Access Policy Security auf Device-Ebene konfigurierbar

Gerätemanagement Keine Hat eine Registrierung für die Geräte, mit der auch die Securityeinstellungen auf Geräteebene einhergehen

Skalierung 5 000 gleichzeitige Verbindungen Millionen von verbundenen Geräten

Tabelle 1: Zentrale Unterschiede zwischen Event Hub und IoT Hub

IoT Hub und Event Hub zusammen

In der Praxis kommen IoT Hub und Event Hub auch oft zusammen zum Einsatz. Dabei werden ein oder mehrere IoT Hubs zum Anbinden der Geräte an Azure verwendet. Die IoT Hubs selbst schreiben ihre Events in einen oder mehrere nachgelagerte Event Hubs, die wiederum ggfs. noch weitere Events aus anderen Quellen empfangen.

www.basta.net 37

DOSSIER Web Development

Page 33: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Für das Protokoll AQMP gibt es verschiedene Libraries, die sich nutzen lassen. Existiert für eine Plattform keine AQMP-Library, kann mit einem gewöhnlichen HTTP-Client ein Event über die REST-Schnittstelle des Event Hubs veröffentlicht werden. Allerdings ist das Security-Token entsprechend im HTTP-Header mitzugeben, was eventuell nicht ganz trivial ist. Auf GitHub [6] gibt es den EventHub.RestClientGenerator, der den HTTP-Cli-ent-Code für eine UWP-App erzeugt. Dieser Code lässt sich leicht auf beliebige andere Plattformen anpassen, da er lediglich einen reinen HTTP-POST-Aufruf enthält, um ein Event in Form eines JSON-Strings im Event Hub zu veröffentlichen.

Wird eine .NET-Anwendung zum Schreiben in einen Event Hub genutzt, ist das Verwenden der REST-Schnitt-stelle nicht notwendig. Statt der REST-Schnittstelle emp-fiehlt sich das NuGet-Paket WindowsAzure.ServiceBus. Dieses Paket enthält die EventHubClient-Klasse, die das Schreiben von Events in den Event Hub via AQMP ermöglicht. Mit der statischen Methode CreateFrom-ConnectionString wird eine EventHubClient-Instanz erstellt, was wie folgt aussieht:

var client = EventHubClient.CreateFromConnectionString(connectionString);

Doch jetzt stellt man sich als Entwickler die berechtig-te Frage, wo es denn den Connection String gibt. Dazu wechselt man im Azure-Portal zum Event Hub und klickt unter den Einstellungen auf Shared Access Poli-cies. Über den + Add-Button lässt sich eine neue Shared Access Policy einrichten. Dabei werden ein Name ver-geben und die entsprechenden Claims für die Policy ausgewählt. Es gibt die selbsterklärenden Claims „Ver-walten“, „Senden“, „Lesen“. In Folge wird eine Policy erstellt, die das Senden und Lesen, jedoch keine Ände-rungen (Manage) am Event Hub erlaubt.

Nachdem die Shared Access Policy angelegt wurde, lässt sich diese über den Event Hub im Azure-Portal aus-wählen. Unter der Shared Access Policy wird dann der für diese Policy geltende Connection String angezeigt. Und genau dieser Connection String lässt sich für die statische CreateFromConnectionString-Methode der EventHub Client-Klasse nutzen. Der Connection String sieht im für diesen Artikel generierten Beispiel wie folgt aus:

Endpoint=sb://windeveloper-eh-ns.servicebus.windows.net/ ;SharedAccessKeyName=SendListenPolicy;SharedAccessKey=0v4Gn+ UtMWXOw4+4qGed7AVeEtG726U0G/wuC1S6Eyk=;EntityPath= windeveloper-eh01

Wie zu sehen ist, enthält der Connection String den Namespace als Endpoint. Der eigentliche Event Hub wird mit dem EntityPath-Attribut angegeben. Mit dem Connection String und der EventHubClient-Klasse las-sen sich jetzt Events in den Event Hub schreiben. Dafür wird – wie zu Beginn des Artikels erwähnt – eine kleine WPF-Anwendung genutzt.

Die SenderanwendungZum Schreiben von Daten in den Event Hub wird an die-ser Stelle eine kleine WPF-Anwendung genutzt, die auf GitHub [1] mitsamt Quellcode verfügbar ist. Die Anwen-dung besitzt eine SensorData-Klasse (Listing 1). Instanzen dieser SensorData-Klasse werden von der Anwendung als Event an einen Event Hub gesendet. Wie Listing 1 zeigt, hat eine SensorData-Instanz die Eigenschaften Device-Name, ReadTime, SensorType und Value.

Wird die WPF-Anwendung gestartet, lässt sich da-rin der Event Hub Connection String einfügen und die Checkbox „Is sending data enabled“ anklicken. Damit legt die App los. Sie sendet alle zwei Sekunden zwei Events an den Event Hub, eines für den Sensortyp Temp (Temperatur) und eines für den Sensortyp Hum (Luftfeuchtigkeit). Über die Slider lassen sich die an den Event Hub gesendeten Werte für Temperatur und Luft-feuchtigkeit steuern.

Ein Blick auf den Code der WPF-Anwendung zeigt die Methode, die zum Senden eines Events genutzt wird (Listing 2). Zuerst wird mit dem Connection String und der statischen CreateFromConnectionString-Methode eine EventHubClient-Instanz erzeugt. Anschließend wird die SensorData-Instanz mit der JsonConvert-Klasse und deren statischer SerializeObject-Methode in einen JSON-String serialisiert. Dieser JSON-String wird UTF8-codiert dem Konstruktor der EventData-Klasse übergeben. Die EventData-Klasse stammt wie auch die EventHubClient-Klasse aus dem NuGet-Paket Windows Azure.Storage. In der letzten Zeile wird in Listing 2 auf der EventHubClient-Instanz die SendA-sync-Methode aufgerufen, die als Parameter die Event-Data-Instanz bekommt. Damit werden die Event-Daten an den Event Hub gesendet.

Hinweis

Anstatt auf dem Event Hub eine Shared Access Policy anzu-legen, gibt es auch die Möglichkeit, eine auf dem Event Hub Namespace erstellte Shared Access Policy zu nutzen. Diese kann dann über alle Event Hubs im Event Hub Namespace verwendet werden. Als Entwickler muss man abwägen, ob man den Zugriff auf Ebene der Event Hubs oder eben auf Ebene des Event Hub Namespace regeln möchte.

Listing 1

public class SensorData{ public string DeviceName { get; set; } public DateTime ReadTime { get; set; } public string SensorType { get; set; } public double Value { get; set; }}

www.basta.net 38

DOSSIER Web Development

Page 34: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

ZwischenstandSoweit ist der Azure Event Hub aufgesetzt. Ebenso ist eine kleine WPF-Anwendung vorhanden, mit der sich Sensordaten an den Event Hub senden lassen. Die ge-sendeten Werte für Temperatur und Luftfeuchtigkeit können dabei über die beiden Slider in der Oberfläche der WPF-Anwendung eingestellt werden. Im nächsten Schritt werden die Events aus dem Event Hub mit einem Stream Analytics Job ausgelesen und in ein Power-BI-Live-Dashboard gepusht.

Stream-Analytics-GrundlagenStream Analytics erlaubt das Verarbeiten eines Event Streams in Echtzeit (Abb. 3). Der Event Hub kann bei Stream Analytics als Input definiert werden. Ne-ben dem Event Hub kann auch ein IoT Hub und/oder ein Azure Blob Storage als Input definiert werden. Mit einer SQL-Variante lassen sich die Daten aus den verschiedenen Inputs im Stream Analytics Job aggregieren. In dem Beispiel dieses Artikels gibt es für den Stream Analytics Job lediglich einen Input, den Event

Hub. Der Output eines Stream Analytics Jobs kann ebenfalls vielfältig sein. In diesem Artikel ist es ein Pow-er-BI-Live-Dashboard, um die erhaltenen Daten nahezu in Echtzeit anzuzeigen.

Stream Analytics aufsetzenIm Azure-Portal [2] wird wie auch zum Anlegen eines Event Hub Namespace über das + eine neue Ressource angelegt. Unter der Kategorie Internet of Things befindet sich das Element „Stream Analytics Job“. Wird darauf geklickt, öffnet sich Blade zum Erstellen eines Stream Analytics Jobs. Nach der Eingabe eines Namens wird die bereits existierende Ressourcengruppe WinDeveloper-ResGroup ausgewählt und der Create-Button geklickt. Ist der Job erstellt, lässt er sich über die Ressourcengrup-pe im Azure-Portal auswählen, um die Inputs, die Abfra-ge und die Outputs zu definieren (Abb. 4).

Den Input definierenEin Klick auf „Input“ (Abb. 4) erlaubt das Definieren des bereits erstellten Event Hubs als Input für den Stream Analytics Job. Dazu muss ein Input-Alias angegeben werden, der später in der SQL-Abfrage des Stream Ana-lytics Jobs zum Referenzieren des Inputs genutzt wird.

Tipp

Im GitHub-Projekt mit der WPF-Anwendung [1] ist neben einer Sender-Applikation auch eine Receiver-Applikation enthalten. Die Receiver-Applikation liest die Events aus einem Event Hub aus. Somit lassen sich Sender und Receiver gleichzeitig starten, um den Fluss der Events sehen zu können.

Hinweis

Wie die an den Event Hub gesendeten JSON-Daten aussehen, ist frei definierbar. Die Struktur muss beim späteren Weiter-verarbeiten jedoch bekannt sein, wenn bspw. in der Stream-Analytics-Abfrage bestimmte Attribute benötigt werden.

Abb. 3: Azure Stream Analytics

Abb. 4: Die Ansicht des Stream Analytics Jobs im Azure-Portal

Listing 2

public async Task<...> PublishAsync(string connectionString, SensorData sensorData){ try { var client = EventHubClient.CreateFromConnectionString( connectionString);

string sensorDataJson = JsonConvert.SerializeObject(sensorData);

var eventData = new EventData(Encoding.UTF8.GetBytes(sensorDataJson));

await client.SendAsync(eventData); ... } ...}

www.basta.net 39

DOSSIER Web Development

Page 35: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Neben dem Input-Alias lässt sich auch das Seriali-sierungsformat wählen, in dem die Events im Event Hub vorliegen. Der Defaultwert ist JSON, was im hier verwendeten Beispiel korrekt ist, da die WPF-Anwen-dung die Sensordaten im JSON-Format an den Event Hub sendet. Weitere mögliche Formate sind CSV oder Avro.

Ein weiterer, sehr wichtiger Punkt ist die Consumer Group. Ein Event Hub hat per Default eine Consumer Group mit dem Namen $Default. Wird keine Consumer Group angegeben, greift der Stream Analytics Job unter der $Default-Consumer-Group auf den Event Hub zu. Consumer Groups sind Teil des Event Hubs und wich-tig für das Lesen/Konsumieren des Event Streams. Eine Consumer Group erlaubt fünf gleichzeitige Verbindun-gen zum Lesen von Events aus dem Event Hub. In der in diesem Artikel verwendeten Architektur greift lediglich der Stream Analytics Job auf den Event Hub zu, was somit exakt eine Verbindung darstellt. Wären es jedoch beispielsweise zehn Stream Analytics Jobs, müssten auf dem Event Hub weitere Consumer Groups erstellt werden. Microsoft empfiehlt, pro lesendem Dienst eine Consumer Group auf dem Event Hub zu erstellen. Ein Event Hub unterstützt via Azure-Portal maximal zwan-zig Consumer Groups. In der in diesem Artikel verwen-deten Architektur wird die $Default-Consumer-Group zum Lesen aus dem Event Hub genutzt, da der Stream Analytics Job der einzige Konsument des Event Streams ist.

Den Output definierenWird unter dem Blade für den Stream Analytics Job auf „Output“ geklickt (Abb. 4), lässt sich ein Output definie-ren, wie bspw. eine Azure SQL DB, ein Azure Table Sto-rage, ein weiterer Event Hub und viele andere nützliche Speichermöglichkeiten. In diesem Artikel wird Power BI als Output gewählt. Damit Power BI als Output erstellt werden kann, muss eine Autorisierung mit einem gülti-gen Power BI-Account erfolgen. Unter [7] sollte man sich somit entsprechend registrieren, um die notwendigen Be-nutzerdaten im Output-Blade zur Autorisierung eingeben zu können. Ist die Autorisierung erfolgt, lassen sich neben dem Output-Alias – wieder wichtig für die SQL-Abfrage – Data Set und Table Name für Power BI benennen. Unter diesen Namen werden die Daten aus dem Stream Ana-lytics Job in Power BI erscheinen. Wie üblich ein Klick auf „Create“, und der Output ist fertig definiert. Mit dem jetzt erstellten Input und dem Output kann es nun mit der Abfrage des Stream Analytics Jobs weitergehen.

Die Abfrage erstellenMit einem Klick auf „Query“ unter dem Blade für den Stream Analytics Job (Abb. 4) wird die Abfrage definiert. Die Abfrage lässt sich dabei direkt im Browser in einem einfachen Editor erstellen. Sie ist notwendig, um die Verbindung zwischen dem Input und dem Output eines Stream Analytics Jobs herzustellen. Dazu kommen die in den vorigen Abschnitten definierten Aliase für den Input und den Output zum Einsatz, was in Abbildung 5 zu se-hen ist.

Ein Stream Analytics Job wird üblicherweise verwen-det, um die massive Menge an Event-Daten – es könnten Millionen sein – zu reduzieren. Dazu kommt klassisches Group By zum Einsatz, wie man es von SQL kennt. Doch im Stream Analytics Job wurde das SQL um ein paar Schlüsselfunktionen erweitert: So lassen sich beispiels-weise mit einem so genannten Tumbling Window Events zeitbasiert in Zehnsekundenblöcke gruppieren und aggre-gieren. In diesem Artikel belassen wir es jedoch bei der ein-fachen Abfrage aus Abbildung 5, die die Daten aus dem Event Hub direkt an Power BI weitergibt, ohne die Daten zu reduzieren.

Nachdem die Abfrage gespeichert wurde, ist ein letz-ter Schritt notwendig: Der Stream Analytics Job muss gestartet werden. Auf dem Blade für den Stream Ana-lytics Job gibt es die Buttons Start und Stop. Ein Klick auf Start genügt, und der Job fährt hoch. Sobald der Job gestartet ist, verarbeitet er den Event Stream aus dem Event Hub und pusht die Daten in Power BI, wo sie sich für ein Live-Dashboard verwenden lassen.

Die Daten in Power BI anzeigenIst der Stream Analytics Job gestartet und werden aus der WPF-Anwendung Events an den Event Hub gesen-det, so tauchen diese Daten jetzt im Power BI auf. Um dies zu sehen, genügt ein Log-in unter [7]. In der Navi-gation des Power-BI-Portals auf der linken Seite befin-det sich unter Datasets der Punkt „Streaming datasets“.

Tipp

In Abbildung 5 ist oben ein TesT-Button zu sehen, der allerdings deaktiviert ist. Wird auf die drei Pünktchen „...“ hinter dem Input „WinDevEH01“ geklickt, erscheint ein kleines Menü, das unter anderem den Menüpunkt Upload sample daTa from file enthält. Damit lässt sich ein kleines Textfile hochladen, das die Events im JSON-Format enthält. Anschließend führt ein Klick auf den nach dem Hochladen der Daten aktiven TesT-Button die aktuelle Query gegen diese Testdaten aus und zeigt die Ergebnisse im Browser an. Das ist sehr effektiv, um Konstrukte wie das Tumbling Window zu testen.

Abb. 5: Die Abfrage des Stream Analytics Jobs

www.basta.net 40

DOSSIER Web Development

Page 36: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Ein Klick darauf zeigt eine Übersicht der Streaming Da-tasets an, diese Übersicht zeigt jetzt das Dataset WinDev-DS (Abb.  6). WinDev-DS ist der Name, der beim Definieren des Outputs für den Stream Ana-lytics Job angegeben wurde.

Mit diesen eingehenden Da-ten lässt sich in Power BI ein kleines Dashboard erstellen, das die Daten anzeigt. Dazu wird in der linken Navigation unter Dashboard ein neues Dashboard über den +-But-ton hinzugefügt. Nachdem ein Name eingegeben wurde, wird das Dashboard selektiert. Im oberen Menü gibt es einen Add Tile-Button, der einen kleinen Dialog öffnet, über den sich eine Kachel zum Anzeigen der Livedaten definiert lässt. Dazu hat der Dialog ganz unten den Punkt Real-time Data. Wird dieser Punkt ausgewählt, lässt sich anschlie-ßend das aus dem Stream Analytics Job stammende Data-set WinDev-DS selektieren. Als Visualisierungstyp kann ein Line Chart gewählt werden, und dabei sind folgende Daten anzugeben: Axis wird auf die ReadTime-Spalte der Daten gesetzt (Sen sorData-Klasse, Listing 1), Legend wird auf die Sensor Type-Spalte der Daten gesetzt, und unter Values wird die Value-Spalte der Sensordaten angegeben. Zu guter Letzt wird ein Titel für die Kachel definiert. Beim Erstellen wird die Kachel direkt auf dem Live-Dashboard angezeigt. Dabei sind in der Legende die beiden Sensoren „Temp“ und „Hum“ zu sehen. Wird jetzt in der WPF-Applikation – die die Events an den Event Hub sendet – ein Slider und somit der Wert eines Sensors verändert, ist dies unmittelbar im Power-BI-Live-Dashboard zu sehen.

FazitDieser Artikel hat einen kleinen Einblick in das Zusam-menspiel von Azure Event Hubs, Azure Stream Ana-lytics und Power BI gegeben. Auch wenn hier lediglich eine kleine WPF-Anwendung Daten an den Event Hub sendet, ist die gebaute Architektur bereits ausreichend, um 1 000 Events pro Sekunde zu verarbeiten. Diese Ska-lierung und die einfachen Konfigurationsmöglichkeiten machen Azure zu einem sehr mächtigen Werkzeug. Und das Beeindruckende ist, dass bis auf die Clientanwen-dung keine einzige Zeile Code geschrieben wurde, um die Livedaten ins Power BI zu pushen – abgesehen von der Abfrage im Stream Analytics Job.

Thomas Claudius Huber arbeitet als Principal Consultant bei der Trivadis AG. Er ist Microsoft MVP für Windows Development und Autor zahlreicher Bücher, Artikel und Pluralsight-Videos. Er spricht regelmäßig auf großen Entwicklerkonferenzen wie der BASTA!.

www.thomasclaudiushuber.com

Links & Literatur

[1] Testapplikationen zum Senden und Empfangen von Events an einen Azure Event Hub: https://github.com/thomasclaudiushuber/EventHub.Clients

[2] Das Microsoft-Azure-Portal: https://portal.azure.com

[3] Das alte Azure-Portal: https://manage.windowsazure.com

[4] IoT Hub vs. Event Hub: https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-compare-event-hubs

[5] Offizielle Azure-Dokumentation: https://docs.microsoft.com/de-de/azure/

[6] Event-Hub-REST-Client-Generator, um Events über die REST-basierte Schnittstelle des Event Hubs zu veröffentlichen: https://github.com/thomasclaudiushuber/EventHub.RestClientGenerator

[7] Power-BI-Portal: https://www.powerbi.com

Abb. 6: Das Dataset in Power BI

Application-Gateways – Routing für Ihre Microservices

Rainer Stropek

Je mehr Microservices es werden, desto dringender brauchen Sie eine verlässliche und sichere Routinglösung, die den Traffic

aus dem Web zum richtigen Endpunkt schickt. Rainer Stropek, langjähriger Azure MVP und MS Regional Di-rector, startet in seiner Session mit einem Überblick über die Rolle von Application-Gateways in Microser-vices-Architekturen. Welche Aufgaben haben Applica-tion-Gateways in Verbindung mit Microservices? Wel-che Vorteile fügt ein professionelles Application-Gateway dem Microservices-Ansatz hin-zu? Nach dieser Einleitung zeigt Rainer Demos mit dem OSS-Tool nginx und im Vergleich dazu den PaaS-Dienst Azure Application Gateway.

Besuchen Sie auch folgende Session:

www.basta.net 41

DOSSIER Web Development

Page 37: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 42

DOSSIER Data Access & Storage

von Manfred Steyer

Bei Entity Framework handelt es sich um die von Mi-crosoft für .NET empfohlene Datenzugriffstechnologie. Seit seinem Erscheinen im Jahr 2008 hat das Produkt-team den populären OR Mapper kontinuierlich wei-terentwickelt. Somit liegt aktuell mit Version  6 eine ausgereifte und performante Lösung vor. Sie ist gut ins Ökosystem von .NET integriert und ermöglicht das Ab-fragen von Daten via LINQ.

Allerdings haben sich in Entity Framework im Laufe der Zeit auch einige Mehrgleisigkeiten angesammelt. Bei-spielsweise existieren derzeit zwei Ansätze zum Abbilden von Entitäten auf Tabellen. Neben der Möglichkeit, diese Informationen in einer XML-Datei zu verstauen, kann das Entwicklungsteam hierzu seit Version 4.1 auch auf eine Konfiguration mittels Quellcode zurückgreifen. Da-neben liegen derzeit auch zwei Programmiermodelle vor: Neben dem ursprünglichen ObjectContext existiert auch der neuere DbContext. Letzteren bindet Visual Studio ab Version 2012 standardmäßig ein. Er ist aus Sicht der An-wendung leichtgewichtiger, auch wenn er intern auf den ObjectContext fußt.

Dazu kommen weitere Herausforderungen: Zum einen funktioniert Entity Framework 6 nicht mit dem neuen .NET Core, das auch unter Linux und Mac OS X läuft. Zum anderen adressiert das aktuelle Entity Frame-work lediglich relationale Datenbanksysteme. Auch wenn es sich dabei um das vorherrschende Datenbank-paradigma handelt, kommen immer mehr Use Cases für alternative Ansätze auf. Unter dem Sammelbegriff

NoSQL zusammengefasst, erleichtern diese Lösungen zum Beispiel auch den Umgang mit semistrukturierten Massendaten.

Ziele und Nichtziele für das neue Entity FrameworkUm die eingangs erwähnten Herausforderungen zu ad-ressieren, schreibt das Produktteam Entity Framework neu. Dies ist in erster Linie für die Unterstützung von .NET Core notwendig. Somit entsteht mit EF Core 1.0 eine Datenzugriffsbibliothek, die sowohl unter Win-dows und Linux als auch unter Mac OS X läuft und auch vor Universal-Windows-Apps nicht Halt macht.

Im Zuge dessen beseitigt das Produktteam auch die entstandenen Mehrgleisigkeiten: Der alte ObjectCon­text wird komplett entfernt, und das Mapping via XML-Dateien gehört ebenfalls der Vergangenheit an. Übrig bleiben der leichtgewichtige DbContext und die Konfiguration via Code. Auch wenn Letzteres in der Vergangenheit Code First genannt wurde, verhindert es nicht den Start mit einer bestehenden Datenbank. Dieses wohl am häufigsten vorkommende Szenario unterstützt EF Core 1.0, wie sein Vorgänger auch, mit einem Re-verse-Engineering-Werkezeug. Dieses liest Metadaten aus der Datenbank aus und generiert Entitätsklassen sowie ein DbContext-Derivat.

Auch wenn EF Core 1.0 ein vollständiger Neubeginn ist, möchte das Produktteam das API, soweit es sinn-voll möglich ist, nicht verändern. Entwickler, die Entity Framework in der Vergangenheit nutzten, sollen sich auch bei EF Core 1.0 heimisch fühlen.

Mit Entity Framework (EF) Core 1.0 schreibt Mi-crosoft sein populäres Datenzugriffsframework neu. Das primäre Ziel ist eine Unterstützung für das plattformunabhängige .NET Core. Daneben will das Produktteam Mehrgleisigkeiten beseitigen und NoSQL-Lösungen berücksichtigen. Da in Version EF Core 1.0 noch einige Aspekte fehlen, empfiehlt sich für Anwendungen jenseits .NET Core vorerst noch die aktuelle Version Entity Framework 6.

Was bringt der Neustart?

Entity Framework Core 1.0

©iS

tock

ph

oto

.co

m/S

tud

ioM

1

Page 38: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 43

DOSSIER Data Access & Storage

Daneben betont das Produktteam, dass das neue Entity Framework die Unterschiede zwischen relatio-nalen Datenbanken und NoSQL-Lösungen nicht „weg-abstrahieren“ kann. Bestimmte Möglichkeiten existieren eben nur im relationalen Paradigma oder in bestimmten NoSQL-Lösungen, und daran kann auch eine moderne Datenzugriffsbibliothek nichts ändern.

Was noch nicht da ist?Das primäre Ziel für EF Core 1.0 ist das Bereitstellen einer Datenzugriffslösung für .NET Core. Um dieses Ziel bis zur Fertigstellung von .NET Core zu erreichen, fokussiert sich das Produktteam bei dieser ersten Version auf die un-abdingbaren Features – oder anders ausgedrückt: Vieles, was man nicht unbedingt benötigt, wird vorerst wegge-lassen. Aus diesem Grund wird EF Core 1.0 auch we-niger Features als sein Vorgänger, Entity Framework 6, mitbringen (Tabelle 1). Dazu gehören m:n-Beziehungen, die vorerst in Form von zwei 1:n-Beziehungen abzubil-den sind, aber auch Lazy Loading, die Unterstützung für Stored Procedures oder Custom Conventions.

Auch auf komplexe Typen zum Gruppieren meh-rerer Eigenschaften einer Entität sowie auf die Verer-bungsstrategien TPT und TPC müssen Core-Entwickler vorerst verzichten. Zum Abbilden von Vererbungsbezie-hungen können sie jedoch auf die Strategie TPH zurück-greifen, die alle Typen einer Vererbungshierarchie in einer einzigen Tabelle platziert. Ebenso muss man noch ein wenig auf NoSQL-Provider warten, die aufgrund von Zeitdruck hinten angestellt wurden. Auch gilt es beim Aktualisieren des Codes nach Datenbankände-rungen Abstriche zu machen, denn die Werkzeuge zum Reverse-Engineering generieren derzeit immer ein kom-plett neues Modell. Somit muss der Entwickler nach dem Aktualisieren der Datenbank händisch eingreifen.

Die meisten Möglichkeiten, die in EF Core 1.0 fehlen, sollen mit späteren Versionen nachgereicht werden. Aus diesem Grund empfiehlt Microsoft für klassische Sze-narien nach wie vor Version 6. Nur wer auf .NET Core setzt, soll – mangels Alternativen – heute schon zu EF Core 1.0 greifen. Auch wenn das Team in Redmond für Entity Framework 6 weitere (Wartungs-)Releases be-reitstellen wird, liegt sein Fokus auf dem Core-Strang.

Aus diesem Grund wird auch EF Core 1.0 früher oder später die empfohlene Version werden.

Allerdings bringt EF Core 1.0, das beim Verfassen dieses Texts als Release Candidate 1 (RC1) vorlag, auch ein paar neue Möglichkeiten. Die nachfolgenden Ab-schnitte gehen anhand einiger Beispiele, die unter [1] zu finden sind, darauf ein.

Mit Entity Framework Core 1.0 loslegenWie im .NET-Umfeld üblich, kommt EF Core 1.0 per NuGet. Da das NuGet-Paket EntityFramework.Com­mands sämtliche nötigen NuGet-Pakete referenziert, kann man sich auf dieses Paket beschränken. Zusätz-lich ist ein weiteres Paket für die gewünschte Datenbank einzubinden. Dies gilt nun auch für Microsofts Haus-datenbank SQL Server. Die vom hier betrachteten RC1 gebotenen Provider finden sich in Tabelle 2. Wie auch schon bei früheren Versionen ist davon auszugehen, dass einzelne Datenbankhersteller sowie Anbieter für Drittkomponenten Provider für weitere Datenbanken anbieten werden. Darüber hinaus möchte auch das Pro-duktteam noch ein paar zusätzliche Provider für nicht relationale Datenbanken hinzufügen.

KonfigurationDie Konfiguration eines DbContext in EF Core 1.0 er-folgt auf den ersten Blick wie gewohnt: Der Entwickler überschreibt OnModelCreating und gibt dem übergebe-nen ModelBuilder die nötigen Informationen bekannt (Listing 1). Nach genauerem Betrachten fällt jedoch auf, dass so manche bekannte Methode nun „nur mehr“ eine Erweiterungsmethode ist. Ein Beispiel dafür ist To Ta­ble, die eine Klasse auf eine Tabelle abbildet. Der Grund hierfür ist ein pragmatischer: In NoSQL-Lösungen gibt es häufig keine Tabellen, und aus diesem Grund macht es auch keinen Sinn, ToTable direkt in das API aufzu-nehmen. Ein weiteres Beispiel dafür ist die neue Methode

Datenbank NuGet-Paket

SQL Server EntityFramework.MicrosoftSqlServer

PostgreSQL EntityFramework7.Npgsql

SQLite EntityFramework.Sqlite

In-Memory (zum Testen) EntityFramework.InMemory

Tabelle 2: NuGet-Pakete für Datenbankprovider

m:n-Beziehungen Lazy Loading Nur TPH-Vererbung

Komplexe Typen Custom Conventions Stored Procedures

Update from Datebase

NoSQL-Provider

Tabelle 1: Einschränkungen in Entity Framework Core 1.0

Entity Framework 6 – so schnell wie ein Tiroler beim Skifahren

Christian Giesswein

Gerade Entity Framework gilt als Allheilmittel, wenn es um die Anbindung einer Datenbank im .NET-Bereich geht. Doch kaum geht es et-

was über das kleine Beispielprojekt hinaus, fangen die Probleme an. Die Performance geht in den Keller, der Kunde ist unzufrieden, als Entwickler wird einem vom Chef der Kopf gewaschen und niemand ist zufrieden. Doch gerade mit den fünf Tipps, die ich Ihnen in dieser Session auf den Weg geben möchte, wird Ihre Anwen-dung so schnell wie ein Tiroler beim Skifahren – ver-sprochen.

Besuchen Sie auch folgende Session:

Page 39: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 44

DOSSIER Data Access & Storage

HasSequence, die den Namen einer Sequenz für die Gene-rierung von Autowerten bekannt gibt.

Einen ähnlichen Ansatz wählt das Produktteam auch für Aspekte, die nur in ganz bestimmten Datenbankpro-dukten zu finden sind. Hier nutzt es auch Erweiterungs-methoden, die jedoch den jeweiligen Produktnamen widerspiegeln. Als Beispiel hierfür präsentiert Listing 1 die Methode ForSqlServerIsClustered.

Eine weitere Neuerung ist die ebenfalls zu überschrei-bende Methode OnConfiguring, die ein DbContextOp­tionsBuilder-Objekt entgegennimmt. Damit kann der Entwickler die adressierte Datenbank bekannt geben. Auch hierzu nutzt er Erweiterungsmethoden. Diese fin-den sich in den NuGet-Paketen der einzelnen Provider. Wer den DbContextOptionsBuilder nicht direkt im Kon-text konfigurieren möchte, kann dies auch beim Start der Anwendung machen und den OptionsBuilder via De-pendency Injection zur Verfügung stellen. Gerade beim Einsatz von ASP.NET Core 1.0 bietet sich dieser Weg an, zumal Dependency Injection dort omnipräsent ist.

Shadow Properties

Ein in der Vergangenheit immer wieder nachgefragtes Feature ist die Unterstützung von Spalten, die erst nach dem Kompilieren hinzugekommen sind. Das ist zum Bei-spiel bei Anwendungen der Fall, die Spalten dynamisch einrichten. EF Core 1.0 löst diese Herausforderung über Shadow Properties. Das sind Properties, die dem Kontext bekannt sind, sich jedoch nicht innerhalb der Entitäten

wiederfinden. Um Shadow Properties zu nutzen, gibt die Anwendung diese zunächst beim Konfigurieren des Kon-texts innerhalb der Methode OnModelCreating bekannt:

modelBuilder.Entity<Flight>().Property(typeof(String), "PlaneType");

Nach dem Abfragen einer Entität kann die Anwendung solche Eigenschaften über den jeweiligen Change-Tra-cker-Eintrag in Erfahrung bringen. Diesen erhält sie, wie in vergangenen Versionen auch, mit der Methode Entry (Listing 2).

Um Shadow Properties über LINQ-Abfragen zu nut-zen, verwendet die Anwendung stellvertretend dafür die statische Methode EF.Properties. Diese ist mit dem Typ und dem Namen der zu adressierenden Eigenschaft so-wie mit der jeweiligen Entität zu parametrisieren:

Listing 1protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.Entity<Flight>().ToTable("Flights"); modelBuilder.HasSequence("Seq");

modelBuilder.Entity<Flight>() .HasKey(p => p.Id).ForSqlServerIsClustered();

[...]}protected override void OnConfiguring(DbContextOptionsBuilder

optionsBuilder){ optionsBuilder.UseSqlServer(@"Data Source=[…]");}

Listing 2using (FlugDbContext ctx = new FlugDbContext()){ var flight = ctx.Flights.First(f => f.Id == 1); var planeType = ctx.Entry(flight).Property("PlaneType").CurrentValue; Console.WriteLine("PlaneType: " + planeType);}

Analyse von SQL Server auf Performanceengpässe

Uwe Ricken

Zum Tagesgeschäft eines DBA sowie von Beratern, die in Sachen „Microsoft SQL Server“ unterwegs sind, gehört die sorgfäl-

tige Analyse von SQL-Server-Systemen, wenn das Business oder der Kunde Performanceprobleme meldet. In dieser Session wird an Hand von konkre-ten Beispielen ein Microsoft SQL Server auf ver-schiedene Problemfälle geprüft, die zu Engpässen führen, wenn die Einstellungen nicht korrekt sind. Die verschiedenen Analysen werden durch Beispiele mit Erläuterungen zu den möglichen Auswirkungen de-monstriert. Gleichzeitig wird ein Lösungsplan erar-beitet, an dem sich der Teilnehmer von den richtigen Einstellungen im Betriebssystem zu den Konfigurati-onseinstellungen in Microsoft SQL Server durchar-beiten kann. Last but not least werden Lösungswege für den Fall aufgezeigt, wenn die Performance inner-halb einer einzelnen Datenbank als nicht ausreichend bewertet wird. Alle möglichen Performanceengpässe werden auf einem installierten Microsoft SQL Server mittels Skripten simuliert und ausgewertet. Die fol-genden Analyseschritte werden behandelt:

• Einstellungen im Betriebssystem überprüfen undbewerten

• Analyse der Datenbanken auf Konfiguration, Nut-zung und Systemauslastung

• Konfiguration und Analyse von TEMPDB

• Waits and Latches: Worauf wartet Microsoft SQLServer und welchen Einfluss haben die verschie-denen Wartevorgänge auf die Performance derAnwendungen?

• Index Maintenance – wie werden Indexe verwen-det, welche Indexe fehlen, wie ist der physikali-sche Zustand von Indexen zu bewerten?

Besuchen Sie auch folgende Session:

Page 40: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 45

DOSSIER Data Access & Storage

var flights = ctx.Flights .Where(f => EF.Property<string>(f, "PlaneType") == "Airbus") .ToList();

Natives SQL (raw SQL)Nicht jede Anforderung lässt sich mit OR Mapper lösen. Wenn alle Stricke reißen, hilft nur noch der Griff zu nati-vem SQL. Dies erlaubt Entity Framework zwar schon seit seinen ersten Tagen, Core 1.0 kann nun aber auch nati-ves SQL mit einer LINQ-Anfrage kombinieren. Auf diese Weise entsteht eine neue Abfrage, die es komplett in der Datenbank ausführt. Ein Beispiel dafür findet sich in Lis-ting 3. Es legt mit FromSql den auszuführenden nativen

SQL-Code fest. Dabei handelt es sich hier um den Aufruf einer Table-Valued Function (TVF). Darin befinden sich auch Parameter, die per Konvention die Namen p0 bis pN aufweisen. Die Werte für diese Parameter finden sich im zusätzlich an FromSql übergebenen Parameterarray. An die native Abfrage schließt das Beispiel per LINQ mit Where eine Einschränkung an.

Ein Blick in den Profiler zeigt, dass Entity Frame-work die beiden Teile tatsächlich zu einer einzigen Ab-frage zusammenfährt und sie an die Datenbank sendet (Abb. 1).

Clientseitige AbfrageausführungEF Core 1.0 unterstützt nun auch Fälle, in denen es LINQ-Abfragen nicht vollständig nach SQL überset-zen kann. Dazu teilt es die Abfrage in einen serversei-tigen und einen clientseitigen Teil. Ersterer wird nach SQL übersetzt und zur Datenbank gesendet. Letzterer wird zum Filtern der daraufhin erhaltenen Ergebnis-menge am Client genutzt. Ein Beispiel dafür findet sich in Listing 4. Da Entity Framework keine serverseitige Entsprechung für den Aufruf der Methode Check Route kennt, entscheidet es sich, sie am Client auszuführen.

Auch hier beweist ein Blick in den Profiler, dass ledig-lich die Einschränkung auf das Datum in der Datenbank zur Ausführung kommt (Abb. 2).

Da eine clientseitige Filterung zu Performanceproble-men führen kann, gibt Entity Framework im Zuge des-sen eine Warnung ins Protokoll aus (Abb. 3). Um an das Protokoll zu kommen, ist ein Logger für Entity Frame-work zu registrieren. Das erfolgt an und für sich per Dependency Injection. Zur Vereinfachung kommt im betrachteten Beispiel stattdessen die benutzerdefinierte Methode LogToConsole zum Einsatz, die im Quellcode zu diesem Text unter [1] zu finden ist.

Wer auf Nummer sicher gehen möchte, kann die cli-entseitige Ausführung von Abfragen auch deaktivieren. Dazu ruft er in der oben besprochenen Methode On­Configuring nach dem Registrieren des Providers die Methode DisableQueryClientEvaluation auf:

optionsBuilder .UseSqlServer(@"Data Source=[...]") .DisableQueryClientEvaluation();

Listing 4

using (FlugDbContext ctx = new FlugDbContext()){ ctx.LogToConsole();

var flight = ctx.Flights.Where(f => f.Date >= DateTime.Today

&& CheckRoute(f, "Vienna-London")).ToList();

// ... do stuff ...}

Abb. 1: Kombination aus nativem SQL und LINQ Abb. 2: Ausführung einer Teilabfrage am Server

Listing 3

using (FlugDbContext ctx = new FlugDbContext()){ var flight = ctx.Flights

.FromSql("select * from dbo.GetFlights(@p0, @p1)", "Vienna", "London")

.Where(f => f.Date > DateTime.Today)

.ToList();

Console.WriteLine("Count: " + flight.Count);}

Page 41: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 46

DOSSIER Data Access & Storage

Dieser Aufruf veranlasst EF Core 1.0 zum Auslösen ei-ner Ausnahme, wenn es eine LINQ-Abfrage nicht voll-ständig nach SQL übersetzen kann.

Syntaxzucker für Eager LoadingEine kleine, aber feine Neuerung bringt .NET Core 1.0 im Bereich Eager Loading: Neben der Methode Inclu­de, die einen direkten Nachbarn der adressierten Entität adressiert, steht nun auch eine Methode ThenInclude zur Verfügung. Diese bezieht sich auf den zuletzt refe-renzierten Nachbarn und gibt einen seiner Nachbarn an. Auf diese Weise lassen sich indirekte Nachbarn per Ea-ger Loading laden. Listing 5 demonstriert das, indem es Flüge samt dessen Buchungen und den Passagieren der Buchungen per Eager Loading anfordert.

Das Laden indirekter Nachbarn war zwar auch schon in früheren Versionen von Entity Framework mög-lich, allerdings war dazu ggf. eine etwas eigenwillige Schreibweise notwendig. Das hier betrachtete Beispiel hätte zum Beispiel wie folgt ausgesehen:

var flight = ctx.Flights.Include( f => f.Bookings.Select(b => b.Passenger)).Where(...).ToList();

Dieses Beispiel bildet innerhalb von Include die Auflis-tung Bookings mit Select auf ein Booking-Objekt ab. Dieser Kunstkniff war notwendig, da die Bookings-Auflistung im Gegensatz zu dessen Objekten nicht die gewünschte Eigenschaft Passenger aufweist.

Unterstützung für getrennte EntitätenDie Methode Add markierte in vergangenen Versionen nicht nur das übergebene Objekt für das Einfügen, son-dern den gesamten Objektgraphen, der über dieses Objekt erreichbar war. Im betrachteten RC1 kann der Aufrufer dieses Verhalten abschalten. Übergibt er den Wert Graph­Behavior.SingleObject markiert Add lediglich das überge-bene Objekt, das den Zustand Added erhält:

ctx.Flights.Add(f1, GraphBehavior.SingleObject);

Getrennte Objekte können nun auch mittels Update wieder angehängt werden. Im Gegensatz zur bekannten Methode Attach, die nach wie vor vorhanden ist, ver-

sucht diese Methode die Zustände sämtlicher Objekte des Graphen auf Added oder Modified zu setzen. Um sich für einen dieser Zustände zu entscheiden, greift sie auf eine simple Heuristik zurück. Hat die Entität bereits einen Primärschlüssel, geht sie von einer bestehenden Entität aus und setzt ihren Zustand auf Modified. An-sonsten nimmt sie an, dass es sich um eine neue Entität handelt. Dies führt zum Zustand Added:

ctx.Flights.Update(flight);

Dass diese Strategie nur funktioniert, wenn die Daten-bank beim Einfügen den Primärschlüssel vergibt, ist selbstredend. Wer nur das übergebene Objekt und nicht den gesamten Objektgraphen betrachten möchte, kann auch hier GraphBehavior.SingleObject übergeben.

Mehr Kontrolle über die Zustände von wieder ange-hängten Objekten erlangt der Entwickler mit der neuen Methode TrackGraph:

ctx.ChangeTracker.TrackGraph(flight, (node) => { // object entity = node.Entry.Entity; node.Entry.State = EntityState.Modified;});

Diese Methode nimmt ein Objekt sowie einen Lambda-Ausdruck entgegen. Sie durchläuft den gesamten Ob-jektgraphen und übergibt die Change-Tracker-Einträge

Abb. 3: Warnung bei clientseitiger Ausführung

Listing 5using (FlugDbContext ctx = new FlugDbContext()){ var flight = ctx.Flights

.Include(f => f.Bookings)

.ThenInclude(b => b.Passenger)

.Where(f => f.From == "Vienna")

.ToList();

// ... do stuff ...}

Custom Conventions Marke EigenbauAuch wenn Entity Framework Core 1.0 keine Un-terstützung für Custom Conventions bietet, kommt der hier betrachtete Release Candidate 1 mit einer einfachen Alternative: Über die Eigenschaft Model des ModelBuilders lassen sich Metadaten zum Mo-dell abrufen. Iteriert man diese Metadaten, lassen sich sämtliche Entitäten anhand einer Konvention konfigurieren. Das nachfolgende Beispiel nutzt dies, um für sämtliche Entitäten einen Tabellennamen festzulegen:

foreach (var entity in modelBuilder.Model.GetEntityTypes()){ modelBuilder.Entity(entity.Name).ToTable(entity.ClrType.Name + "s");}

Page 42: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 47

DOSSIER Data Access & Storage

der einzelnen Objekte nach und nach an den Lambda-Ausdruck. Dieser kann nun den Eintrag sowie die En-tität betrachten und den Zustand entsprechend einer eigenen Logik setzen.

Alternate KeysNeben Primärschlüsseln unterstützt EF Core  1.0 nun auch alternative Schlüssel (Alternate Keys). Dabei handelt es sich um eindeutige Schlüssel, auf die Fremdschlüssel verweisen können. Listing 6 demonstriert den Umgang damit. Es definiert für die Entität Flight neben dem Pri-märschlüssel Id einen alternativen Schlüssel FlightNum­ber. Zusätzlich richtet es einen Fremdschlüssel ein, der den alternativen Schlüssel referenziert. Dazu nutzt es die Methode HasPrincipalKey. Ohne diese Methode würde Entity Framework davon ausgehen, dass sich der Fremd-schlüssel auf den Primärschlüssel von Flight bezieht.

Verbesserungen bei der AbfrageausführungEF Core 1.0 versucht nun auch, performantere Abfra-gen zu erzeugen. Beispielsweise realisiert es nicht mehr ausnahmslos Eager Loading mittels Joins. Stattdessen greift es in manchen Fällen auf mehrere in einem Batch übersendete Abfragen zurück, um eine unnötige Aus-kreuzung der benötigten Daten zu vermeiden. Auch für DML-Operationen nutzt Core 1.0 Batches und verrin-gert somit die Anzahl der an die Datenbank zu übersen-denden Nachrichten.

FazitDank EF Core 1.0 können .NET-Teams in .NET-Core-Projekten und somit auch unter Linux, Mac OS X und in Universal-Windows-Apps ihre lieb gewonnene Datenzu-griffstechnologie nutzen. Das bedingt ein Neuschreiben des Frameworks und öffnet somit auch die Türen für das Beseitigen von Mehrgleisigkeiten. Dies scheint eine gute Entscheidung zu sein, zumal sich in den letzten Jahren einige Spielarten in Entity Framework angesammelt ha-ben.

Durch den Fokus auf den DbContext und Code First konzentriert sich das Produktteam fortan auf die mo-dernste Art der Nutzung von Entity Framework. Dieses Verfahren ist im Übrigen auch mit jenem vergleichbar, das NHibernate seit vielen Jahren erfolgreich nutzt. Die Zeiten, in denen der Entwickler mit einem schwerfälli-

gen Designer ein XML-Mapping warten musste, gehö-ren somit der Vergangenheit an. Wichtig dabei ist zu betonen, dass Code First auch den Start mit einer be-stehenden Datenbank via Reverse-Engineering erlaubt.

Die Unterstützung für NoSQL-Lösungen ist ebenfalls erfreulich. Somit können Entity-Framework-Entwickler ihr vorherrschendes Entity-Framework-Know-how zum Zugriff auf solche Datenbanken nutzen. Das bedeutet jedoch nicht, dass sie eine relationale Datenbank ohne Weiteres gegen ein NoSQL-Gegenstück tauschen kön-nen – die Unterschiede zwischen diesen Lösungen sind einfach zu groß.

Auch wenn Microsoft das API so weit wie möglich aus dem Vorgänger Entity Framework 6.x übernimmt, wird es die eine oder andere Änderung geben, und eine automatische Portierung erscheint somit auch schwie-rig. Dazu kommt, dass EF Core 1.0 vorerst nicht alle Features von Entity Framework 6 mitbringen wird und Version 6 somit die empfohlene Version bleibt. Ledig-lich Entwickler, die für .NET Core entwickeln müssen, kommen mangels Alternativen nicht an EF Core  1.0 vorbei. Allerdings liegt der Fokus bei der Weiterent-wicklung auf dem Core-Strang, und somit wird in ab-sehbarer Zukunft EF Core 1.0 den Stellenwert haben, den heute Version 6 genießt.

Manfred Steyer (www.softwarearchitekt.at) ist selbstständiger Trainer und Berater mit Fokus auf moderne Web- und Service-architekturen. Er ist Mitglied im Expertennetzwerk www.IT-Visions.de und schreibt für O’Reilly und Heise. In seinem aktuellen Buch „Moderne Datenzugriffslösungen mit Entity Framework 6“ behan-

delt er die vielen Seiten des OR Mappers aus dem Haus Microsoft.

Links & Literatur

[1] https://github.com/manfredsteyer/ef7-rc-samples/tree/master

Listing 6modelBuilder.Entity<Flight>().HasKey(p => p.Id);

modelBuilder.Entity<Flight>().HasAlternateKey(f => f.FlightNumber);

modelBuilder.Entity<Flight>() .HasMany(f => f.Bookings) .WithOne(b => b.Flight) .HasForeignKey(b => b.FlightNumber) .HasPrincipalKey(f => f.FlightNumber);

Offline First mit Angular und SQL Server – Es geht auch ohne Netz

Thorsten Hans

Offline First, überall und jederzeit arbeiten, auch ohne Internetverbindung. Eine Anforde-rung, der sich immer mehr Softwarehersteller

trotz 4G und freien WLANs stellen müssen. In diesem Vortrag werden Sie lernen, wie Sie Angular-Anwendun-gen mit Web-API und SQL Server Backend auch ohne Netz in vollem Funktionsumfang betreiben können: Egal ob iOS, Android, Windows, Linux oder macOS. Sie wer-den sehen, was Sie bei der Implementierung von Offline First beachten sollten und welche Fallstricke es aus dem Weg zu räumen gilt.

Besuchen Sie auch folgende Session:

Page 43: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 48

DOSSIER HTML5 & JavaScript

von Dr. Holger Schwichtenberg

TypeScript hat sich rasant weiterentwickelt: Von der Erstankündigung am 02.10.2013 über die Version 1.0 am 02.04.2014 bis zur aktuellen Version 1.8.10 vom 09.04.2016. Maßgeblich an der Entwicklung von Type-Script beteiligt ist Anders Hejlsberg, der Schöpfer von Turbo Pascal, Delphi und C#. Ein wichtiger Meilenstein für TypeScript war der 05.03.2015: An diesem Tag gab Google bekannt, dass das beliebte JavaScript-Frame-work Angular mit TypeScript entwickelt und dafür die eigene Sprache AtScript aufgegeben wird. TypeScript unterstützt aber nicht nur Angular 2, sondern viele JavaScript-Frameworks, einschließlich React mit seiner besonderen eingebetteten Tagsyntax.

WerkzeugeDer TypeScript-Compiler liegt selbst in JavaScript vor (tsc.js). Für Windows gibt es eine direkt ausführbare Version tsc.exe. Den Compiler bekommt man von www.nuget.org [1] oder dem Node Package Manager [2]. In Visual Studio 2013 und Visual Studio 2015 wird der TypeScript-Compiler mitgeliefert (siehe Verzeichnis C:\Program Files (x86)\Microsoft SDKs\Type Script\). Da der Compiler häufig aktualisiert wird, sollte man immer die aktuellsten Visual-Studio-Gesamtupdates bzw. die Updates des TypeScript-Compilers einzeln installieren.

Der TypeScript-Compiler erzeugt aus dem TypeScript-Programmcode dann JavaScript-Programmcode (Abb. 1). Diese Transformation von TypeScript-Syntax zu JavaScript-Syntax kann zu drei Zeitpunkten erfolgen:

1. Nach jedem Speichern einer TypeScript-Datei2. Im Rahmen der Übersetzung eines Projekts3. Zur Laufzeit

Bei den JavaScript-Versionen für das Kompilat hat der Entwickler die Wahl zwischen den Standards ECMA-Script Version 3, ECMAScript Version 5 und dem ak-tuellen ECMAScript Version 2015 (alias Version 6). Bei der Ausgabe von Modulen hat der Entwickler inzwi-schen sogar fünf Modulformate zur Wahl: Asynchro-nous Module Design (AMD), CommonJS, Universal Module Definition (UMD), SystemJS und ECMAScript 2015 (ES6) (Abb. 1).

In Visual Studio wählt der Webentwickler die wich-tigsten Compileroptionen komfortabel in den Projekt-einstellungen in Webprojekten (Abb. 2). Leider sind nicht alle TypeScript-Compiler-Optionen auf diesem Wege verfügbar. Manche Einstellungen (z. B. die Ak-tivierung einiger noch als „experimentell“ geltenden Sprachfeatures, wie Dekoratoren) muss man, wie in Listing 1, durch einen Eintrag in der XML-Projektdatei direkt vornehmen.

TypeScript = JavaScript + x

Die Alternative für JavaScript-Hasser An JavaScript scheiden sich die Geister: Die einen lieben die Sprache aufgrund ihrer Flexibilität, die anderen hassen sie aufgrund ihrer Komplexität. Aber auch die Liebhaber müssen feststellen, dass manche JavaScript-Entwickler Programmcode schreiben, der schwer lesbar und nicht gut zu warten ist. Sprachen, die von JavaScript abstrahieren, gibt es schon länger. Microsofts TypeScript setzt sich hier immer stärker durch, vor al-lem nachdem auch Google mit Angular 2 aufgesprungen ist.

Page 44: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

Abb. 1: Der TypeScript-Compiler erzeugt aus den TypeScript-Dateien dann JavaScript-Dateien und außerdem noch Abbildungsdateien für den Debugger

www.basta.net 49

DOSSIER HTML5 & JavaScript

Der zweite Weg für die TypeScript-Compi-ler-Konfiguration ist, dem Projekt eine tscon­fig.js-Datei hinzufügen (Listing 2). Dazu kann man in Visual Studio ein neues Element vom Typ TypeScript JSON Configura tion File an-legen.

Die von dem TypeScript-Compiler erzeug-ten .js-Dateien sind im Standard nicht Teil des Visual-Studio-Projekts. Man kann sie wie üblich über die Option Show all files im Solution Explorer einblenden. Fehler bei der TypeScript-Kompilierung landen in der Error List – wie man es erwartet. In eine Webseite einbetten muss man immer die .js-Datei. Visu-al Studio hilft hier mit: Beim Drag and Drop einer .ts-Datei in eine .html-Datei erstellt die IDE ein <script>-Tag mit Verweis auf die .js-Datei statt auf die .ts-Datei.

Visual Studio bietet auch Debugging: Bei ei-nem Laufzeitfehler oder einem Haltepunkt in der Type Script-Datei zeigt die Entwicklungsum-gebung im Debugger nicht den eigentlich aus-geführten JavaScript-Programmcode, sondern den ursprünglichen TypeScript-Programmcode an. Der Entwickler kann diesen wie üblich zei-lenweise durchlaufen. Die für dieses komfortab-le Debugging notwendige Beziehung zwischen den Zeilen in der ausgeführten JavaScript-Datei und in der ursprünglichen TypeScript-Datei erhält Visual Studio durch eine Source-Map-Datei (.map), die ebenfalls im Rahmen der Ty-peScript-Kompilierung entsteht (Abb. 1).

Wer mit Visual Studio Code oder anderen „einfacheren“ Editoren arbeiten will, muss auf die Kommandozeilenoptionen zurückgrei-fen. Der folgende kurze Kommandozeilenbe-fehl sorgt dafür, dass zwei Type Script-Dateien immer beim Speichern in ECMA Script 5 und das Modulformat AMD übersetzt werden:

tsc datei1.ts datei2.ts --watch --target es5 --module amd

Listing 1: Ausschnitt aus den TypeScript-Compiler-Einträgen in „.csproj“-Datei

<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <TypeScriptExperimentalDecorators>true</TypeScriptExperimentalDecorators> <TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata> <TypeScriptTarget>ES6</TypeScriptTarget>...</PropertyGroup>

Listing 2: Beispiel für eine „tsconfig.js“-Datei{ "compilerOptions": { "experimentalDecorators": true, "noImplicitAny": false, "noEmitOnError": true, "removeComments": false, "sourceMap": true,

"target": "es2015" }, "exclude": [ "node_modules", "wwwroot" ]}

Abb. 2: Projekteinstellungen für TypeScript in Webprojekten in Visual Studio 2015

Page 45: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 50

DOSSIER HTML5 & JavaScript

Die Option ­­watch gibt es leider nur, wenn man tsc.cmd aus Node.js-Paketen zum Kompilieren verwendet. Die tsc.exe, die in Visual Studio enthalten ist, kann das Dateisystem leider nicht überwachen, dafür küm-mert sich Visual Studio selbst um die Überwachung. Eine weitere wichtige Kommandozeilenaktion ist tsc

­­init. Damit legt man im aktuellen Verzeichnis eine tsconfig.js an, über die man dann den Compiler steuern kann.

Übersetzung in JavaScriptDie Sprache TypeScript stellt eine Übermenge von JavaScript da, das heißt, (fast) jede JavaScript-Datei ist auch gültiger Type Script-Programmcode. Das ist hilfreich für den Umstieg von JavaScript auf Ty-peScript bzw. für die Nutzung beste-henden JavaScript-Programmcodes aus Büchern und dem Internet. Aller-dings steht in dem Satz oben bewusst ein „fast“: Denn den JavaScript-Programmcode in Listing 3 (eine Fil-terdefinition für AngularJS) mag der TypeScript-Compiler nicht. Er be-mängelt: error TS2322: Type 'string' is not assign able to type 'number'. Das liegt daran, dass der Type Script-Compiler durch die Zeile var num = parseInt(n, 10) der Variablen num auf dem Wege der automatischen Ty-pherleitung bereits den Typ number zuweist. Dann später hat der Autor dieses JavaScript-Codes [3] die Vari-able num nun leider als Zeichenkette

verwendet. In JavaScript ist sowas möglich – aber na-türlich schlechter Programmierstil. Gut programmiertes JavaScript ist kein Problem für den TypeScript-Compi-ler. Auch in diesem Fall kann man die Situation retten, indem man in Type Script explizit num auf den Typ any deklariert: var num : any = parseInt(n, 10).

Microsoft hat sich bei der TypeScript-Syntax von An-fang an bemüht, nahe am ECMAScript-2015-Standard zu sein, d. h., bei der Ausgabe in ECMAScript 2015 hat der Compiler nur ganz wenig zu tun. Je niedriger die Zielversion jedoch ist, desto mehr Programmcode muss der Compiler erzeugen, um die fehlenden JavaScript-Sprachfeatures nachzubilden.Ein guter erster Anlaufpunkt für Neueinsteiger in Ty-peScript ist die interaktive, browserbasierte Codeum-wandlung, die Microsoft unter [4] bietet: Während man in der linken Fensterhälfte von Abbildung 3 Type Script er-fasst, sieht man rechts schon das JavaScript-Ergebnis. Ab-bildung 3 zeigt eine Vererbungshierarchie mit drei Klassen in TypeScript und rechts das deutlich längere Pendant in JavaScript. Man sieht sofort: TypeScript ist hier wesent-lich prägnanter und eleganter. In dieser Webanwendung kann man leider die JavaScript-Zielversion nicht wählen; die Ausgabe ist aktuell immer ECMAScript 5.

DatentypenDas Typsystem von TypeScript unterscheidet zwischen primitiven und komplexen Typen. Zu den primitiven

Abb. 3: Onlineeditor und Ausführungsumgebung bilden eine „Spielwiese für TypeScript“ [4]

Listing 3: Diesen JavaScript-Code bemängelt der Type Script-Compiler

// Quelle: http://jsfiddle.net/TheRoks/8uCT9/var app = angular.module('myApp', []);

app.filter('numberFixedLen', function () { return function (n, len) { var num = parseInt(n, 10); len = parseInt(len, 10); if (isNaN(num) || isNaN(len)) { return n; } num = '' + num; while (num.length < len) { num = '0' + num; } return num; };});

Page 46: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 51

DOSSIER HTML5 & JavaScript

Typen zählen number, boolean, string und void. Daneben exis-tiert noch der Datentyp any, der jeden beliebigen Wert aufneh-men kann und somit das Stan-dardverhalten von Variablen in JavaScript widerspiegelt. Zu den komplexen Typen zählen Typen, die aus der Deklaration

von Schnittstellen und Klassen hervorgehen. Im Gegensatz zu JavaScript kann beim Deklarieren

einer Variablen mit var ein Datentyp angegeben wer-den. Dazu wird der Datentyp der Variablen mit einem Doppelpunkt nachgestellt. Im Zuge dessen kann der Entwickler bereits auch einen Initialwert angeben. Die TypeScript Language Specification [5] nennt auch null und undefined als Typen; diese kann man aber nicht in gleicher Weise nach dem Doppelpunkt angeben, son-dern lediglich als Wert nach dem Gleichheitszeichen verwenden:

var id: number = 123;var name: string = "Holger Schwichtenberg";var autor: boolean = true;

Auch wenn die explizite Typangabe fehlt, wird eine Va-riable durch die Initialisierung während der Deklaration geprägt (Typherleitung). In folgendem Codefragment kommt es daher in Zeile 2 zum Fehler Cannot convert 'number' to 'string':

var Website1 = "https://entwickler.de ";Website1 = 456; // falsch !!!

Etwas kurios ist dabei, dass der TypeScript-Compiler den Fehler zwar ausgibt, aber dennoch kompiliert und die monierte Zeile so nach JavaScript übernimmt, dass zur Laufzeit tatsächlich die Zahl 456 in der Variablen Website1 steht. Erst seit Version 1.4 des TypeScript-Compilers gibt es die Option noEmitOnError, die die 1:1-Ausgabe bei Kompilierfehlern verhindert.

Wenn der Entwickler in TypeScript tatsächlich eine Variable will, deren Typ sich ändern darf, muss er any verwenden:

var Website2: any = "http://www.dotnet-doktor.de";Website2 = 456;

Alternativ dazu erhält er eine any-Variable, wenn die Deklaration keine Initialisierung enthält:

Abb. 4: IntelliSense für typisierte Variablen in TypeScript

Abb. 5: Keine IntelliSense für untypisierte Variablen in JavaScript

var Website3; // dies prägt Webseite3 auf anyWebsite3 = " https://www.typescriptlang.org"Website3 = 456; // Dies ist hier möglich, da die Variable Website3 den Typ "any" erhalten hat

Microsoft hat sich in den letzten Jahren sehr bemüht, auch JavaScript-Entwickler mit IntelliSense zu unter-stützen. Aufgrund der fehlenden Typisierung kann dies aber immer nur begrenzt möglich und begrenzt präzise sein. Abbildung 4 und 5 zeigen in der Gegenüberstel-lung schon an einem einfachen Beispiel eindrucksvoll, dass Visual Studio für TypeScript eine bessere Eingabe-unterstützung anbietet als für JavaScript.

Seit TypeScript 1.6 gibt es alternativ zu var auch die Schlüsselwörter let und const aus ECMAScript 2015: bei-de beschränken die Deklaration auf den aktuellen Block:

if (...) { var x: number = 5;}print("x=" + x); // Ausgabe x=5 :-( nur Warnung

if (...) { let y: string = "Holger Schwichtenberg";}print("y=" + y); // Fehler!

const z: boolean = true;z = false; // Fehler!

Seit Version 1.4 von TypeScript gibt es Union Types. Sie bezeichnen eine Deklaration, bei der man einer Varia-blen zwei oder mehr verschiedene Typen zuweisen und damit in bestimmten Situationen die Deklaration auf any vermeiden kann:

TypeScript für .NET-EntwicklerChristian Wenz

Mit TypeScript macht sich Microsoft daran, das – für viele Entwickler aus dem eigenen Kosmos – ungewohnte JavaScript zugängli-

cher zu machen, indem beispielsweise statisches Ty-ping und bestimmte OOP-Features hinzugefügt wer-den. Nach einigen Anlaufschwierigkeiten hat TypeScript inzwischen auch außerhalb der Microsoft-Welt Traktion erhalten. Viel besser noch: Angular setzt auf TypeScript! Es ist also höchste Zeit, sich mit der Sprache zu beschäftigen. Diese Session stellt die Features von TypeScript vor und geht dabei auch auf die Toolunterstützung seitens Visual Studio und Co. ein. Damit sind Sie auch für die Entwicklung von Anwendungen auf Basis von Angular bestens ge-wappnet.

Besuchen Sie auch folgende Session:

Page 47: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 52

DOSSIER HTML5 & JavaScript

var mitarbeiterID: number | string;mitarbeiterID = 123; // OKmitarbeiterID = "A123"; // auch OKmitarbeiterID = true; // Fehler!

Das folgende Fragment zeigt eine Typprüfung und eine Typkonvertierung. Für Letztere gibt es zwei Syntaxfor-men <typ>variable oder variable as typ:

var eingabe: any = 5;if (typeof eingabe === 'number') { var zahl1 = (<number>eingabe) + 1; // alternativ: var zahl2 = (eingabe as number) + 2;}

Arrays und TupelAuch für typisierte Arrays gibt es in TypeScript zwei Syntaxformen: Array<number> oder number[]. Lis-ting 4 zeigt weitere Besonderheiten:

• Die for…of-Schleife (aus ECMAScript 2015, ent-spricht foreach in C#/Visual Basic .NET)

• Das elegante Aufbrechen von Arrays in einzelne Vari-ablen mit dem Sprachfeature Destructuring

Seit Version 1.3 beherrscht TypeScript auch Tupel. Das sind Wertemengen, in denen jeder einzelne Wert auf einen anderen Typ deklariert sein kann. Das folgende Fragment zeigt ein Tupel, das aus einer Zahl, einer Zei-chenkette und einem Bool-Wert besteht:

var PersonTupel: [number, string, boolean];PersonTupel = [123, "Holger S.", true];var personName = PersonTupel[1];

KlassenDas Herzstück von TypeScript sind Klassen. ECMA-Script 2015 kann auch Klassen, aber mit TypeScript kann man Klassen auch in älteren ECMAScript-Versi-onen nutzen.

Wie Listing 5 zeigt, werden Klassen, wie in C#, mit dem Schlüsselwort class eingeleitet. TypeScript erlaubt, ähnlich wie C# oder Java, die Definition von Schnittstel-len. Um anzugeben, dass eine Klasse eine Schnittstelle implementiert, verwendet der Entwickler das Schlüssel-wort implements. In Listing 5 implementiert die Klasse Kontakt die zuvor definierte Schnittstelle IKontakt.

Im Gegensatz zu vielen anderen objektorientierten Sprachen ist die standardmäßige Sichtbarkeit immer public. Daneben kann der Entwickler Mitglieder mit pri vate als nicht von außen nutzbare Mitglieder dekla-rieren. Seit Version 1.3 gibt es auch protected (nicht nutzbar von außen, aber nutzbar von angeleiteten Klassen).

Konstruktoren werden mit dem Schlüsselwort con­structor eingeleitet. Eine wichtige Besonderheit gibt es hier jedoch: Die Sichtbarkeit public, private oder pro­tected darf man in Konstruktorparametern verwenden. Wenn die Parameter des Konstruktors so eingeleitet werden, werden diese Parameter automatisch zu Instanz-mitgliedern. Das in anderen OO-Sprachen notwendige Umkopieren der Werte aus den Konstruktorparametern in die Instanz erfolgt automatisch. Eine Konstruktorim-plementierungszeile wie this.id = id ist in TypeScript also überflüssig. Es ist nicht erlaubt, die in den Konstruktor-parametern verwendeten Bezeichner nochmals für ande-re Properties der Klasse zu verwenden. Microsoft hatte

Listing 4: Arrays in TypeScripttype ZahlenArray = number[]; // Array<number> oder number[]var lottozahlen2: ZahlenArray; // Deklarationvar lottozahlen: Array<number>; // Initialisierunglottozahlen = [11, 19, 28, 34, 41, 48, 5]; // Schleife über alle Elemente (seit Version 1.5)for (let z of lottozahlen) { print(z);}; // Destructuring (seit Version 1.5)let [z1, z2, z3, z4, z5, z6, zusatzzahl] = lottozahlen;print(`Zusatzzahl: ${zusatzzahl}`);

Effizienter Datenfluss vom Entity Framework über Web-API bis zum JavaScript-Client

Dr. Holger Schwichtenberg

Es gibt genug Vorträge, die eine Technik de-tailliert diskutieren. In diesem Vortrag schauen wir hingegen auf die End-to-End-

Integration: Wie schreibt man heutzutage als .NET-Entwickler eine Webanwendung und/oder HTML-ba-sierte Cross-Platform-App unter Einsatz der aktuellen Techniken möglichst budgetsparend? Auf dem Server kommen Entity Framework Core und ASP.NET-Core-Web-API-basierte Microservices zum Einsatz, flankiert von Swagger und der zugehörigen Generierungen von Clientcode, den man sonst müh-sam schreiben müsste. Der Client nutzt TypeScript und Angular sowie Electron und Cordova. Der .NET- und Webexperte zeigt dies anhand eines eindrucks-vollen End-to-End-Fallbeispiels.

Besuchen Sie auch folgende Session:

Page 48: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 53

DOSSIER HTML5 & JavaScript

solch ein Sprachfeature auch für C# 6.0 unter dem Na-men „Primary Constructors“ in Arbeit, hat es aber leider wieder verworfen.

Konstruktoren dürfen auch nicht überladen werden; es kann also pro Klasse nur einen Konstruktor geben. Den Methoden einer Klasse darf der Entwickler nicht das Schlüsselwort function voranstellen. Für alle Zugrif-fe auf Instanzmitglieder ist in TypeScript innerhalb der Klasse zwingend this voranzustellen. Statische Mitglie-der erhalten den Zusatz static.

Listing 5 zeigt ein Beispiel mit Namensraum, Schnitt-stelle, Enumeration, Klassen und Vererbung. Auch JS-Doc-Kommentare wurden an einigen Stellen verwendet (aus aber Platzgründen nicht an allen).

Um von einer bestehenden Klasse abzuleiten, ver-wendet der Entwickler, wie unter Java, das Schlüs-selwort extends (siehe Klasse Kunde in Listing  5).

Ebenso kann man mit extends eine Schnittstelle von einer anderen ableiten. Es ist nur Einfachvererbung er-laubt. Anders als in C# und Visual Basic .NET werden Konstruktoren vererbt. Sobald eine Unterklasse aber einen eigenen Konstruktor realisiert, muss zwingend der Konstruktor der Basisklasse mit super() aufgerufen werden (siehe Konstruktor der Klasse Kunde in Lis-ting 5). Alle Typen können in Namensräumen organi-siert werden (Listing 5).

Die Instanziierung von TypeScript-Klassen erfolgt mit dem Schlüsselwort new. Um zur Laufzeit zu prü-fen, ob eine Variable ein Objekt eines bestimmten Typs enthält, kann der Entwickler den Operator instanceof heranziehen. Auch bei der Prüfung gegen eine Basis-klasse liefert instanceof den Wert true. Destructuring kann man auch auf Objekte anwenden, wie das folgen-de Fragment zeigt:

Listing 5: Beispiel mit Namensraum, Schnittstelle, Enumeration, Klassen und Vererbung/// <reference path="../TypeScriptHTMLApp/_Hintergründe/lib.d.ts" />

namespace Demo.Interfaces { // Schnittstelle export interface IKontakt { id: number; name: string; geprueft: boolean; erfassungsdatum: Date; toString(details: boolean): string; } // Aufzählungstyp export enum KundenArt { A = 1, B, C }}

namespace Demo.GO {

// #region Basisklasse /** Kontakt mit id, name, ort* @autor Dr. Holger Schwichtenberg*/export class Kontakt implements Interfaces.IKontakt {// ---------- Properties ohne Getter/Setterpublic geprueft: boolean;protected interneID: number;private _erfassungsdatum: Date;

// ---------- Properties mit Getter/Setter get erfassungsdatum(): Date { return this._erfassungsdatum; }

set erfassungsdatum(erfassungsdatum: Date) { this._erfassungsdatum = erfassungsdatum; }

/**

* @param id Kundenummer* @param name Kundenname*/constructor(public id: number,

public name: string) {this.erfassungsdatum = new Date();this.id = id; // Diese Zeile ist ueberfluessig !!!Kontakt.Anzahl++;

}

// Öffentliche Methode toString(details: boolean = false): string { { var e: string = `${this.id}: ${this.name}`; return e; } }

// Statisches Mitglied static Anzahl: number; } // #endregion

// #region Erbende Klasse export class Kunde extends Kontakt { public KundenArt: Interfaces.KundenArt; constructor(id: number, name: string, umsatz: number) { super(id, name); this.KundenArt = Interfaces.KundenArt.C; console.log("Umsatz", umsatz + 1.01); if (umsatz > 10000) this.KundenArt = Interfaces.KundenArt.A; if (umsatz > 5000) this.KundenArt = Interfaces.KundenArt.B; } toString() { return `Kunde ${super.toString()}`; } } // #endregion

} // End Namespace

Page 49: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 54

DOSSIER HTML5 & JavaScript

var k = new GO.Kunde(123, "H. Schwichtenberg", 99.98);var ausgabe1 = k.toString();

// Typprüfungif (k instanceof GO.Kunde) { ... }if (k instanceof GO.Kontakt) { ... }

// Destructuring von Objektenlet { id, name } = k;var ausgabe2: string = `${id}: ${name}`;

Generische KlassenBereits die oben gezeigte Syntaxform Array<number> ist der Einsatz einer generischen Klasse. Listing 6 zeigt die Realisierung einer eigenen generischen Klasse und deren Verwendung.

Strukturelle Typäquivalenz (Duck Typing)Wie in anderen Sprachen auch kann man eine Vari-able auch auf eine Schnittstelle typisieren, also statt var k1: GO.Kunde auch var k2: Interfaces.IKontakt schreiben und dann anschließend eine Instanz von Kunde zuweisen mit k2 = new GO.Kunde(…);. Im Ge-gensatz zu Sprachen wie Java oder C# setzt TypeScript aber auf „strukturelle Typäquivalenz“. Das bedeutet, dass Objekte, die einer Variablen zugewiesen werden, nicht in der Vererbungshierarchie passen müssen. Es ist ausreichend, dass das zugewiesene Objekt dieselbe Struktur, sprich dieselben Mitglieder, wie der Typ der Variable aufweist. Man könnte also im Fall von Lis-ting 5 das implements Interfaces.IKontakt auch weg-lassen und dennoch var k3: Interfaces.IKontakt = new GO.Kunde(…) schreiben.

Das folgende Codefragment zeigt, wie der Entwick-ler auf Basis der strukturellen Typäquivalenz definieren kann, dass eine Variable nur auf ein Objekt mit einer bestimmten Struktur verweisen darf, indem für die Va-riable k2 definiert wird, dass sie nur für Objekte mit ei-ner Eigenschaft id heranzuziehen ist. Aus diesem Grund kann auch eine Instanz von Kunde zugewiesen werden. Dass die Klasse Kunde neben der id noch weitere Eigen-schaften hat, stellt hier kein Hindernis dar:

var k2: { id: number; };k2 = new Demo.GO.Kunde(456, "Holger Schwichtenberg", 99.98);var schluesselwert: number = k2.id;

Funktionen und Lambda-AusdrückeAuch beim funktionalen Programmieren bietet Type-Script sinnvolle Erweiterungen. Bei der Deklaration von Funktionen kann der Entwickler für die einzelnen Parameter einen Typ definieren. Dasselbe gilt auch für den Rückgabewert der Funktion, dessen Typ der Funk-tionssignatur nachgestellt wird:

function add(a: number, b: number): number { return a + b;}

Auch Lamdba-Ausdrücke beherrscht TypeScript. Man kann damit sowohl einen Funktionstyp deklarieren:

var add: (a: number, b: number) => number;

als auch einen Funktionstyp implementieren:

add = (a, b) => a + b;

Beides ist natürlich auch in einer Zeile möglich:

var print = (s: string) => console.log(s);

Ähnlich wie in C# ist auch TypeScript häufig in der Lage, den Typ eines Ausdrucks herzuleiten, ohne dass der Entwickler ihn explizit anführen muss:

// Typherleitung: Add liefert number!var add = (a: number, b: number) => a + b;

Funktionen können natürlich auch Parameter sein, wie es sich für die funktionale Programmierung gehört. Ein Beispiel dafür zeigt Listing 7. Es definiert eine Funkti-

Listing 7: Lambda-Ausdrücke und Clo suresfunction filter(objs: Array<number>, callback: (item: number) =>

boolean): Array<number> {

var ergebnismenge: Array<number> = new Array<number>(); for (var i: number = 0; i < objs.length; i++) {

if (callback(objs[i])) { ergebnismenge.push(objs[i]); } } return ergebnismenge;}

var result = filter([1, 2, 3, 4, 5, 6], (item: number) => item % 2 == 0);ausgabe("Anzahl der Elemente in der Ergebnismenge: " + result.length);

Listing 6: Generische Klasse in TypeScript

export class Verbindung<T1 extends Kontakt, T2 extends Kontakt> { constructor(public k1: T1, public k2: T2) { } public toString(): string { return `${this.k1.name} und ${this.k2.name}`; }}...

var v = new Demo.GO.Verbindung<GO.Kontakt,GO.Kunde>(k1, k2);var ausgabe2 = v.toString();

Page 50: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 55

DOSSIER HTML5 & JavaScript

on filter, die ein übergebenes Array unter Verwendung einer zu übergebenden Funktion filtert. Die Funktion, die an den Parameter callback übergeben wird, nimmt jeweils eine number entgegen und bildet diese auf einen bool ab. Dieser Rückgabewert sagt aus, ob das jewei-lige Element Teil der Ergebnismenge sein soll. Die Im-plementierung der betrachteten Funktion iteriert durch das übergebene Array und ruft für jeden Eintrag die Funktion callback auf. Liefert diese Callback-Funktion dann true, erfolgt der Eintrag in die Ergebnismenge. Das betrachtete Listing demonstriert auch den Aufruf dieser Funktion. Dabei wird das Array [1, 2, 3, 4, 5, 6] sowie der Lambda-Ausdruck (item:number) => item % 2 == 0 übergeben, der dazu führt, dass alle geraden Werte in die Ergebnismenge aufgenommen werden. An der Stelle eines Lambda-Ausdrucks könnte man auch, wie unter JavaScript üblich, eine anonyme Funktion hinterlegen.

Listing 8: Dekoratoren in Aktion

/// <reference path="node_modules/reflect-metadata/reflect.ts" /> console.log("==================== Dekoratorbeispiele"); // einfacher Klassendekorator ohne Factoryfunction klassendekorator(klasse) { console.log("# Klassendekorator: " + klasse);}

// Property-Dekorator mit Factoryfunction range(min: any, max: any) { return function (target: any, name: string, descriptor: TypedPropertyDescriptor<number>) { let set = descriptor.set; console.log("# range1", target, name, descriptor, set); descriptor.set = function (value: number) { let type = Reflect.getMetadata("design:type", target, name); console.log("# range2", value, type); if ((value < min || value > max)) { console.error(`Range-Fehler bei ${name}: Wert ${value} <${min} oder >${max}!`); } else { console.log(`Range-OK bei ${name}: Wert ${value} >${min} und <${max}!`); } }

}}

console.log("==================== Klasse"); @klassendekoratorclass Lottoziehung { private _zahl: number; @range(1, 49) set zahl(value: number) { this._zahl = value; } get zahl() { return this.zahl; } constructor(z: number) { this.zahl = z; }} console.log("==================== Client"); var ziehung1 = new Lottoziehung(1);ziehung1.zahl = 5;ziehung1.zahl = 123;ziehung1.zahl = 40;ziehung1.zahl = 20;ziehung1.zahl = 10;ziehung1.zahl = 800;

DekoratorenTypeScript versucht bisher stets, dem ECMAScript-Standard voraus zu sein. So gibt es bereits erste Mög-lichkeiten, die für eine kommende ECMAScript-Version geplant sind [6], in TypeScript. Die Unterstützung für Dekoratoren war ein Feature, das Google von Mi-crosoft für Angular 2 gefordert hat. Ein Dekorator ist ähnlich wie eine Annotation in Java oder Attribute in .NET: Der Entwickler zeichnet damit Klassen, Proper-ties, Methoden oder Methodenparameter aus. Anders als ein Attribut in .NET ist ein Dekorator aber keine Klasse, sondern eine Funktion, und die Dekoratorfunk-tion wird automatisch zur Laufzeit aufgerufen. Eine Dekoratorfunktion kann das betreffende Sprachkonst-rukt analysieren, modifizieren oder auch ganz ersetzen. Dafür benötigt sie aber eine Reflection-Hilfsbibliothek mit Namen reflect-metadata [7], die eine Polyfill-Imple-mentierung des für ECMAScript Version 7 geplanten

Ein Dekorator ist ähnlich wie eine Annotation in Java oder Attribute in .NET: Der Entwickler zeichnet damit Klassen, Properties, Methoden oder Methodenparameter aus.

Page 51: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 56

DOSSIER HTML5 & JavaScript

Metadata Reflection API darstellt. Wie bereits oben erwähnt, gehören Dekoratoren zu den experimentellen Features, die erst separat im TypeScript-Compiler ak-tiviert werden müssen. Microsoft behält sich hier eine Syntaxänderung vor, wenn der laufende Standardisie-rungsprozess die Syntax noch ändert.

Eine einfache Dekoratorfunktion wird bei der Ini-tialisierung aufgerufen. Eine Dekoratorfunktion kann eine weitere Funktion zurückliefern. Diese Fabrikfunk-

tion wird dann bei jeder Verwendung aufgerufen. Die Parameter der Dekoratorfabrikfunktion variieren je nach Dekoratorart.

Der Dekorator wird dann mit einem @-Zeichen be-ginnend zugewiesen. Listing 8 zeigt zwei Dekoratoren am Beispiel einer Lottozahlziehung, bei der es leider ungültige Zahlen geben kann, was mit dem Property-Dekorator range festgestellt wird. Der Klassendekorator hat keine aktive Funktion in dem Beispiel. Er könnte aber die Klassendefinition verändern, was hier aber aus Platzgründen nicht gezeigt wird.

Ein Sprachkonstrukt kann mehrere Dekoratoren auf-weisen. Die Aufrufreihenfolge ist festgelegt: Erst werden Dekoratoren auf Instanzmitgliedern, dann auf statischen Mitgliedern und dann auf Konstruktoren aufgerufen. Erst zum Schluss folgen Klassendekoratoren. Listing 8 zeigt nur ein Beispiel ausgewählter Möglichkeiten.

Module und Verweise

TypeScript kann zusammen mit einem Modulsystem (Abb. 1 und 2) oder ohne Modulsystem (Auswahl in Vi-sual Studio in Abbildung 2 None) verwendet werden. Ohne Modulsystem müssen:

• die verwendeten TypeScript-Dateien mit einem<reference>-Tag zu Beginn einer .ts-Datei eingebun-den werden: /// <reference path="modulA.ts" />.Solch ein Tag entsteht in Visual Studio per Drag andDrop einer .ts-Datei in eine andere.

• alle verwendeten TypeScript-Dateien explizit per<script>-Tag in die HTML- Datei eingebunden wer-den: <script src="ModulA.js"></script>. Solch einTag entsteht in Visual Studio per Drag and Dropeiner .ts-Datei in eine HTML-Datei. Alternativdazu kann man mit der Compileroption ­­out alle

Listing 11: „app.ts“

/// <reference path="../jslibs/jquery.d.ts" />import {xy} from './ModulB' // ModulB.ts enthält noch mal

// explizites Modulimport * as A from './ModulA'

export function main() {

$("#C_Ausgabe").append(new Date() + "<hr>"); var ma = new A.KlasseA(); var a1 = "Modul A: " + ma.test() console.log(a1); $("#C_Ausgabe").append(a1 + "<br>");

var mb = new xy.KlasseB(); var a2 = "Modul B: " + mb.test() console.log(a2); $("#C_Ausgabe").append(a2 + "<br>");}

main();

Listing 10: „ModulB.ts“

import {KlasseA} from './ModulA'export module xy { // Class export class KlasseB { test(): string {

var a1 = new KlasseA(); return ("1. " + a1.test() + " 2. KlasseB:Test"); } }}

Listing 9: „ModulA.ts“

export class KlasseA { test(): string { return ("KlasseA: Test"); }}

Listing 12: „index.html“

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"> <head> <script src="/JSLibs/jquery-2.0.3.js"></script> <script src="jspm_packages/system.js"></script> <script> //SystemJS konfigurieren System.config({ defaultJSExtensions: true }); System.import('app').catch(console.error.bind(console));</script> </head> <body> Modulclient <hr /> <div id="C_Ausgabe"></div> </body></html>

Page 52: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 57

DOSSIER HTML5 & JavaScript

TypeScript-Dateien in eine .js-Datei übersetzen. Dann muss nur diese eine .js-Datei referenziert werden.

• optional können innerhalb einer TypeScript-Dateimit dem Schlüsselwort namespace Namensräumegebildet werden. Wenn Namensräume zum Einsatzkommen, müssen alle zu exportierenden Klassen undFunktionen den Zusatz export erhalten (Listing 5).

Mit einem Modulsystem bildet jede TypeScript-Datei ein eigenständiges Modul. Das Modulsystem erfordert immer eine Zusatzbibliothek zur Laufzeit, wie z.  B. RequireJS [8] oder SystemJS [9], die eingebunden und konfiguriert werden muss. Der Nutzer muss dann die zu verwendeten Klassen explizit importieren:

• Import einer Klasse: import {KlasseA} from './ModulA'• Import aller Exporte: import * as A from './ModulA'

Damit entfallen dann aber <reference>- und <script>-Tags für jedes Modul (Listing 9 bis 12). In diesem Fall kann man mit dem Schlüsselwort module innerhalb ei-ner Datei verschiedene Module bilden (Listing 9).

DeklarationsdateienListing 11 zeigt die Einbindung einer Deklarationsdatei (d.ts). Deklarationsdateien sind enorm wichtig, denn sie liefern die TypeScript-Typdefinitionen für beste-hende JavaScript-Bibliotheken, die nicht in TypeScript geschrieben wurden. Dadurch kann der TypeScript-Entwickler auch mit solchen Bibliotheken typsicher und mit Eingabeunterstützung arbeiten. Listing  13 zeigt einen Ausschnitt aus jquery.d.ts zur Deklaration der jQuery-Schnittstelle. Ein großer Fundus für solche Deklarationsdateien ist [10].

Es ist notwendig, die benötigen Deklarationsdateien zu Beginn in eine Type Script-Datei einzubinden:

/// <reference path="libs/typings/jquery/jquery.d.ts" />

Bei den drei Schrägstrichen handelt es sich um keinen Schreibfehler, sondern um die Konvention, über die Vi-

sual Studio solche Kommentare erkennt, die Zusatzin-formationen beinhalten.

FazitJavaScript erweitert zwar nun langsam seine Sprach-features: Kaum ein Browser versteht heute schon ECMAScript 2015. Mit TypeScript kann man alle ECMA Script-2015-Features und mehr jedoch bereits heute überall nutzen.

Auch wenn dieser Beitrag nicht alle TypeScript-Sprachfeatures darstellen konnte, hat er doch klar aufgezeigt, dass TypeScript dem Programmierstil we-sentlich näher liegt, der aus OOP-Hochsprachen wie C#, Visual Basic .NET und Java bekannt ist, als reines JavaScript. Gerade wenn man nun von solchen Spra-chen auf die Webentwicklung umsteigen will (oder muss), gibt es keinen Grund, warum man noch direkt mit JavaScript arbeiten sollte.

Ein Kollege in unserer Firma meinte letztens, es wäre wichtig, auch das, was hinten rauskommt, zu beherr-schen. Ich habe dem entgegnet: Gut, dann muss du ja nun auch MSIL-Bytecode beherrschen, wenn du C# schreibst.

Natürlich ist das Verhältnis von JavaScript zu Type-Script noch etwas anders, weil TypeScript ja eine Ober-menge von JavaScript ist, C# aber nicht von MSIL. Aber ich meine tatsächlich, dass man heute nicht mehr lernen muss, wie man mit Prototypen eine Vererbung in Ja-vaScript nachbildet, wenn man TypeScript hat, das den lästigen und unübersichtlichen Code für den Entwickler generiert.

Dr. Holger Schwichtenberg, alias „der Dotnet-Doktor“, ist tech-nischer Leiter des auf .NET und Webtechniken spezialisierten Be-ratungs- und Schulungsunternehmens www.IT-Visions.de mit Sitz in Essen. Er gehört durch zahlreiche Fachbücher und Vorträge auf Fachkonferenzen zu den bekanntesten Experten für .NET und Vi-

sual Studio in Deutschland.

[email protected] www.dotnet-doktor.de, www.it-visions.de

Listing 13: Ambiente Deklarationen für jQuery („jquery.d.ts“)

interface JQuery { ... replaceAll(target: any): JQuery; replaceWith(func: any): JQuery; text(): string; text(textString: any): JQuery; text(textString: (index: number, text: string) => string): JQuery; toArray(): any[]; unwrap(): JQuery;..}

Links & Literatur

[1] Install-Package Microsoft.TypeScript.Compiler

[2] npm install -g typescript

[3] http://jsfiddle.net/TheRoks/8uCT9/

[4] http://www.typescriptlang.org/play/

[5] https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.docx?raw=true

[6] https://github.com/tc39/ecma262/blob/master/README.md

[7] https://www.npmjs.com/package/reflect-metadata

[8] http://requirejs.org/

[9] https://github.com/systemjs/systemjs

[10] https://github.com/DefinitelyTyped/DefinitelyTyped

Page 53: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 58

DOSSIER HTML5 & JavaScript

von Rainer Stropek

Meiner Ansicht nach versteht man Softwareentwick-lungskonzepte am besten, wenn man sich klarmacht, welches Problem damit gelöst wird. Lassen Sie uns unse-re Reise zu async/await damit beginnen. JavaScript-Ent-wicklung sowohl am Client als auch am Server hat viel mit asynchroner Programmierung zu tun. Wann immer I/O-Operationen ausgeführt werden sollen, hat man kei-ne andere Wahl, als Funktionen asynchron aufzurufen. Auch wenn die Details sich von Fall zu Fall unterschei-den, das Grundkonzept ist immer das Gleiche: Man gibt im Funktionsaufruf eine Callback-Funktion an, die auf-gerufen werden soll, wenn die Operation fertig ist. Das

Problem dabei ist, dass die Struktur des Codes aufgrund der Callbacks nicht mehr dem Algorithmus entspricht, den man eigentlich programmieren möchte. Die techni-sche Notwendigkeit der asynchronen Programmierung diktiert die Struktur des Codes. Dadurch wird er schwer zu lesen und zu warten.

Lassen Sie uns dieses Problem zum besseren Verständ-nis an einem Beispiel betrachten. Angenommen wir möchten den nachfolgenden, einfachen Algorithmus umsetzen:

• Öffne Verbindung zur Datenbank• Lies alle Personen mit Vornamen „John“• Iteriere über alle ermittelten Personen

TypeScript lernt async/await

Asynchrones TypeScript In JavaScript sind viele APIs asynchron. Im Gegensatz zu .NET stehen synchrone Funktionen zum Zugriff auf Netzwerk, Datenbanken oder das Dateisystem überhaupt nicht zur Verfügung. Die Folge ist, dass man in JavaScript-Programmen vor lauter Callbacks häufig den eigentli-chen Algorithmus nicht mehr sieht. Der Code ist schwer lesbar und fehleranfällig. Frameworks wie Async [1] verbessern die Situation, sind aber gewöhnungsbedürftig. Mit der Version 1.7 hat TypeScript in Sachen asynchrone Funktionen mit C# gleichgezogen: async/await wurde eingeführt. Dieser Artikel zeigt, welche Verbesserungen async/await bringt, wie das Sprach-konstrukt einzusetzen ist und welche Einschränkungen es aktuell gibt.

©iS

tock

pho

to.c

om

/lvc

and

y

Page 54: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 59

DOSSIER HTML5 & JavaScript

• Wenn die aktuelle Person ein Kunde ist, lies die Kun-dendaten und gib Personen- und Kundendaten amBildschirm aus

• Wenn die aktuelle Person ein Lieferant ist, lies dieLieferantendaten und gib Personen- und Lieferanten-daten am Bildschirm aus

• Schließe die Datenbankverbindung

Diesen Algorithmus möchten wir mit TypeScript und Node.js programmieren. Als Datenbank verwenden wir exemplarisch MongoDB. (Falls Sie mit dem Code expe-rimentieren wollen, finden Sie ihn auf GitHub [2]) .

„Callback Hell“Listing 1 zeigt die Umsetzung unseres Algorithmus mit TypeScript ohne Verwendung von Frameworks wie Async oder async/await. Wie Sie sehen, wird bei jedem MongoDB-Aufruf eine Callback-Funktion angegeben. Der Code wird dadurch schwer verständlich. Man spricht daher auch von der „Callback Hell“. Beden-

ken Sie, dass unser Beispiel bewusst einfach gehalten ist. Echte Programme in der Praxis sind weitaus kom-plexer, das Problem wird dadurch noch verschärft. Ganze Webseiten wie etwa unter [3] widmen sich der Frage danach, wie man Code, der Callbacks intensiv verwendet, trotzdem noch halbwegs gut strukturieren kann. Aber auch diese Lösungen sind alles andere als optimal.

AsyncFrameworks wie Async bieten Hilfsfunktionen, um der Callback Hell zu entkommen. Statt endlos ver-schachtelter Callbacks hat man Konstrukte wie async.Waterfall, um eine Sequenz von asynchronen Funkti-onsaufrufen auszuführen. Es würde den Rahmen dieses Artikels sprengen, wenn wir das Async­Framework hier im Detail beschreiben. Wer sich einen tiefergehenden Überblick über die gebotenen Funktionen verschaffen möchte, findet die Dokumentation unter [1].

Listing 2 zeigt unser Beispiel mit Async. Man sieht, dass der Code dem eigentlichen Algorithmus schon näher kommt, die Umsetzung hat allerdings noch im-mer eine Menge Nachteile. Das API von Async ist für Anfänger erfahrungsgemäß schwer verständlich. Der Code ist gespickt mit Callbacks in Form von Lambda-Funktionen. Sie sind nicht von Natur aus notwendig. Es ist Komplexität, die sich aus den Einschränkungen der Sprache im Umfang mit den asynchronen Funktionen ergibt. Es muss also noch besser gehen.

Listing 1: Asynchroner Code mit Callbacks

import * as mongodb from 'mongodb';

// Open Databasemongodb.MongoClient.connect("mongodb://10.0.75.2:27017/demo", (err,

db) => { // Read all persons with first name "John" db.collection("Person", (err, coll) => { coll.find({ firstName: "John" }).toArray((err, res) => { // In order to close DB after we are done, we have to use a counter var counter = res.length; var closedb = () => { if (--counter == 0) db.close(); };

// For each person for (var i = 0; i < res.length; i++) { var p = res[i];

// If Person is customer if (p.isCustomer) { // Read customer details for person db.collection("Customer", (err, custColl) => { custColl.findOne({ id: p.customerId }, (err, cust) => {

// Print person and customer detailsconsole.log('John ${p.lastName} works for ${cust.

customerName}.');

// Call helper function to close DB if doneclosedb();

}); }); } else { // Read supplier details for person db.collection("Supplier", (err, supplColl) => { supplColl.findOne({ id: p.supplierId }, (err, suppl) => {

// Print person and supplier detailsconsole.log(''John ${p.lastName} works for ${suppl.

supplierName}.');

// Call helper function to close DB if doneclosedb();

}); }); } } }); });});

Video-Link:„Vor der App kommt die (Ser-vice-)Architektur!“ Interview mit Christian Weyer

Page 55: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 60

DOSSIER HTML5 & JavaScript

async/awaitListing  3 zeigt unser Beispiel mit async/await. Keine Sorge, falls das Grundkonzept von async/await neu für Sie ist; wir werden gleich zu den Details kommen. Zuvor werfen Sie bitte einen Blick auf den Code, den wir mit async/await schreiben können. Der TypeScript-Code entspricht fast eins zu eins dem Algorithmus, den wir umsetzen wollten. Er ist leicht zu schreiben und

leicht verständlich. Genau das ist die Motivation hinter async/await.

PromisesLassen Sie uns jetzt einen genaueren Blick auf die Funk-tionsweise von async/await in TypeScript werfen. Tech-nisch gesehen unterbricht await die Ausführung der aufrufenden Funktion, bis die jeweilige asynchrone Ope-

Listing 3: async/await

import * as mongodb from ‘mongodb’;

async function run() { // Open Database var db = await mongodb.MongoClient.connect("mongo db://10.0.75.2:27017/demo");

// Read all persons with first name "John" var persons = await db.collection("Person").find({ firstName: "John" }).toArray();

// For each person for (var i = 0; i < persons.length; i++) { var p = persons[i];

// If Person is customer if (p.isCustomer) { // Read customer details for person

var cust = await db.collection("Customer").findOne({ id: p.customerId });

// Print person and customer details console.log(`John ${p.lastName} works for ${cust.customerName}.`); } else { // Read supplier details for person var suppl = await db.collection("Supplier").findOne({ id: p.supplierId });

// Print person and supplier details console.log(`John ${p.lastName} works for ${suppl.supplierName}.`); } }

db.close();}

run();

Listing 2: Asynchroner Code mit Callbacksimport * as mongodb from 'mongodb';import * as async from 'async';

var database: mongodb.Db;async.waterfall([ // Open Database callback => mongodb.MongoClient.connect("mongodb://10.0.75.2:27017/ demo", callback),

// Read all persons with first name "John" (db, callback) => { database = db; db.collection("Person", callback); }, (coll, callback) => coll.find({ firstName: "John" }).toArray(callback), (res, callback) => { // In order to call the callback after we are done, we have to use a counter var counter = res.length; var markOneAsProcessed = () => { if (--counter == 0) callback(); };

// For each person for (var i = 0; i < res.length; i++) { var p = res[i];

// If Person is customer if (p.isCustomer) { async.waterfall([

// Read customer details for person callback => database.collection("Customer", callback), (custColl, callback) => custColl.findOne({ id: p.customerId }, callback),

// Print person and customer details (cust, callback) => { console.log(`John ${cust.lastName} works for ${cust.customerName}.`); callback(); }, ], (err, result) => markOneAsProcessed()); } else { async.waterfall([ // Read supplier details for person callback => database.collection("Supplier", callback), (supplColl, callback) => supplColl.findOne({ id: p.supplierId }, callback),

// Print person and supplier details (suppl, callback) => { console.log(`John ${suppl.lastName} works for ${suppl.customerName}.'); callback(); }, ], (err, result) => markOneAsProcessed()); } } }], (err, result) => database.close());

Page 56: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 61

DOSSIER HTML5 & JavaScript

ration fertig ist. Sobald das der Fall ist, wird die Ausfüh-rung beim nächsten Statement der aufrufenden Funktion automatisch wieder aufgenommen. Die Voraussetzung dafür, dass in einer Funktion await verwendet werden kann, ist das Präfix async. Listing 3 zeigt dies gleich in der ersten Zeile nach import.

Worauf kann await warten? Woher weiß await, wann die aufgerufene asynchrone Operation fertig ist? Schreibt der TypeScript-Compiler den Code um, sodass im Endef-fekt wieder die Callbacks aus Listing 1 herauskommen? Nein, async/await verwendet ECMAScript 2015 Promi­ses [4]. Jede Funktion, die ein Promise-Objekt zurück-gibt, kann in Verbindung mit await verwendet werden.

Ein Promise-Objekt ist ein Proxy für ein Funktionser-gebnis, das erst später vorliegen wird, da es asynchron ermittelt werden muss. Das Objekt hat einen von drei Zuständen:

• Pending: Initialer Status, die asynchrone Operationläuft noch.

• Fulfilled: Die asynchrone Operation wurde erfolg-reich beendet.

• Rejected: Die asynchrone Operation wurde mit Feh-ler beendet.

In unserem Beispiel habe ich bewusst auf MongoDB als Datenbank zugrückgegriffen, da das Node.js-API von

MongoDB sowohl Callbacks als auch Promises unter-stützt. Gibt man beim Aufruf von MongoDB-Funktionen keinen Callback an, bekommt man ein Promise-Objekt zurück, auf das man mit await warten kann. Was macht man aber, wenn man ein Callback-basierendes, asynchro-nes API hat, das noch keine Promises unterstützt? Es ist recht einfach, sich selbst einen Wrapper zu schreiben, der aus einem Callback ein Promise-Objekt macht. Listing 4 zeigt zwei Beispiele. Das erste, die Methode sleep(), macht aus setTimeout() ein awaitable Promise-Objekt. Die zweite Methode verwendet die Library Needle, um ein RESTful Web-API aufzurufen. Beachten Sie im zweiten Fall, dass das Promise-Objekt abhängig vom Ergebnis des Web-API-Aufrufs resolved oder rejected ist.

GeneratorsWer besonders neugierig ist und ganz genau wissen möchte, wie man async/await Promises im Hinter-grund verwendet, dem empfehle ich, einen Blick auf den vom Type Script-Compiler erzeugten JavaScript-Code zu werfen. Sie werden sehen, dass der entste-hende Code Promises in Verbindung mit ECMAScript 2015 Generators [5] nutzt. Sich durch den generierten JavaScript-Code zu graben, ist für die erfolgreiche Ver-wendung von async/await zwar nicht notwendig – die beiden neuen Schlüsselwörter sollen Ihnen schließlich die technischen Details abnehmen. Eine interessante Übung ist es aber allemal, also los.

Sind C#-Entwickler unter den Lesern? Für sie kann ich die Sache abkürzen: Generators sind wie Iterator­Blocks mit yield return in C#. Sowohl in C# als auch in TypeScript handelt es sich um Funktionen, die verlas-sen werden, dabei ein Ergebnis zurückgeben und später wieder betreten werden. Der gesamte Kontext, also die

Listing 4: Promises

interface IForm { name: string;}

interface IPokemon { forms: IForm[];}

function sleep(seconds: number) : Promise<void> { return new Promise<void>((resolve, reject) => setTimeout(() => resolve(), seconds * 1000));}

function getPokemonName(pokemonId: number): Promise<string> { return new Promise<string>((resolve, reject) => {

needle.get(`http://pokeapi.co/api/v2/pokemon/${pokemonId}/`, (err, res) => {

if (!err && res.statusCode == 200) { let pokemon = <IPokemon>res.body; resolve(pokemon.forms[0].name); } else { reject("Could not get pokemon data"); } }); });}

Listing 5: Generatorfunction *gen() : Iterable<string> { for(let i=0; I < 5; i++) { console.log("Yielding item..."); yield `Item ${i}`; console.log("Yielded item."); }}

for (let s of gen()) { console.log(`Got item ${s}`);}/* * This will print:Yielding item...Got item Item 0Yielded item.Yielding item...Got item Item 1Yielded item....

*/

Page 57: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 62

DOSSIER HTML5 & JavaScript

lokalen Variablen inklusive ihrer Werte, bleibt dabei erhalten.

Listing 5 zeigt ein Beispiel. Die Methode gen() ist eine Generator­Funktion. Der Stern vor dem Funk-tionsnamen kennzeichnet sie als solche. Mit dem Schlüsselwort yield wird die Ausführung der Methode unterbrochen und ein Ergebnis, hier eine Zeichenket-te, zurückgegeben. Die aufrufende Funktion erhält die Kontrolle. Sie durchläuft in diesem Beispiel mit einer for..of-Schleife das Ergebnis der Generator­Funktion. Wichtig ist, dass bei jedem weiteren Schleifendurchlauf wieder zurück in die gen()-Methode gesprungen wird. Diese startet nicht wieder von vorne, sondern führt die Ausführung beim nächsten Statement nach dem letzten yield fort. Alle Variablen, in unserem Beispiel vor allem die Laufvariable i, bleiben erhalten.

Was haben die Generators also mit async/await zu tun? Sie kümmern sich darum, dass nach dem Been-den einer asynchronen Operation die Ausführung der Funktion an der richtigen Stelle wiederaufgenommen wird. Der Vergleich von Listing 6 (TypeScript) und Lis-ting 7 (generierter JavaScript-Code) macht klar, was damit gemeint ist. Aus dem await in TypeScript wird yield in JavaScript. Wie wir zuvor schon gesehen ha-ben, gibt yield die Kontrolle an die aufrufende Funkti-on zurück, sorgt dafür, dass der Kontext gesichert wird und setzt die Ausführung später genau an der richtigen Stelle fort.

FehlerbehandlungZur klareren Struktur des Codes trägt bei async/await auch die Fehlerbehandlung bei: Man kann nämlich try/catch verwenden. Wird das zugrunde liegende Promise-

Listing 8: Parallele asynchrone Funktionen

async function run() : Promise<void> { ... // Siehe Listing 6}run();

/* * Note that run does NOT block the execution of the program. * Therefore, the following lines start another async. operation * in parallel. As a result the program will print something like: Heartbeat... Heartbeat... Heartbeat... Pokemon 25 is pikachu. Heartbeat... Error "Could not get pokemon data" happened. Heartbeat... */ var cb = () => { console.log("Heartbeat..."); setTimeout(cb, 1000); };cb();

Listing 7: Generierter JavaScript-Code

function run() { return __awaiter(this, void 0, Promise, function* () { try { var name = yield getPokemonName(25); console.log(`Pokemon ${25} is ${name}.`);

// The following line will produce an exception name = yield getPokemonName(99999); } catch (ex) { console.log(`Error "${ex}" happened.`); } });}

Listing 6: Fehlerbehandlung

async function run() : Promise<void> { try { var name = await getPokemonName(25); console.log(`Pokemon ${25} is ${name}.`);

// The following line will produce an exception name = await getPokemonName(99999); } catch(ex) { console.log(`Error "${ex}" happened.`); }}

Web-APIs mit Node.js und TypeScript – für .NET-Entwickler

Manuel Rauber

Full-Stack TypeScript: Im Frontend werkeln moderne Single-Page-Application-Frame-works wie Angular, während im Hintergrund

leichtgewichtige Web-APIs zur Kommunikation arbei-ten. Dank Node.js sprechen beide Welten eine ge-meinsame Sprache: TypeScript – ein um Typen ange-reichertes Superset von JavaScript. Ob async/await, Klassen, Eigenschaften oder Generics – all das ist mit TypeScript möglich. Lassen Sie uns gemeinsam einen Blick in die Node.js-Welt mit TypeScript werfen und dabei Aspekte von modernen Web APIs – wie die Anbindung von Datenbanken oder die Echtzeit-kommunikation – beleuchten. Mit wenigen Zeilen Code schafft Manuel Rauber von Thinktecture eine Grundlage für Ihr „Next Generation“ Web-API.

Besuchen Sie auch folgende Session:

Page 58: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 63

DOSSIER HTML5 & JavaScript

Objekt rejected, kommt es automatisch zu einer Excep­tion. Listing 6 zeigt ein Beispiel. In einem try-Block wird die in Listing 4 gezeigte Methode getPokemonName() aufgerufen. Darin wird das Promise-Objekt rejected, falls kein Pokémon mit der spezifizierten ID gefunden werden konnte. Dadurch kommt es zu einer Exception und der catch-Block wird ausgeführt.

Gleichzeitige, asynchrone OperationenEs ist wichtig zu bedenken, dass durch await nicht die gesamte Programmausführung gestoppt wird. Es ist möglich, mehrere parallellaufende asynchrone Funk-tionen zu starten. Listing 8 zeigt das Prinzip. Die Me-thode run() ruft Web-APIs asynchron auf (Listing 6). Das Hauptprogramm läuft aber weiter. Dadurch kann es weitere asynchrone Funktionen starten (Listing 8). Durch setTimeout() wird ein Timer gestartet, der jede Sekunde eine Meldung am Bildschirm ausgibt. Timer und Web-APIs laufen parallel. Durch async/await wird bei Ende einer asynchronen Methode die Program-mausführung an der jeweils richtigen Stelle wieder auf-genommen.

LimitierungenKlingt alles zu schön um wahr zu sein? Zugegeben, einen Nachteil hat async/await in TypeScript (noch): Man kann die Schlüsselwörter nur verwenden, wenn man ECMAScript  6 (ES6) bzw. ECMAScript  2015 (ES2015) als Zielumgebung wählt. Diese Option kann man entweder in der Kommandozeile dem TypeScript-Compiler tsc mit der Option ­­target übergeben oder sie in der .tsconfig­Datei einstellen. Um in den Genuss von async/await zu kommen, braucht man also eine ES2015-kompatible Ausführungsumgebung. Aktuelle Node.js-Versionen erfüllen diese Anforderung. Viele Browser, die Web entwickler (noch) unterstützen müs-sen, jedoch noch nicht.

Die gute Nachricht ist, dass Microsoft an der Umset-zung von async/await für ES5 und ES3 arbeitet. Sie ist für die TypeScript-Version 2.1 angekündigt  [6]. Dass Microsoft die Unterstützung der alten ECMAScript-Ver-sionen nicht von Anfang an eingebaut hat, ist nachvoll-ziehbar. Dort stehen weder Generators noch Promises zur Verfügung. Der generierte ECMAScript-Code wird daher kaum mehr Ähnlichkeiten mit dem geschriebenen TypeScript-Code haben. Vor solchen weitreichenden Generierungen von Code ist Microsoft bisher im Ty-peScript-Compiler zurückgeschreckt. Das Feedback der TypeScript-Community war aber so deutlich, dass man sich schließlich umstimmen ließ.

FazitC#-Entwickler haben schon vor langer Zeit async/await kennen und lieben gelernt. Jetzt liefert Microsoft diesen Produktivitäts-Boost auch in TypeScript. Der Code wird leichter zu schreiben, besser lesbar und damit stabiler und leichter zu warten. Die Node.js-User unter den Le-sern können sofort loslegen und async/await verwenden.

Webentwickler brauchen eventuell noch etwas Geduld. Das Rückwärtskompilieren nach ECMAScript 5 oder sogar 3 steht noch nicht zur Verfügung, ist aber für die Version 2.1 von TypeScript bereits angekündigt. Alles in allem ist async/await ein wichtiger Schritt für die Spra-che TypeScript und ein weiterer Grund, TypeScript statt JavaScript zu schreiben.

Rainer Stropek ist IT-Unternehmer, Softwareentwickler, Trainer, Autor und Vortragender im Microsoft-Umfeld. Er ist seit 2010 MVP für Microsoft Azure und Microsoft Regional Director. In seiner Fir-ma entwickelt Rainer mit seinem Team die Zeiterfassung für Dienstleistungsprofis time cockpit.

www.timecockpit.com

Links & Literatur

[1] http://caolan.github.io/async/

[2] https://github.com/rstropek/Samples/tree/master/TypeScriptAsyncAwait

[3] http://callbackhell.com/

[4] https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise

[5] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator

[6] https://github.com/Microsoft/TypeScript/wiki/RoadmapAPI Summit

Yarn – npm, nur besserRainer Stropek

npm ist aus der modernen Softwareent-wicklung nicht wegzudenken. Egal ob Ty-peScript-Compiler, DevOps CLI Tools oder

JavaScript-Framework, alles kommt aus npm. Umso wichtiger ist es, ein performantes, verlässliches Tool zu haben, um seine npm-Pakete zu verwalten. Yarn hat in dieser Hinsicht die Nase gegenüber npm in ei-nigen Punkten vorne: Package Caching, Security, Stabilität und Offlineunterstützung sind nur einige Beispiele dafür. In dieser Session stellt Rainer Stro-pek Yarn vor. Er demonstriert die Unterschiede zu npm und bringt Anwendungsbeispiele für Server (Node.js) und Client (Angular).

Besuchen Sie auch folgende Session:

Page 59: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 64

DOSSIER User Interface

von Roman Schacherl und Daniel Sklenitzka

Jetzt haben wir uns gerade erst damit abgefunden, dass Konsolenanwendungen (CLI, Command Line Interfaces) nicht der Gipfel der Usability sind. Nach GUI kam NUI (Natural User Interface) – wobei sich die Interpretation zum Teil nur auf die Verwendung des Fingers statt der Maus reduzierte. Und dann kamen Siri, Google Now und Cortana. Plötzlich finden Benutzer Gefallen daran, mit Software zu kommunizieren – sei es mündlich oder schriftlich. Nicht die TV-App zu starten, den Sender aus-zuwählen, ins Hauptabendprogramm zu scrollen und die Beschreibung zu öffnen – sondern das Vorhaben einfach in Worte zu gießen: „Was läuft heute Abend auf 3sat?“.

Genau in diese Kerbe schlagen Conversational User Interfaces (CUI). Die Schnittstelle zwischen Mensch und Computer nähert sich immer mehr einer Mensch-zu-Mensch-Kommunikation an und verdient immer öfter die Bezeichnung „intelligent“. Damit das zuverläs-sig funktioniert, ist ein tiefes Verständnis von Sprache erforderlich. Gesprochene Sätze müssen analysiert und in Text umgewandelt werden, bei geschriebenen Texten dürfen auch kleinere Tipp- oder Rechtschreibfehler der Erkennung keinen Abbruch tun. Darüber hinaus muss ein CUI über den aktuellen Kontext Bescheid wissen: Wer spricht? Was wurde bereits gesagt? Was muss nicht gesagt, kann aber aus anderen Informationsquellen (In-ternet, Kalender, Kontakte) ermittelt werden? Was wur-

Conversational UIs: Ein erster Blick auf das Microsoft Bot Framework

Das GeBOT der Stunde?Die Build-Konferenz brachte Ende März mit dem Microsoft Bot Framework eine unerwartete Neuigkeit. Entwicklern soll es leicht gemacht werden, eigene Bots zu schreiben – also mög-lichst intelligente Programme, die in einem Chat Antworten geben und Aktionen durchführen können. Was steckt dahinter? Wo hilft das Framework? Und: Wozu eigentlich?

©iStock

photo.com/kompozit

Page 60: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 65

DOSSIER User Interface

de bereits erlernt und kann als gegeben hingenommen werden? Es ist das Ende von harten Fakten, Interpre-tation ist angesagt: „um 10 Uhr“ bedeutet am Morgen etwas anderes als am späten Abend.

Kurzum: Das Entwickeln von Conversational User Interfaces erfordert weitaus mehr als ein paar aneinan-dergereihte if-Statements, die dem Benutzer vordefinier-te Phrasen zurückwerfen. Es geht um das Verständnis von Sprache – mit allem, was dazugehört.

Das Microsoft Bot FrameworkDas Microsoft Bot Framework [1] stellt einen Werk-zeugkasten für die Entwicklung derartiger Schnittstel-len – eben Bots – zur Verfügung. Es unterstützt bei der Entwicklung (Bot Builder SDK), der Verteilung/Bewer-bung (Bot Directory) und der Integration von Bots in andere Kommunikationsplattformen (Bot Connector). Darüber hinaus stehen Cognitive Services  [2] bereit,

um beispielsweise beim Verständnis von Texten oder Bildern assistierend zur Seite zu stehen – nicht aus-schließlich für Bots, aber gerade dort sehr hilfreich.

Bot Builder SDKDas SDK steht auf GitHub unter einer Open-Source-Lizenz sowohl für C# als auch für Node.js zur Ver-fügung. Dieser Artikel beschäftigt sich im Folgenden mit der C#-Variante. Im Wesentlichen ist ein Bot nichts anderes als ein REST-Service, der ein bestimmtes Pro-tokoll spricht:

public async Task<Message> Post([FromBody]Message message){ ... }

Man bekommt also eine Message, und muss auch wie-der eine Message zurückliefern, wobei immer sämtliche Informationen des Gesprächs hin- und hergeschickt werden – die Implementierung muss zustandslos erfol-gen, damit der Bot später beliebig skaliert werden kann. Darüber hinaus beinhaltet eine Message diverse Meta-informationen (Sprache, Teilnehmer, …).

Listing 1

public enum AmountType{ Hours, Days}

[Serializable]public class Alert{ public string Project { get; set; } public int Amount { get; set; } public AmountType AmountType { get; set; }}

Listing 2

[Serializable]public class AlertDialog : IDialog<Alert>{ private Alert alert;

public async Task StartAsync(IDialogContext context) { context.Wait(MessageReceivedAsync); }

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<Message> argument) { var message = await argument; await context.PostAsync("Ich habe Sie leider nicht verstanden."); context.Wait(MessageReceivedAsync); }}

Video-Link:Entwickeln für die HoloLens & Microsoft Bot Framework – Interview mit Roman Schacherl

Build, Test, Distribute: App-Entwick-lung mit Visual Studio Mobile Center automatisieren

Jörg Neumann

Großartige Apps entstehen nur durch ein kon-tinuierliches Einbeziehen der Anwender. Direk-tes, zeitnahes Feedback und die Analyse des

Nutzungsverhaltens sind hierbei essenziell. Doch wie schafft man es, eine lauffähige App nach jedem Sprint automatisiert auf die unterschiedlichen Devices der Be-tatester zu verteilen? Das Visual Studio Mobile Center bietet hierfür eine elegante Lösung. Es bietet nicht nur eine Verteilung über die Cloud, sondern bringt mit sei-nen Analyse- und Feedbackmöglichkeiten alles mit, um den Anwender in die Entwicklung zu integrieren. Zudem bietet es ein Mobile Backend sowie eine Test-Cloud-Integration. Jörg Neumann stellt das System vor und zeigt Ihnen, wie Sie Ihren Entwicklungsprozess vollstän-dig automatisieren können.

Besuchen Sie auch folgende Session:

Page 61: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 66

DOSSIER User Interface

Man kann entweder mit einer leeren ASP.NET-An-wendung starten, das SDK über NuGet hinzufügen (Microsoft.Bot.Connector) und einen entsprechenden HTTP-Endpunkt implementieren – oder man installiert das Bot Application Template [3], das einem bei der Er-stellung eines Projekts diese Schritte abnimmt.

Für die weitere Implementierung dieses Service stellt das SDK über ein weiteres NuGet-Package Microsoft.Bot.Builder zwei verschiedene Hilfestellungen zur Verfügung: Dialoge und FormFlow. Diese sollen nun anhand desselben Beispiels vorgestellt werden: Ziel ist es, einen Bot für ein Projektmanagement- und Zeiter-

fassungssystem zu entwickeln, der einem bei der Ein-richtung von Benachrichtigungen behilflich ist. Diese sollen versandt werden, wenn in einem Projekt ein bestimmter Teil des (Zeit-)Budgets aufgebraucht ist. Der Bot muss also in Erfahrung bringen, für welches Projekt und nach welchem Zeitraum (in Stunden oder Tagen) die Benachrichtigung erstellt werden soll. Lis-ting 1 zeigt eine entsprechende Klasse.

DialogeDer Begriff „Dialog“ ist im Bot Framework im ur-sprünglichen Wortsinn zu verstehen – und nicht wie

Listing 4private async Task ProjectReceivedAsync(IDialogContext context,

IAwaitable<string> result){ this.alert = new Alert() { Project = await result };

PromptDialog.Choice( context, AmountTypeReceivedAsync, new[] { "Tagen", "Stunden" }, $"Soll der Alarm für {this.alert.Project} nach Stunden oder Tagen

erfolgen?");}

private async Task AmountTypeReceivedAsync(IDialogContext context, IAwaitable<string> result)

{ var amountType = await result; this.alert.AmountType = (amountType == "Tage" ? AmountType.Days :

AmountType.Hours);

PromptDialog.Number( context, AmountReceivedAsync, $"Nach wie vielen {(this.alert.AmountType == AmountType.Days ?

"Tagen" : "Stunden")} soll der Alarm eingestellt werden?");

}

private async Task AmountReceivedAsync(IDialogContext context, IAwaitable<long> result)

{ this.alert.Amount = (int)await result;

PromptDialog.Confirm( context, FinishedAsync, $"Möchtest du einen Alarm für das Projekt {alert.Project} nach {alert.Amount}{(alert.AmountType == AmountType.Days ? "Tagen" : "Stunden")}

setzen?");}

private async Task FinishedAsync(IDialogContext context, IAwaitable<bool> result)

{ if (await result) { // add alarm

context.Done(this.alert); } else { context.Done<Alert>(null); }}

Listing 3

private async Task MessageReceivedAsync (IDialogContext context,IAwaitable<Message>

argument){ var message = await argument;

if (message.Text.Equals("neuer alarm", StringComparison.CurrentCultureIgnoreCase))

{ PromptDialog.Text(context, ProjectReceivedAsync,

"Für welches Projekt?", "Entschuldigung - welches Projekt?"); }

else { await context.PostAsync("Ich habe Sie leider

nicht verstanden"); context.Wait(MessageReceivedAsync); }}

Page 62: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 67

DOSSIER User Interface

sonst in der Softwareentwicklung üblich als Synonym für ein Anwendungsfenster. Ein Dialog ist eine – im besten Falle wiederverwendbare – kurze Konversati-on zwischen Benutzer und Bot, die eine bestimmte In-formation als Ergebnis liefert. Ein Dialog kann dabei weitere Dialoge aufrufen, sodass sich ein Gespräch mit einem Bot meist aus mehreren Dialogen zusammen-setzt. Das Framework definiert ein (recht überschau-bares) Interface für einen Dialog:

public interface IDialog<out T>{ Task StartAsync(IDialogContext context);}

Listing 2 zeigt die erste Version der Klasse AlertDialog, die dieses Interface implementiert.

Das Pattern, das dabei verwendet wird, sieht auf den ersten Blick etwas ungewöhnlich aus, man gewöhnt sich aber schnell daran: Durch den Aufruf von context.Wait wartet man auf die nächste Nachricht und gibt eine Callback-Methode an. In unserem einfachen Bei-spiel wird diese Methode sofort aufgerufen, da ja bereits eine Nachricht verfügbar ist. Bemerkenswert ist, dass auch der Zugriff auf die Message mittels await erfol-gen muss – mit dem Warten auf die Nachricht hat das aber nichts mehr zu tun. Mit PostAsync kann man eine Antwort senden, um danach wieder mittels Wait auf die nächste Antwort zu warten.

Gespräche mit dieser ersten Version unseres Bots sind vielleicht noch etwas eintönig (böse Zungen behaupten allerdings, für den Einsatz im Kundendienst mancher Unternehmen würde es bereits reichen). Listing 3 zeigt den nächsten Schritt: Wir prüfen, ob die eingegangene Message gleich dem Text „Neuer Alarm“ ist. Wenn dem so ist, starten wir einen neuen Dialog, um den Namen des Projekts zu erfragen.

Die Klasse PromptDialog bietet bereits mehrere wie-derverwendbare Dialogimplementierungen, mit denen sich diverse Standarddatentypen erfragen lassen – in die-sem Fall ein Text. Als Parameter anzugeben ist wieder-rum ein Callback, außerdem der Text für die Frage und optional eine zweite Frage, falls die erste Antwort un-verständlich ist. Aus diesen Bausteinen können wir nun den Alert-Dialog fertig implementieren, sodass nach und nach die alert-Variable unserer Klasse befüllt wird (Listing 4). Dabei wird immer das beschriebene Muster

Listing 5

public async Task<Message> Post([FromBody]Message message){ if (message.Type == "Message") { return await Conversation.

SendAsync(message, () => new

DiaryDialog()); } else { return HandleSystemMessage(message); }}

Abb. 1: Bot Framework Emulator

Page 63: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 68

DOSSIER User Interface

verwendet. In den Callback-Methoden kann jeweils mit await result auf den Rückgabewert des Dialogs zugegrif-fen werden. Durch den Aufruf von context.Done been-den wir schließlich den aktuellen Dialog.

Im Service können wir den Alert-Dialog schließlich starten (Listing 5).

Zeit für einen ersten Test: Starten Sie das Projekt und merken Sie sich den lokalen Port, unter dem die Websi-te läuft (üblicherweise Port 3978). Natürlich könnten Sie jetzt mit Postman oder Fiddler Requests austau-schen – einfacher geht es aber mit dem Bot Framework Emulator [4, Abb. 1]. Geben Sie in der oberen Leiste den URL (z. B.: http://localhost:3978/api/messages) an und achten Sie darauf, dass die App-ID und das App Secret den Werten aus der Web.config entsprechen  – vorerst können wir die Standardwerte (YourAppId, YourAppSecret) belassen. Anschließend steht Ihnen das linke Chatfenster zur Kommunikation zur Verfügung,

beim Klicken auf das blaue Icon in den Antworten sehen Sie auch das dazugehörige JSON-Objekt.

Übrigens: Wenn Sie CLI und CUI mischen möchten, dann können Sie auch ein Kommandozeilenprogramm als Emulator bauen; den entsprechenden Sourcecode finden Sie online unter [5].

Einfache Dialoge lassen sich also recht schnell imple-mentieren, und auch komplexere Konversationen sind durch die Verschachtelung von mehreren Dialogen gut handhabbar. Bei der Erfassung von komplexeren Daten-strukturen als unserer Alert-Klasse kann es aber trotz-dem schnell aufwendig werden, alle Eigenschaften „von Hand“ abzufragen. Hier kommt die zweite Variante ins Spiel.

FormFlowDie Grundidee von FormFlow ist, auf Basis einer C#-Klasse (Form) automatisch einen entsprechenden Dialog

Abb. 2: FormFlow: Je nach Datentyp werden unterschiedliche Fragen gestellt

Abb. 3: Deployment als API-App in Azure

Listing 6public enum AmountType{ [Describe("Stunden")] [Terms("Stunde", "Stunden")] Hours, [Describe("Tagen")] [Terms("Tag", "Tage", "Tagen")] Days}

[Serializable]public class Alert{ [Describe("Projekt")] [Prompt("Für welches Projekt?")]

public string Project { get; set; }

[Describe("Art")] [Prompt("Wie soll der Alarm eingestellt werden? {||}")]

public AmountType? AmountType { get; set; }

[Numeric(1, 100)] [Describe("Limit")] [Prompt("Nach wie vielen {AmountType} soll der Alarm eingestellt werden?")] public int Amount { get; set; }

public static IForm<Alert> BuildForm() { return new FormBuilder<Alert>() .Message("Hallo, ich helfe dir bei der Erstellung.") .AddRemainingFields() .Confirm("Möchtest du einen Alarm für das Projekt {Project} nach {Amount} {AmountType} setzen?") .Message("Der Alarm wurde erstellt.") .Build(); }}

Page 64: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 69

DOSSIER User Interface

zu erstellen, der alle Felder und Eigenschaften befüllt. Dazu müssen wir die Alert-Klasse um eine statische Me-thode erweitern, die mittels der Klasse FormBuilder eine IForm<Alert> erstellt:

public static IForm<Alert> BuildForm(){ return new FormBuilder<Alert>().Build();}

Mithilfe dieser können wir dann einen entsprechenden Dialog erzeugen:

return await Conversation.SendAsync(message, () => FormDialog. FromForm(Alert.BuildForm));

Die FormFlow Engine fragt dann Schritt für Schritt nach jedem Feld, wobei die Fragen je nach Datentyp unterschiedlich gestellt werden. Bei Enumerationen werden beispielsweise gleich alle Optionen aufgelistet (Abb. 2).

Ist man mit diesem Standardverhalten nicht zufrieden, kann man auf verschiedene Arten eingreifen. So kann man mittels eines Fluent-API beispielsweise den Begrüßungs-text und die Abschlussfrage formulieren oder mittels Attributen die Fragen nach den jeweiligen Feldern beein-flussen. Dabei kommt eine eigene Pattern Language mit Platzhaltern zum Einsatz, mit der etwa die Werte einzelner Felder oder die verfügbaren Optionen angezeigt werden können (Listing 6). Eine detaillierte Beschreibung aller Features ist unter [6] zu finden.

Auf diese Weise ist es mit minimalem Aufwand mög-lich, selbst große Objekte zu befüllen, auch wenn man im Vergleich zur manuellen Implementierung etwas Fle-xibilität verliert. Dank der zahlreichen Konfigurations-möglichkeiten fällt das allerdings kaum ins Gewicht, und da auf Basis der Form ebenfalls ein Dialog erstellt wird, lassen sich die beiden Ansätze auch wunderbar kombinieren.

Deployment und RegistrierungDa es sich bei einem Bot-Projekt technologisch um ein Web-API handelt, kann das Deployment wie bei her-kömmlichen ASP.NET-Anwendungen erfolgen  – also auch auf On-Premise-Servern. Dass die Integration in Microsoft Azure leicht gemacht wird, überrascht aber natürlich wenig. In Visual Studio kann mittels Pub lish (Kontextmenü des Projekts) direkt ein neuer App-Ser-vice in Azure erstellt werden (API-App); der Bot ist nach wenigen Eingaben online (Abb. 3). Sollte Ihr Bot großen Anklang finden und von vielen Benutzern konsultiert werden, spricht nichts gegen ein Scale-out der Azure-Instanzen: Dank Zustandslosigkeit des Service können während einer Konversation problemlos die Server ge-wechselt werden.

Im nächsten Schritt erfolgt die Registrierung des Bots. Unter [1] können Sie einen neuen Bot anlegen; vor allem der URL zum Message Endpoint ist relevant

(achten Sie auf die komplette Angabe des URLs, also inkl. der Route auf /api/messages). Auch die App-ID ist wichtig; der hier eingegebene Wert muss mit dem Wert aus der Web.config im Bot-Projekt übereinstimmen und sollte spätestens jetzt auf einen sinnvollen Namen (ohne Leer- und Sonderzeichen) geändert werden. Die Aufnahme ins Bot Directory, die mittels Option bean-tragt werden kann, ist noch Zukunftsmusik. In diesem Verzeichnis werden später alle Bots aufgelistet, derzeit sind aber nur die Microsoft-eigenen Kandidaten gelis-tet.

Nach der Erstellung werden Sie zur Übersichtsseite weitergeleitet (Abb. 4), auf der Sie u. a. das Primary app secret finden. Kopieren Sie diesen Schlüssel, passen Sie damit die Web.config an und updaten Sie den Bot durch ein erneutes Deployment.

Bot ConnectorZusätzlich sehen Sie auf der Übersichtsseite auch noch den dritten Tätigkeitsbereich des Microsoft Bot Frameworks: den Bot Connector. Über welches Interface möchten Sie mit dem Bot „sprechen“? Bisher haben Sie nur den Emulator genutzt, für Endbenutzer ist das aber wenig sinnvoll. Ihr Bot muss dort sein, wo Ihre Benutzer sind: auf Skype, Slack oder GroupMe. Er soll per E-Mail oder SMS antworten können. Oder einfach auf Ihrer Website für Fragen aller Art zur Verfügung stehen. Alle diese Sze-narien deckt der Bot Connector ab. Microsoft hat nicht nur Schnittstellen zu den Kommunikationsplattformen („Kanäle“) entwickelt; in bebilderten Schritt-für-Schritt-Anleitungen werden auch die notwendigen Einstellungen detailliert beschrieben. Eine Integration des Bots in Slack ist dadurch in weniger als 5 Minuten erledigt (Abb. 5). Für die ausgewählten Kanäle stehen auch fertige HTML-

Design-First Development mit Story-boards

Jörg Neumann

Erfolgreiche Apps sehen nicht nur gut aus, sondern bieten vor allem eine exzellente Usability. Besonders im Enterprise-Umfeld

spielen intuitive Bedienung und die proaktive Unter-stützung der Anwender eine entscheidende Rolle. Um dies zu erreichen, müssen alle Stakeholder in den De-signprozess eingebunden werden. Hierbei können Storyboards helfen, denn anders als statische Wire-frames vermitteln sie dem Anwender einen guten Ein-druck vom Verhalten der App. Zudem bieten sie eine solide Grundlage für die Entwicklung. Jörg Neumann zeigt Ihnen an Beispielen aus der Praxis, wie gute Storyboards entworfen werden und welche Faktoren für ein gutes App-Design wichtig sind.

Besuchen Sie auch folgende Session:

Page 65: Jubiläums-Dossier 2017¤ums-Dossier.pdf · Web Development Echtzeitchat mit Node.js und Socket.IO 22 Nutzung von echtem Server-Push mit dem WebSocket-Protokoll von Manuel Rauber

www.basta.net 70

DOSSIER User Interface

Einbettungscodes zur Verfügung, um das Chat-Control auf der eigenen Website platzieren zu können.

FazitWarum nicht neue Benutzer mit einem Bot um die Ver-vollständigung des Userprofils bitten (wie bei Slack hervorragend gelöst)? Warum nicht einfache Aktionen durch einen Bot abwickeln anstatt Wartungsmasken zu bauen? Warum ein FAQ nicht einmal anders lösen?

Das Microsoft Bot Framework ist eine faszinierende Spielwiese. Nicht für jede Anwendung sofort sinnvoll einsetzbar – aber auf alle Fälle einen Blick in die Zukunft wert. Viel Spaß und liebe Grüße an Ihren ersten Bot!

Roman Schacherl (MVP) und Daniel Sklenitzka sind Gründer der Firma softaware gmbh und entwickeln mit ihrem Team individuelle Lösungen auf Basis von Microsoft-Technologien. Beide sind Autoren mehrerer Fachartikel, nebenberuflich Lehrende an der FH Ha-

genberg (Österreich) und als Sprecher auf Entwicklerkonferenzen tätig.

www.softaware.at

Abb. 5: Verwendung des Bots in Slack

Links & Literatur

[1] Microsoft Bot Framework: https://dev.botframework.com

[2] Cognitive Services: https://www.microsoft.com/cognitive-services

[3] Bot Application Template: http://aka.ms/bf-bc-vstemplate

[4] Emulator: http://aka.ms/bf-bc-emulator

[5] C#-Referenz: http://docs.botframework.com/sdkreference/csharp/

[6] FormFlow-Dokumentation: http://docs.botframework.com/sdkreference/csharp/forms.html

Abb. 4: Registrierung des Bots