JavaScript für C#- und .NET-Entwickler

Keine Angst vor JavaScript
Kommentare

Anwendungen laufen schon lange nicht mehr ausschließlich auf dem Desktop oder Server. Mit der Einführung von HTML5 und JavaScript als native Programmiersprache für die Entwicklung von Windows-8-Apps wird diese Technologie für Entwickler von Windows-8-Apps gleichermaßen interessant wie für Entwickler von webbasierten mobilen Anwendungen. In jedem Fall sollte kein .NET-Entwickler Angst davor haben, sich intensiv mit JavaScript zu beschäftigen.

Durch konsequente Weiterentwicklung verschiedener JavaScript-Frameworks lassen sich Anwendungen inzwischen plattformübergreifend innerhalb und außerhalb des Browsers in einer den Desktopanwendungen ähnlichen Architektur und Qualität erstellen. Die vorgestellten Konzepte sind größtenteils unabhängig davon, ob die Applikation als Webanwendung oder als Windows-Store-Anwendung in HTM5 und JavaScript laufen wird. Dieser Artikel soll einen ersten Überblick über die wichtigsten Konzepte sowie die Architektur einer in JavaScript entwickelten Anwendung geben. Viele .NET-Entwickler stehen der „neuen JavaScript-Welt“ relativ skeptisch, gleichzeitig aber auch neugierig gegenüber. Lassen sich damit „richtige“ Anwendungen entwickeln? Wie soll das überhaupt gehen, so ganz ohne Typsicherheit? Diese und andere Fragen möchte ich gern versuchen, mit diesem und einer sich anschließenden Artikelreihe im Windows Developer, zu beantworten. Begleiten Sie mich auf einer Entdeckungsreise durch die alte neue JavaScript-Welt.

JS verändert .NET

Welche Auswirkungen hat die Einführung von JavaScript als First-Class-Language in der Windows-8-App-Entwicklung auf die Zukunft von .NET? Im Windows-Umfeld bedeutet die Einführung dieser neuen Möglichkeit, plattformübergreifend Oberflächen zu erstellen, auf längere Sicht, dass es eine Verschiebung geben wird. Oberflächen mobiler und verteilter Anwendungen werden mit zunehmender Tendenz in JavaScript entwickelt werden. Im Backend-Bereich wird .NET voraussichtlich seine Position sogar verstärken können, da mit JavaScript als Frontend-Technologie Windows Azure zunehmend auch attraktiver für nicht Windows-basierte Clients wird. Ein Blick auf die Möglichkeiten von JavaScript lohnt sich also in jedem Fall.

Das wahrscheinlich größte Missverständnis ist der Name der Programmiersprache JavaScript selbst. Weder hat diese Programmiersprache große Gemeinsamkeiten mit der Programmiersprache Java noch ist dies eine Script-Sprache im klassischen Sinne. JavaScript wird interpretiert und die modernen Implementierungen der JavaScript Engines (z. B. IE mit Chakra oder Chrome mit Google V8) müssen sich keinesfalls hinter den Implementierungen des .NET-Runtime oder Java-Runtime-Interpreters verstecken. Seit der ersten Implementierung der Programmiersprache LiveScript (damals von Netscape zunächst so genannt) hat sich sehr viel verändert, von einer zur Manipulation von DOM-Elementen im Browser erschaffenen Sprache hin zu einer vollwertigen Umgebung für die Entwicklung plattformübergreifender client- aber auch serverseitiger Anwendungen. Aber nicht nur im Webumfeld sondern auch bei der Entwicklung mobiler Applikationen spielt JavaScript mit zunehmender Tendenz eine Rolle.

Was bedeutet das für uns Entwickler? Neben den klassischen Möglichkeiten einer rein serverseitigen Multiplattformanwendungsentwicklung kommt eine weitere Option zur Erstellung von plattformunabhängigen clientseitigen Anwendungen hinzu. Das bedeutet nicht, dass alle bisherigen Techniken durch JavaScript vollständig abgelöst werden, vielmehr werden vorhandene Techniken und Technologien ergänzt.

Die Qual der Wahl

Mittlerweilen gibt es sehr viele leistungsfähige und umfangreiche Bibliotheken sowie Frameworks im JavaScript-Umfeld. Viele dieser Bibliotheken und Framewoks überschneiden sich teilweise in ihrer Funktionalität, ergänzen sich oder schließen sich manchmal auch aus. Da es nicht die eine Kombination gibt, soll im Folgenden zunächst die Architektur einer JavaScript-Anwendung genauer betrachtet werden, um im Anschluss die verschiedenen Verwendungsmöglichkeiten der einzelnen Bibliotheken und Frameworks innerhalb dieser Architektur etwas genauer zu betrachten sowie einen Überblick und eine erste Einführung in das Thema zu geben.

Bei der Entscheidung, welches das richtige Framework oder welches die richtige Bibliothek für eine der benötigten Teilfunktionen ist, kommen oft verschiedene Aspekte zusammen. So lassen sich in manchen Fällen bereits erworbene Kenntnisse mit anderen Technologien übertragen bzw. weiter nutzen. Möglicherweise soll die Abhängigkeit zu verschiedenen Anbietern von Bibliotheken eher gering gehalten werden, dann wäre ein Framework mit besonders großem Funktionsumfang wie AngularJS [3] oder Ember.js [4] zu empfehlen. Möchte man hingen eher nicht von einem einzelnen Framework abhängig sein, lassen sich die Funktionen, welche in der geplanten Single Page Application (SPA) benötigt werden, auch durch individuelles zusammenstellen der einzelnen Bibliotheken abbilden.

JavaScript als Programmiersprache

Auf den ersten Blick erscheint diese Programmiersprache C/++, C# oder Java sehr ähnlich. Jedoch verwendet diese Sprache teilweise sehr unterschiedliche Konzepte und Paradigmen. JavaScript ist eine dynamisch typisierte, objektorientierte aber klassenlose Programmiersprache. Objekthierarchien und Vererbung lassen sich mithilfe der so genannten prototypischen Vererbung realisieren. Vereinfacht kann man sich vorstellen, dass es eine Vorlage (den Prototypen) gibt, anstelle der Klassendefinition, die instanziiert würde. Dabei werden alle Eigenschaften des Prototypen auf die jeweiligen Klassen übertragen. Das Besondere hierbei ist, dass auch die Eigenschaften und Funktionen vererbt werden, die erst nachträglich zur Laufzeit hinzugefügt wurden.

Dazu kommen weitere Besonderheiten wie der Funktions-Scope, automatisches Konvertieren von Typen sowie teilweises automatisches Einfügen von Semikolons. Diese Eigenheiten führen zu einem anderen Verhalten, als bei prozeduralen, imperativen, klassenbasierten Programmiersprachen mit strenger Typisierung und einem Block-Scope für lokale Variablen, wie in C# oder Java. Gerade weil die Syntax anderen Programmiersprachen teilweise zum Verwechseln ähnlich ist, ist es besonders wichtig, sich mit den Eigenheiten der Sprache auseinanderzusetzen.

Eine Möglichkeit, JavaScript um Klassen und statische Typen zu erweitern, stellt die Sprache TypeScript dar. Eine TypeScript-Datei wird nach JavaScript kompiliert. Diese Sprache ist 100 Prozent mit allen JavaScript-Projekten und Bibliotheken kompatibel, weil die zusätzliche Syntax nur eine Erweiterung von JavaScript darstellt. Purer JavaScript-Code kann auch innerhalb von TypeScript direkt weiterverwendet werden. So hat der Entwickler das Beste aus beiden Welten und kann selbst entscheiden, an welcher Stelle die Erweiterungen von TypeScript zum Einsatz kommen sollen und an welcher Stelle nicht.

Single Page Application

Der Begriff der „Single Page Application“ (SPA) wird oft im Zusammenspiel mit einer modernen plattformübergreifenden mobilen Anwendung in JavaScript verwendet. Bei einer SPA handelt es sich um eine Anwendung, die ohne serverseitige Seitennavigation auskommt und einer Desktopanwendung in nichts nachsteht. Das bedeutet, dass diese Anwendung flüssig auf Eingaben und Aktionen des Benutzers reagiert. Daten werden bei Bedarf nachgeladen oder zum Server gesendet. Viele Single-Page-Applikationen sind auch offlinefähig und lassen sich mithilfe entsprechender Frameworks (z. B. PhoneGap) als App auf verschiedenen Plattformen über den jeweiligen Marktplatz deployen.

Charakterisierend ist die strikte Trennung zwischen dem Frontend und dem Backend. Wie bei anderen verteilten Anwendungen auch, spielt es keine Rolle, in welcher Technologie das Backend implementiert ist (Abb. 1).

Abb. 1: Überblick über eine Single Page Application

Auf der Serverseite gibt es zwei unabhängige Instanzen. Den Anteil des Web-UI, der für die Auslieferung aller mit der Anwendung in Verbindung stehenden Ressourcen zuständig ist, sowie die Datendienste (Data Services), die für den Austausch von Daten und ggf. für den Aufruf von RPCs verantwortlich sind. Dabei ist es weder erforderlich, dass diese Dienste auf den gleichen physikalischen Server laufen, noch ist es erforderlich, dass diese beiden Dienste in der gleichen Technologie implementiert sind. So ist es denkbar, dass das Web-UI von einem LAMP-Server ausgeliefert wird, während die Datendienste die Windows-Mobile-Services von Azure verwenden.

Eines haben jedoch (fast) alle serverseitigen Implementierungen verschiedenster Datendienste gemeinsam: Zur Vereinfachung der Kommunikation und zur Reduktion von überflüssigem Overhead kommt hier eine REST-basierte Schnittstelle zum Einsatz, welche entweder JSON- oder XML-formatierte Daten überträgt.

Die Anwendung auf dem Client selbst setzt sich aus verschiedenen Teilen zusammen. Wie bei jeder anderen HTML-basierten Anwendung gibt es zunächst den sichtbaren Teil der Oberfläche, der im Wesentlichen aus HTML und CSS besteht, sowie die Anwendungsschicht, die aus JavaScript besteht. Bis hier gibt es noch keinen großen Unterschied zu anderen HTML-basierten Anwendungen.

Zusätzlich kommt bei einer SPA jedoch ein „Anwendungsrahmen“ hinzu, dieser bildet das Laufzeitverhalten der App auf dem Client ab und übernimmt unter anderem z. B. die Navigation zwischen verschiedenen Ansichten auf der Clientseite. Dieser Rahmen kann je nach verwendeter Bibliothek oder auch Framework mehr oder weniger umfangreich implementiert sein.

Eine weitere Komponente stellt die clientseitige Zugriffsschicht für die Daten dar. Diese Schicht übernimmt die Verwaltung der Daten und die zugehörige Kommunikation mit dem Server. Je nach Mächtigkeit der verwendeten Bibliothek sind Funktionen wie Offline-Caching, automatische Synchronisation mit dem Server und clientseitige Abfragen möglich.

[ header = Seite 2: MV* ]

MV*

Die Entwurfsmuster MVC (Model View Controller), MVP (Model View Presenter) bzw. MVVM (Model View ViewModel) lassen sich nicht nur auf native Programmierung anwenden. Auch im JavaScript-Umfeld gibt es verschiedene Bibliotheken bzw. Frameworks, die die Trennung in ein Drei-Schichten-Modell verwenden.

Das Grundprinzip ist immer das gleiche, jedoch kann der Name des Entwurfsmusters je nach Bibliothek variieren. Das Model enthält dabei die Geschäftsdaten (Business Domain), die Ansicht (View) ist dafür zuständig, das Benutzerinterface anzuzeigen und ggf. Eingaben und Interaktionen des Benutzers entgegenzunehmen. Um die Darstellung vom Verhalten trennen zu können, ist es erforderlich, dass die View keine direkten Zugriffe auf das Modell und umgekehrt ausführt. Diese Aufgabe übernimmt der Controller (ja nach Implementierungsdetails auch ViewModel oder Presenter genannt). Hier erfolgt die Verbindung des Modells und der View, indem die Funktionalität innerhalb des ViewModels implementiert wird. Das bedeutet, dass das ViewModel die View steuert indem die anzuzeigenden Daten mittels Bindung an das UI sowie die Interaktionen zurück zum ViewModel übertragen werden (Abb. 2).

Abb. 2: MVVM-Entwurfsmuster

Für .NET-Entwickler, die bereits Erfahrungen mit der Entwicklung von XAML-basierten Oberflächen haben und sich mit dem MVVM-Entwurfsmuster auskennen, ist an dieser Stelle besonders die Bibliothek Knockout.js [1] zu empfehlen. Diese Bibliothek verwendet Bindungen mit einer dem XAML sehr ähnlichen Syntax sowie spezielle „observable“ Objekttypen, welche ähnlich einer ObservableCollection< > im .NET Framework funktioniert, indem das UI über Änderungen mithilfe von entsprechenden Ereignissen informiert wird. Diese Bibliothek lässt sich sehr gut im Zusammenspiel mit anderen Bibliotheken wie jQuery, jQuery UI oder auch Breeze [2] verwenden.

Alternativ verfügen Applikationsframeworks wie AngularJS [3] oder Ember.js [4] über eine eigene integrierte Bibliothek, die die deklarative Verbindung zwischen dem ViewModel und dem UI ermöglicht.

Anwendungsrahmen

Ein Anwendungsrahmen ist immer dann erforderlich, wenn es verschiedene Ansichten auf der Clientseite geben soll. Ein Beispiel wäre eine Anwendung, die alle relevanten Aufträge in einer Ansicht zunächst übersichtsartig darstellt und beim Auswählen eines einzelnen Auftrags zu einer Detail- und/oder Bearbeitungsansicht des Auftrags wechselt. Hierfür wird eine clientseitige Navigation benötigt, welche die passende Schablone (eng. Template) zu einem vorgegebenen lokalen Pfad lädt und anzeigt. Dabei spielt es keine Rolle, ob die Schablone inline oder als externe Ressource definiert wurde, die vom Browser ggf. nachgeladen werden muss.

Diese Funktion wird auch als routing bezeichnet. Immer wenn innerhalb der Anwendung zu einem neuen Pfad navigiert wird, wird ein entsprechendes Ereignis ausgelöst und die Schablone für die jeweilige Darstellung muss aus dem Pfad ermittelt werden. Mit vielen Bibliotheken und auch Frameworks sind auf diese Weise sogar deep-links möglich.

Die am einfachsten zu verwendende Bibliothek für Routing und Events ist Sammy.js [5], die sehr gut mit anderen Bibliotheken z. B. Knockout.js [1] für das Data Binding sowie jQuery für die Manipulation des DOMs zusammenarbeitet. Größere Frameworks wie AngularJS [3] und Ember.js [4] bringen die Funktionalität des Routings und der dafür benötigten Events als entsprechendes Modul bereits mit (Abb. 3).

Abb. 3: Funktionsweise eines clientseitigen URL-Routers

Datenlayer

Ein nicht unwesentlicher Teil des Aufwands bei der Erstellung einer Anwendung fließt in den Datenlayer, der aus zwei Teilen besteht, dem Server- und dem Clientanteil. Um hier eine möglichst große Unabhängigkeit zwischen der auf dem Client und der auf dem Server verwendeten Technologie zu erreichen, ist es wichtig, sich soweit es geht an vorhandene Standards zu halten und zu orientieren.

Der Server sollte möglichst den Dienst bzw. die Dienste in der Form einer REST-basierten Web-API bereitstellen, das die Daten in einem für einen Browser leicht verständlichem Format repräsentiert (z. B. JSON oder XML). Im Gegensatz zu WCF kommt es bei einem REST-basierten Web-API darauf an, Nachrichten und Aktionen so einfach wie möglich zu beschreiben. Hierbei kommen die Verben des HTTP-Protokolls direkt zum Einsatz. Jedem der vier bekanntesten Verben GET, POST, PUT und DELETE wird eine Aktion zugeordnet, die HTTP-Adresse gibt an, welche Ressource abgefragt oder manipuliert werden soll. Mithilfe von zusätzlichen Parametern lassen sich weitere Aktionen unterscheiden, z. B. das Lesen aller Elemente oder das Lesen eines Elements. Dabei spielt es bei den meisten Frameworks keine Rolle, ob der Parameter Bestandteil des URI ist oder mithilfe des QueryStrings übergeben wird. Tabelle 1 zeigt eine Möglichkeit, ein REST-basiertes Web-API zu definieren.

Aktion HTTP-Verb URI Schema
Lesen aller Elemente get http://server/api/elments

Lesen eines Elements anhand

der ID

get “ class=“elf-external elf-icon“ rel=“nofollow“>http://server/api/elements?id=
Erstellen eines Elements post http://server/api/elements
Aktualisieren eines Elements put http://server/api/elements
Löschen eines Elements delete “ class=“elf-external elf-icon“ rel=“nofollow“>http://server/api/elements?id=

Tabelle 1: Beispiel für die Verwendung der HTTP-Verben in einem REST-basierten Web-API

Der Datenlayer ist zunächst unabhängig von den anderen Layern auf der Clientseite. Hier gibt es verschiedene Möglichkeiten, mit den Daten vom Server zu interagieren. Je nach Architekturmodell können die API-Funktionen entweder mit entsprechenden AJAX-Aufrufen manuell verwendet werden oder ein clientseitiger Entity-Manager kommt zum Einsatz.

Der direkte Aufruf von API-Funktionen lässt sich sehr gut mithilfe von jQuery und der $.ajax()-Funktion realisieren. Zu übertragende Daten werden als JSON serialisiert, an den Server übertragen und das Ergebnis wird wieder zurück übertragen. Diese Variante der Interaktion mit dem Dienst bietet sich an, wenn viele verschiedene Datentypen und unterschiedlichste Funktionen vom Server zur Anwendung kommen sollen.

Bibliotheken wie Breeze [2] oder AmplifyJS [8] stellen einen lokalen Data-Manager zur Verfügung, der die Daten auf dem Client (zwischen-)speichert. Auf diese Weise wird der Kommunikationsaufwand reduziert und es müssen nur die Daten nochmals übertragen werden, die sich auf der einen oder anderen Seite geändert haben. So lassen sich lokale Abfragen genau realisieren, wie bei einem Offlinezwischenspeicher, der alle Änderungen synchronisiert, nachdem die Anwendung wieder online ist.

In komplexeren, sehr datenlastigen Projekten ist Breeze [2] im Zusammenspiel mit dem ASP.NET Web API auf der Serverseite sehr zu empfehlen (Abb. 4). Diese Bibliothek ermöglicht client- und serverseitige Abfragen. Dabei kommt eine eigens hierfür bereitgestellte Erweiterung für das Web-API zum Einsatz, die eine Auswertung zusätzlicher Abfrageparameter nach dem OData-Standard ermöglicht. Voraussetzung ist jedoch, dass es für die zu verwendende Datenquelle einen mit Breeze kompatiblen ContextProvider gibt. Ein Provider für das Entity Framework und NHibernate werden mitgeliefert. Auf der Webseite von Breeze [2] findet sich ein Beispiel, wie ein solcher Provider für eine benutzerdefinierte Datenquelle programmiert werden kann.

Abb. 4: Serverseitiges ASP.NET Web API mit Breeze-Erweiterung

[ header = Seite 3: Modularisierung und Verwaltung von Abhängigkeiten ]

Modularisierung und Verwaltung von Abhängigkeiten

Webseiten werden mehr und mehr zu Anwendungen und Apps. Besonders mit der Möglichkeit, Apps mithilfe von Framewoks wie PhoneGap oder auch Nativ als Windows-8-Store-App in HTML5 und JavaScript zu entwickeln. Das führt zu einem starken Anstieg der Komplexität der Anwendungen. Um dem entgegenzuwirken, lässt sich eine Anwendung in Module gliedern. Was jedoch wiederum dazu führt, das eine Applikation bzw. App aus immer mehr einzelnen Teilen und Bibliotheken besteht, die nicht selten auch diverse Abhängigkeiten untereinander aufweisen. Um dieses Problem zu lösen, wird ein Mechanismus für das Einbinden und Verwalten von Bibliotheken und Modulen bereitgestellt (Abb. 5).

Abb. 5: Abhängigkeiten verschiedener JavaScript-Bibliotheken

Viele Frameworks wie AngularJS, Ember.js oder Dojo [6] verfügen über eine integrierte Modulverwaltung. Bei kleineren Projekten ist es jedoch nicht immer erforderlich, ein komplettes Framework einzusetzen, wenn nur eine Funktionalität benötigt wird. So lassen sich Module und deren Abhängigkeiten alternativ zu einem großen Framework auch sehr gut mit der Bibliothek RequireJS [7] verwalten.

Architekturmodelle

Wie bereits zum Anfang dieses Artikels erwähnt, gibt es nicht die eine „wahre“ Kombination von JavaScript-Bibliotheken. Für die einzelnen Anforderungen einer Anwendung gib es verschiedene Bibliotheken, die sich teilweise überlagern und teilweise ergänzen. Im Folgenden sollen zwei der gebräuchlichsten Kombinationen von Bibliotheken kurz vorgestellt werden (Abb. 6).

Variante 1 verwendet eine Kombination aus den Bibliotheken Knockout.js, Breeze, Sammy.js und RequireJS, die zusammen ein eigenes kleines benutzerdefiniertes Framework darstellen. Dabei bildet Sammy.js den Anwendungsrahmen, der die clientseitige Navigation sowie das Laden und Initialisieren der View-Modelle enthält. Die Bindung der View-Modelle an die Ansichten in HTML erfolgt mithilfe der Bibliothek Knockout.js. Die Daten werden mithilfe von Breeze und der entsprechenden Erweiterung auf der Serverseite in einem Web-API bereitgestellt. Abhängigkeiten zwischen den einzelnen Modulen werden mit der Bibliothek RequireJS aufgelöst.

Variante 2 verwendet das Framework AngularJS in Kombination mit der Bibliothek AmplifyJS. Da Angular über eine eigene Modulverwaltung verfügt, die auf „Dependancy Injection“ basiert, bietet es sich in diesem Fall an, diese zusammen mit dem Routing-Modul für die lokale Navigation und der integrierten Bindungsunterstützung zu verwenden. AngularJS verfügt zwar über eine eigene Ressourcenverwaltung, mit der sich unter anderem auch REST-basierte APIs ansprechen lassen, jedoch ist die Bibliothek AmplifyJS in diesem Zusammenhang mächtiger. Da die einzelnen Bibliotheken und Teilfunktionalitäten unabhängig voneinander verwendet werden können, lassen sich verschiedene Bibliotheken und Frameworks kombinieren. Alternativ wäre es auch denkbar, Breeze anstelle von AmplifyJS zu verwenden.

Abb. 6: Gegenüberstellung von zwei Varianten einer SPA-Architektur

Tabelle 2 soll einen kurzen Überblick über die jeweils wichtigsten Funktionalitäten der beschriebenen Bibliotheken und damit über ihre jeweilige Einsatzmöglichkeit geben.

Tabelle 2: Funktionen der verschiedenen Bibliotheken

Tabelle 2: Funktionen der verschiedenen Bibliotheken

Zusammenfassung

Um die geeignetste Kombination von Bibliotheken und/oder Frameworks für eine mobile App mit HTML und JavaScript zu finden, lohnt es sich in jedem Fall, die Vor- und Nachteile der einzelnen Komponenten und ihrer Alternativen genauer zu betrachten und zu vergleichen. Je nach Anforderungen kann es vorteilhaft sein, ein größeres Framework zu verwenden oder alternativ die einzelnen Funktionalitäten mit verschiedenen Bibliotheken abzubilden. Diese Variante ist besonders dann interessant, wenn nicht alle Funktionalitäten sondern nur einzelne Teilfunktionen benötigt werden.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -