Knock you out and shot you up

Moderne Webanwendungen mit knockout.js und upshot.js (Teil 3)
Kommentare

Windows Developer

Der Artikel „Knock you out and shot you up“ von Manfred Steyer ist erstmalig erschienen im Windows Developer 7.2012
Web-API-basierte Services für upshot.js bereitstellen
Sämtliche

Windows Developer

Der Artikel „Knock you out and shot you up“ von Manfred Steyer ist erstmalig erschienen im Windows Developer 7.2012

Web-API-basierte Services für upshot.js bereitstellen

Sämtliche bis dato betrachtete Beispiele sind ohne Model ausgekommen. Der Grund dafür lag unter anderem darin, dass die verwendeten Daten lediglich lokal vorlagen. Dies soll sich nun ändern, indem gezeigt wird, wie Datenobjekte aus einer serverseitigen Datenbank bei Bedarf (nach-)geladen werden können. Hierzu kommt clientseitig upshot.js sowie serverseitig ein auf dem neuen ASP.NET-Web-API basierender Service zum Einsatz. Upshot.js vereinfacht das Anfordern von serverseitigen Daten sowie deren Modifizieren, Validieren und Zurücksenden. Als Datenquellen werden hierbei REST-basierte Dienste, die bestimmte von upshot.js geforderte Charakteristiken aufweisen müssen, eingesetzt.

Ein Beispiel für solch einen Service, der sich auf das ASP.NET-Web-API abstützt, findet sich in Listing 7. Als Basisklasse kommt dabei nicht die ansonsten übliche Klasse ApiController, sondern ihr Derivat DbDataController zum Einsatz. Diese ist mit einem über das Entity-Framework Code Only bereitgestellten DbContext, über den auch der Zugriff auf die Datenbank erfolgt, zu typisieren. „Herkömmliche“ ObjectContext-Instanzen, die Entity-Framework für Database-First- oder Model-First-Szenarien erzeugt, werden (noch) nicht unterstützt. Möchte man diese Spielarten einsetzen, muss man auf JSON-basierte WCF-RIA-Services, die ebenfalls von upshot.js unterstützt werden, zurückgreifen.

Die in Listing 7 betrachtete Implementierung delegiert im Falle von Operationen, die Entitäten einfügen, ändern oder löschen lediglich an entsprechende geerbte Methoden weiter, die diese anschließend an den DbContext übergeben. Die Methode GetBuchungen liefert sämtliche verfügbaren HotelBuchungen zurück. Der DbContext wird dabei über die gleichnamige Eigenschaft bezogen. Die Möglichkeit einer weiteren Einschränkung der abgerufenen Daten wird mit einem Kommentar angedeutet.

Listing 7

public partial class BuchungenController : DbDataController
{
    public IQueryable GetBuchungen()
    {
        return DbContext.HotelBuchung;//.Where(b => b.Datum.Date == datum.Date);
    }
 
    public void UpdateHotelBuchung(HotelBuchung b)
    {
        UpdateEntity(b);
    }
 
    public void InsertHotelBuchung(HotelBuchung b)
    {
        InsertEntity(b);
    }
 
    public void DeleteHotelBuchung(HotelBuchung b)
    {
        DeleteEntity(b);
    }
}  

Der Einsatz von DbDataController erlaubt upshot.js neben dem Zugriff auf die eigentlichen Daten auch das Abrufen von benötigten Metainformationen. Beispielsweise benötigt upshot.js neben Informationen zur Validierung auch Informationen darüber, welche Serviceoperationen sich um das Einfügen, Aktualisieren oder Löschen einer bestimmten Entität kümmern. Als Entwickler hinterlegt man diese Informationen im Programmcode, indem man sich an Namenskonventionen hält. Diese besagen, dass Methoden zum Einfügen, Aktualisieren bzw. Löschen mit Insert, Update bzw. Delete zu beginnen haben und die jeweilige Entität entgegennehmen müssen. Möchte der Entwickler aus diesem Schema ausbrechen, muss er die einzelnen Methoden mit den Attributen Insert, Update bzw. Delete annotieren.

Methoden, die Daten abfragen, können beliebig benannt werden, sollten jedoch ein IQueryable zurückliefern. Der Grund dafür ist, dass dieses IQueryable über Angaben, die clientseitig via upshot.js gemacht werden, weiter eingeschränkt bzw. um Informationen zu Paging und der gewünschten Sortierreihenfolge ergänzt werden können. Erst das mit diesen Informationen erweiterte IQueryable wird schlussendlich in der Datenbank zur Ausführung gebracht, was die abgerufene Datenmenge auf das nötige Minimum beschränkt.

Da zusätzliche am Client hinterlegte Abfragebedingungen mit den im Service erzeugten IQueryable-Instanzen immer UND-verknüpft werden, ergibt sich dadurch auch keine Sicherheitslücke, solange die serverseitigen Routinen die mögliche Datenmenge auf jene, die der aktuelle Benutzer auch sehen darf, einschränken.

Services via upshot.js konsumieren

Listing 8 zeigt anhand eines minimalen Beispiels, wie der zuvor betrachtete Service via upshot.js konsumiert werden kann. Dazu definiert es zunächst eine Konstruktor-Funktion HotelBuchung. Diese erzeugt ein Objekt, das als ViewModel für vom Service erhaltene HotelBuchungs-Entitäten dient. Eine JavaScript-Repräsentation solch einer Entität, die als Model fungiert, wird dabei als Argument übergeben.

Dank der Möglichkeiten, die die dynamische Typisierung in JavaScript mit sich bringt, ist es nicht notwendig, für jede Eigenschaft des Models manuell ein entsprechendes Observable anzulegen. Dies wird durch den Aufruf von upshot.map zur Laufzeit erledigt. Dabei wird das Model sowie ein String im Format Entitätsname:#Namespace, der den Typ der Entität wiederspiegelt, übergeben. Als dritten Parameter erwartet diese Methode das mit Observables zu bestückende ViewModel.

Daneben kümmert sich der Aufruf der Methode upshot.addEntityProperties darum, dass das Model um für Entitäten typische Methoden erweitert wird. Darunter finden sich Methoden, die über den Zustand der Entität Auskunft geben, wie zum Beispiel EntityState, EntityError, IsUpdated, IsAdded, IsDeleted, IsChanged oder CanDelete. Daneben fügt diese Funktion eine Eigenschaft ValidationError, die über Validierungsfehler informiert.

Nach dem Laden der Seite ruft das betrachtete Beispiel die Funktion upshot.metadata auf und übergibt dabei jene Metadaten, die vom verwendeten Service zurückgeliefert werden. Dazu wird die serverseitige Erweiterungsmethode Html.Metadata, die diese Metainformationen als JSON-Objekt zurückliefert, bemüht. Anschließend wird eine RemoteDataSource, die auf den gewünschten Service verweist, eingerichtet. Die Eigenschaft entityType nimmt dabei abermals einen String auf, der den Typ der abgerufenen Entitäten beschreibt; an mapping wird hingegen die Konstruktor-Funktion des gewünschten ViewModels übergeben. In weiterer Folge werden eine gewünschte Sortierreihenfolge sowie ein Filter definiert und Angaben zum Paging gemacht. Um mehrere Sortierreihenfolgen oder Filter zu definieren, kann an setSort bzw. setFilter auch ein Array mit den gezeigten Objekten übergeben werden. Die Angabe includeTotalCount:true beim Aufruf von setPaging bewirkt, dass trotz Paging auch die Anzahl aller Datensätze, die den Filterkriterien entsprechend, in Erfahrung gebracht wird.

Erst der Aufruf von refresh bewirkt, dass die Daten tatsächlich abgerufen werden. Im Zuge dessen wird das zuvor über die Funktion getEntities bezogene knockout-basierte observableArray mit den ViewModels der abgerufenen Entitäten bestückt. Darüber hinaus wird, nachdem diese Entitäten zur Verfügung stehen, auch die an refresh übergebene Funktion aufgerufen. Diese bekommt ebenfalls die ViewModels der abgerufenen Entitäten übergeben – allerdings innerhalb eines „herkömmlichen“ Array. Da vorhin beim Aufruf der Funktion setPaging includeTotalCount:true übergeben wurde, übergibt upshot.js als zweiten Parameter die gewünschte Anzahl an Entitäten, die den definierten Filterkriterien genügen.

Nachdem die betrachtete Methode einige der abgerufenen Daten zur Veranschaulichung mittels jQuery ausgibt, ändert sie eines der Observable. Dies hat zur Folge, dass die entsprechende Entität sofort, jedoch asynchron, über die bereitgestellte Service-Operation aktualisiert wird.

Listing 8









  
Daten cachen

Soll upshot.js geänderte Entitäten nicht unmittelbar nach deren Modifikation zum Service Retour senden, ist bei der Erzeugung eines DataSources die Eigenschaft bufferChanges auf true zu setzen (Listing 9). Somit hält upshot.js sämtliche Änderungen vorerst lediglich lokal vor. Um den DataSource zu veranlassen, sämtliche Änderungen an den Service weiterzugeben, bringt der Entwickler an der gewünschten Stelle dessen Funktion commitChanges zur Ausführung. Das Gegenstück revertChanges führt hingegen dazu, dass der DataSource die vorgehaltenen Änderungen verwirft.

Listing 9

dataSource = upshot.RemoteDataSource({
        providerParameters: { 
               url: "/data/Buchungen", operationName: "GetBuchungen" },
        entityType: "HotelBuchung:#HotelReservierung.Data",
        //provider: ...,
        mapping: HotelBuchung,
        bufferChanges: true
    }); 

    [...]

    dataSource.commitChanges();
    // dataSource.revertChanges();  
Upshot.js Provider

Obwohl eine upshot.js-DataSource standardmäßig einen Web-API-basierten Service erwartet, der auf der Basisklasse DbDataController beruht, kann sie durch die Bereitstellung von Providern auch zur Zusammenarbeit mit anderen Services bewegt werden. Der gewünschte Provider wird bei der Erzeugung des DataSource-Objektes über die Eigenschaft provider angegeben. Listing 9 deutet dies mit einem Kommentar an.

Möchte der Entwickler einen Provider explizit angeben, kann er oder sie eine entsprechende Konstruktor-Funktion an diese Eigenschaft zuweisen. Derzeit stehen drei solche Konstruktor-Methoden zur Verfügung. Die Funktion upshot.DataProvider erzeugt den standardmäßigen Provider für die Kommunikation mit DbDataController. Ein upshot.riaDataProvider richtet sich an über WCF-RIA-Service veröffentlichte JSON-basierte Endpunkte und upshot.ODataDataProvider erlaubt die Kommunikation mit beliebigen OData-Services [5], wobei dieser derzeit lediglich Read-Only-Szenarien unterstützt.

Informationen von Microsoft [6] zufolge, wird es künftig auch einen offlinefähigen Provider geben. Dieser soll in der Lage sein, abgerufene Daten im HTML5 localStore oder in einem damit vergleichbaren lokalen Speicher, vorzuhalten. Somit kann der Benutzer auch im Offlinebetrieb mit diesen Daten arbeiten und eventuelle Änderungen zum Server Retour senden, sobald wieder eine Verbindung mit dem Netzwerk besteht. Zusammen mit dem HTML5-Anwendungs-Cache [7], der es erlaubt, abgerufene Ressourcen, wie Seiten, Skripte oder Bilder, lokal zu cachen, wird die Entwicklung von offlinefähigen Webanwendungen somit erheblich vereinfacht.

Fazit

Single Page Applications stellen eine attraktive Möglichkeit zur Entwicklung von Anwendungen, die auf sämtlichen mobilen als auch klassischen Plattformen eingesetzt werden können, dar. Die kommenden Neuerungen in ASP.NET MVC 4 werden die Entwicklung von Anwendungen dieser Art erheblich vereinfachen. Positiv fällt dabei auf, dass das Rad nicht an allen Stellen neu erfunden wurde, sondern stattdessen auf etablierte und freie JavaScript-Bibliotheken, wie jQuery aber auch vor allem kockout.js gesetzt wird.

Auf der anderen Seite ist es fraglich, ob es sinnvoll ist, all diese Neuerungen unter dem Sammelbegriff SPA zusammenzufassen, zumal die bereitgestellten Neuerungen streng genommen nichts mit Single Page Applications zu tun haben. Vielmehr erlauben sie die Implementierung von Webanwendungen, die zur Steigerung der Benutzerfreundlichkeit stark auf JavaScript setzen, um zum Beispiel Post-Backs zu vermeiden. Auch der Aspekt der Offlinefähigkeit ist mittlerweile durch den HTML5 localStore sowie HTML5-Anwendungs-Cache einfach ohne eine Single-Page-Architektur zu erreichen. Dies könnte ein Indiz dafür sein, dass es derzeit keinen (modernen) Begriff für diese Art von Webanwendungen gibt, die im Wesentlichen auf bereits seit einigen Jahren bekannten und möglichen Ideen fußen.

Manfred Steyer (softwarearchitekt.at) ist freiberuflicher Trainer und Berater bei IT-Visions (it-visions.de) sowie Leiter des in Deutschland und Österreich stattfindenden berufsbegleitenden Master-Studiengangs Software Engineering Leadership der FH CAMPUS 02 (campus02.at). Er spricht regelmäßig auf Fachkonferenzen und schreibt für Microsoft Press und Hansa. Derzeit schreibt er gemeinsam mit Dr. Holger Schwichtenberg bei Microsoft Press über „Moderne Webanwendungen“ mit ASP.NET MVC und JavaScript APIs.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -