Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Problemloser Datenzugriff in JavaScript – Ja, JavaScript
Was die Softwareentwicklung betrifft, verwende ich meistens grundlegenden Code, trotzdem muss ich auch relativ viel Entwicklungsarbeit auf der Clientseite leisten. Selbst in dieser Kolumne beschäftige ich mich ausführlich mit den Teilen der Clientanwendungen, die Überschneidungen mit dem Datenzugriff aufweisen. Wenn es sich bei der Clientseite jedoch um JavaScript handelt, war es nie einfach – meine Kenntnisse in JavaScript kann ich leider immer noch verbessern, und jede Lernkurve ist schwierig für mich (und meine Follower auf Twitter, die immer wieder mitbekommen, wie ich Dampf ablasse). Wenn ich dann aber einen Durchbruch geschafft habe, war es stets die Mühe wert. Und alles, das mir die Arbeit mit JavaScript erleichtert, begrüße ich mit offenen Armen. Als uns Ward Bell von IdeaBlade anlässlich einer Benutzergruppenpräsentation von Vermont.NET zu Single Page Applications Einblicke in eine Open-Source-Datenzugriffs-API für JavaScript gewährte, die er zusammen mit seinem Team entworfen hatte, war ich daher äußerst interessiert. Meine Erfahrungen mit Entity Framework haben gezeigt, dass sich das Gezeigte sehr gut mit dem Einsatz von EF für die clientseitige Webentwicklung vergleichen lässt. Die API hat den Namen „Breeze“ und liegt zum Zeitpunkt der Verfassung dieses Artikels als Betaversion vor. Bell nahm sich sehr viel Zeit für mich, um mir für diese Kolumne weitere Informationen über Breeze zu liefern. Unter breezejs.com finden Sie die API und zudem eine beeindruckende Sammlung von Dokumentationen, Videos und Beispielen.
In meiner Kolumne zu Datenpunkten „Datenbindung von OData in Webanwendungen mit Knockout.js“ (msdn.microsoft.com/magazine/jj133816) vom Juni 2012 habe ich mich auf eine einfachere Ausführung der clientseitigen Datenbindung mithilfe von Knockout.js konzentriert. Breeze lässt sich nahtlos zusammen mit Knockout einsetzen. Deshalb möchte ich hier noch einmal auf das Beispiel aus der Kolumne vom Juni zurückkommen. Ich möchte feststellen, wie sich meine Codierungsarbeit im Beispielworkflow durch die Einführung von Breeze vereinfachen lässt:
- Datenabruf vom Server
- Bindung und Präsentation dieser Daten
- Rückübertragung von Änderungen mit Push zum Server
Ich werde die kritischen Teile einer aktualisierten Lösung durchgehen, damit Sie sehen können, wie sich die Puzzleteile zusammenfügen. Wenn Sie meinen Ausführungen mit einer fertig eingerichteten Lösung folgen und das Ganze selber testen möchten, können Sie die komplette Lösung von der Website archive.msdn.microsoft.com/mag201212DataPoints herunterladen.
Das Originalbeispiel
Hier finden Sie die wichtigsten Schritte meiner früheren Lösung:
- Auf der Clientseite habe ich eine Person-Klasse definiert, die von Knockout für die Datenbindung verwendet werden kann:
function PersonViewModel(model) {
model = model || {};
var self = this;
self.FirstName = ko.observable(model.Name || ' ');
self.LastName = ko.observable(model.Name || ' ');
}
- Meine Daten wurden über einen OData-Datendienst bereitgestellt. Somit konnte ich mit datajs, einem Toolkit zur Verwendung von OData mit JavaScript, auf die Daten zugreifen.
- Anhand der Abfrageergebnisse (die als JSON zurückgegeben werden) erstellte ich eine PersonViewModel-Instanz mit den Werten.
- Meine App wies Knockout an, die Datenbindung vorzunehmen. Dabei wurden auch die vom Benutzer vorgenommenen Änderungen koordiniert.
- Ich verwendete die geänderte PersonViewModel-Instanz, um das JSON-Objekt mit diesen Werten zu aktualisieren.
- Schließlich übergab ich das JSON-Objekt an datajs, damit es über OData wieder auf dem Server gespeichert werden konnte.
Dabei kümmerte ich mich nicht einmal um die zugehörigen Daten, da dieses kleine Beispiel sonst noch komplexer geworden wäre.
Ein aktualisierter Dienst, der ASP.NET Web API verwendet
Mit Breeze kann ich HTTP-Aufrufe an den OData-Dienst oder einen von der ASP.NET Web API (asp.net/web-api) definierten Dienst ausführen. Ich stellte meinen Dienst auf die ASP.NET Web API um, die auch mit dem zuvor von mir verwendeten EF-Modell funktioniert – mit einem Zusatz. Mein früheres Beispiel machte nur Person-Daten verfügbar. Jetzt habe ich zugehörige Daten in Form einer Device-Klasse, da jeder mir bekannte Entwickler eine kleine Sammlung persönlicher Geräte besitzt. Die von meiner ASP.NET Web API zur Verfügung gestellten Funktionen sind eine GET-Funktion, von der Person-Daten zurückgegeben werden, eine weitere GET-Funktion für Device-Daten und eine POST-Funktion zum Speichern der Änderungen. Außerdem verwende ich eine Metadatenfunktion, um das Schema meiner Daten zur Verfügung zu stellen (siehe Abbildung 1). Anhand dieser Metadaten wird mein Modell für Breeze verständlich.
Abbildung 1: Hauptbestandteile meines Web API-Diensts
readonly EFContextProvider<PersonModelContext> _contextProvider =
new EFContextProvider<PersonModelContext>();
[AcceptVerbs("GET")]
public IQueryable<Person> People()
{
return _contextProvider.Context.People;
}
[AcceptVerbs("GET")]
public IQueryable<Device> Devices()
{
return _contextProvider.Context.Devices;
}
[AcceptVerbs("POST")]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
[AcceptVerbs("GET")]
public string Metadata()
{
return _contextProvider.Metadata();
}
Breeze.NET auf dem Server
In diesen Methoden verwende ich eine _contextProvider-Variable. Ich rufe keine Methoden direkt von meinem DbContext-EF („PersonModelContext“) auf. Stattdessen habe ich sie in „EFContextProvider“ aus Breeze eingeschlossen. Von dort stammen die Methode „_contextProvider.Metadata“ sowie die Signatur von „SaveChanges“, das einen saveBundle-Parameter annimmt. Über „saveBundle“ ermöglicht es mir Breeze, von meiner App aus einen Satz Datenänderungen zu senden, die dann an „DbContext“ übergeben werden, um in der Datenbank gespeichert zu werden.
Ich habe meiner ASP.NET Web API-Anwendung den Namen „BreezyDevices“ gegeben. Somit kann ich das Schema jetzt mit „http://localhost:19428/api/breezydevices/metadata“ anfordern. Und ich kann Daten abfragen, indem ich eine der GET-Methoden angebe: „http://localhost:19428/api/breezydevices/people“.
Da Breeze auf der Clientseite Abfragen vornimmt und in einem ASP.NET Web API-Remotedienst speichert, kann ich datajs aus meiner Clientanwendung löschen.
So unterstützt Breeze mein Beispiel
Im Rahmen dieses Beispiels verwende ich Breeze, um mich auf drei besondere Problempunkte zu konzentrieren:
- Mein Dienst akzeptiert und gibt reines JSON zurück. Ich muss aber für die Datenbindung in der UI JavaScript-Objekte mit Knockout-Observable-Eigenschaften verwenden.
- Ich möchte zugehörige Daten einschließen, auf dem Client ist das aber schwer.
- Ich muss mehrere Änderungen zur Speicherung an den Server senden.
Mit Breeze kann ich die Datenbindung direkt bei den resultierenden Daten vornehmen. Ich konfiguriere Breeze für die Verwendung von Knockout. Daraufhin erstellt Breeze für mich im Hintergrund Knockout-Observable-Eigenschaften. Somit wird das Arbeiten mit zugehörigen Daten wesentlich einfacher, da ich diese nicht von JSON in bindbare Objekte übersetzen muss. Auch brauche ich Diagramme auf der Clientseite nicht zusätzlich mithilfe meiner Abfrageergebnisse neu zu definieren.
Bei der Verwendung von Breeze fällt ein gewisser Konfigurationsaufwand auf der Serverseite an. Die Einzelheiten dazu sind in der Breeze-Dokumentation zu finden. Somit kann ich mich auf den Teil des clientseitigen Datenzugriffs im Beispiel konzentrieren. Und da Breeze noch so viel mehr bietet, als ich in diesem Beispiel nutzen werde, können Sie, sobald ich Ihnen einen Vorgeschmack darauf gegeben habe, unter breezejs.com mehr dazu nachlesen.
Abbildung 2 zeigt, an welcher Stelle des Workflows auf der Serverseite und auf der Clientseite sich Breeze einfügt.
Abbildung 2: Die Breeze.NET-API wirkt auf dem Server, die BreezeJS-API auf dem Client
Abfragen über Breeze
Dank meiner Erfahrungen mit OData und mit Entity Framework bin ich mit Abfragen über Breeze bereits vertraut. Ich werde mit der EntityManager-Klasse von Breeze arbeiten. Der EntityManager kann das von den Metadaten Ihres Diensts gelieferte Datenmodell lesen und eigenständig JavaScript-„Entitätsobjekte“ erzeugen. So brauchen Sie keine Entitätsklassen zu definieren oder Zuordnungen zu erstellen.
Aber auch auf der Clientseite ist ein gewisser Konfigurationsaufwand erforderlich. Beispielsweise werden vom folgenden Codeausschnitt Verknüpfungen zu einigen Breeze-Namespaces erstellt, und Breeze wird dann für die Verwendung von Knockout und ASP.NET Web API konfiguriert:
var core = breeze.core,
entityModel = breeze.entityModel;
core.config.setProperties({
trackingImplementation: entityModel.entityTracking_ko,
remoteAccessImplementation: entityModel.remoteAccess_webApi
});
Breeze lässt sich für die Verwendung einer Reihe alternativer Frameworks zur Datenbindung (wie Backbone.js oder Windows-Bibliothek für JavaScript) sowie von Datenzugriffstechnologien (wie OData) konfigurieren.
Als Nächstes erstelle ich einen EntityManager, dem die relative URI meines Diensts bekannt ist. Der EntityManager ist mit einem Kontext von Entity Framework oder OData vergleichbar. Er fungiert als Gateway zu Breeze und führt die Zwischenspeicherung von Daten durch:
var manager = new entityModel.EntityManager('api/breezydevices');
Nun kann ich eine Abfrage definieren und diese von EntityManager ausführen lassen. Dieser Code unterscheidet sich nicht allzu sehr von der Verwendung von Entity Framework und LINQ to Entities oder vom Arbeiten mit einer der APIs des OData-Clients. Deshalb hat mir dieser Teil beim Erlernen der Anwendung von Breeze am meisten Spaß gemacht:
function getAllPersons(peopleArray) {
var query = new entityModel.EntityQuery()
.from("People")
.orderBy("FirstName, LastName");
return manager
.executeQuery(query)
.then(function (data) {
processResults(data,peopleArray); })
.fail(queryFailed);
};
Ich mache das auf der Clientseite und kann meine Abfrage asynchron ausführen. Deshalb kann ich mit der Methode „executeQuery“ definieren, was bei erfolgreicher Ausführung der Abfrage (.then) bzw. bei nicht erfolgreicher Ausführung (.fail) zu tun ist.
Ich leite ein Array (bei dem es sich um ein Knockout-Observable-Array handelt, wie Sie bald sehen werden) an „getAllPersons“ weiter. Bei erfolgreicher Ausführung der Abfrage leite ich dieses Array an die Methode „processResults“ weiter, die das Array anschließend leert und mit den Daten vom Dienst füllt. Zuvor hätte ich die Ergebnisse durchlaufen und selbst die einzelnen PersonViewModel-Instanzen erstellen müssen. Mithilfe von Breeze kann ich diese zurückgegebenen Daten direkt verwenden:
function processResults(data, peopleArray) {
var persons = data.results;
peopleArray.removeAll();
persons.forEach(function (person) {
peopleArray.push(person);
});
}
So erhalte ich ein Array von Person-Objekten, das ich in der Ansicht präsentieren werde.
Die Funktion „getAllPersons“ ist in einem Objekt enthalten, das ich als Datendienst bezeichne. Den Datendienst verwende ich im nächsten Codeelement.
Ein sich selbst füllendes Ansichtsmodell
Im Beispiel aus meinem Knockout-Artikel vom Juni waren die Abfrage und die Ergebnisse getrennt von der PersonViewModel-Klasse, die ich für die Datenbindung in der Ansicht verwendete. Deshalb führte ich die Abfrage aus und übersetzte die Ergebnisse mit dem von mir geschriebenen Zuordnungscode in eine PersonViewModel-Instanz. Da ich bei Breeze weder Zuordnungscode noch ein PersonViewModel brauche, gestalte ich meine App dieses Mal etwas intelligenter: Sie soll ein Array von Person-Objekten anzeigen, die mein Datendienst von der Datenbank abgerufen hat. Dazu habe ich jetzt ein Objekt mit dem Namen „PeopleViewModel“. Dieses macht eine People-Eigenschaft verfügbar, die ich als Knockout-Observable-Array definiert habe, das ich mithilfe von „dataservice.getAllPersons“ fülle.
(function (root) {
var app = root.app;
var dataservice = app.dataservice;
var vm = {
people: ko.observableArray([]),
}
};
dataservice.getAllPersons(vm.people);
app.peopleViewModel = vm;
}(window));
Im Beispiel, das Sie herunterladen können, finden Sie eine Datei mit dem Namen „main.js“, die den Ausgangspunkt für die Anwendungslogik darstellt. Sie enthält die folgende Codezeile, mit der die Knockout-Methode „applyBindings“ aufgerufen wird:
ko.applyBindings(app.peopleViewModel, $("content").get(0));
Die Methode „applyBindings“ verbindet die Eigenschaften des Ansichtsmodels über die in der Ansicht deklarierten Datenbindungen mit den Steuerelementen der HTML-Benutzeroberfläche.
In diesem Fall handelt es sich bei der Ansicht um kleine HTML-Teile in „index.cshtml“. Das Datenbindungsmarkup von Knockout bindet und zeigt den Vor- und Nachnamen aller Person-Objekte im People-Array an:
<ul data-bind="foreach: people">
<li class="person" >
<label data-bind="text: FirstName"></label>
<label data-bind="text: LastName"></label>
</li>
</ul>
Bei der Ausführung meiner App erhalte ich eine schreibgeschützte Ansicht der Person-Daten (siehe Abbildung 3).
Abbildung 3: Einfache Nutzung von Daten in JavaScript mithilfe von Breeze und Knockout
Optimierung von JavaScript und Knockout zu Bearbeitungszwecken
In der Kolumne vom Juni hatte ich bereits beschrieben, dass sich mit Knockout die Datenbindung zu Bearbeitungszwecken einfach gestaltet. In Verbindung mit Breeze ist dies eine hervorragende Kombination, um Daten mühelos bearbeiten und wieder auf dem Server speichern zu können.
Zunächst füge ich dem Datendienstobjekte eine Funktion hinzu, die die manager.saveChanges-Methode von Breeze aufruft. Sobald sie aufgerufen ist, bündelt EntityManager von Breeze die anstehenden Änderungen und übermittelt sie per POST-Anforderung an den Web API-Dienst:
function saveChanges() {
manager.saveChanges();
}
Anschließend stelle ich die neue Funktion „saveChanges“ als ein Feature des Datendiensts zur Verfügung:
var dataservice = {
getAllPersons: getAllPersons,
saveChanges: saveChanges,
};
Das PeopleViewModel-Objekt muss nun seine eigene Speichermethode für die Bindung an der Ansicht zur Verfügung stellen. Die Speicherfunktion des Ansichtsmodells delegiert an die saveChanges-Methode des Datendiensts. Hier definiere ich das Speichern des Ansichtsmodells mit einer „anonymen Funktion“ von JavaScript:
var vm = {
people: ko.observableArray([]),
save: function () {
dataservice.saveChanges();
},
};
Als Nächstes ersetze ich die Beschriftungen durch Eingabeelemente (Textfelder), damit der Benutzer die Person-Objekte bearbeiten kann. Um eine bidirektionale Bindung an die Benutzereingabe zu ermöglichen, muss ich anstelle von „Text“ das Knockout-Schlüsselwort „Wert“ verwenden. Außerdem füge ich mit einem Klickereignis, das an die PeopleViewModel.save-Methode gebunden ist, ein Bild hinzu:
<img src="../../Images/save.png"
data-bind="click: save" title="Save Changes" />
<ul data-bind="foreach: people">
<li class="person" >
<form>
<label>First: </label><input
data-bind="value: FirstName" />
<label>Last: </label> <input
data-bind="value: LastName" />
</form>
</li>
</ul>
Das war’s. Breeze und Knockout übernehmen den Rest! Die für die Bearbeitung angezeigten Daten sind in Abbildung 4 dargestellt.
Abbildung 4: Daten mithilfe von Breeze mittels JavaScript speichern
Ich kann alle oder nur beliebige Felder bearbeiten und auf die Schaltfläche „Speichern“ klicken. Der EntityManager von Breeze sammelt alle Datenänderungen und überträgt sie mit Push zum Server, der sie wiederum zur Aktualisierung der Datenbank an Entity Framework sendet. In dieser Demo berücksichtige ich keine Einfügungen und Löschungen. Breeze kommt jedoch auch mit diesen Änderungen sehr gut zurecht.
Und zu guter Letzt – Hinzufügen in zugehörigen Daten
Dieser Teil einer JavaScript-Anwendung wird von vielen Entwicklern gefürchtet – und genau das hat mich bewogen, diese Kolumne zu schreiben.
Ich nehme an meinem Skript eine kleine Änderung vor und füge dem Formular ein kleines Markup hinzu, das dafür sorgt, dass jede Person eine bearbeitbare Master-/Detailperson wird.
Die Skriptänderung erfolgt im Datendienst. Dort ändere ich die Abfrage, indem ich sie der Abfragemethode „Expand“ von Breeze hinzufüge, um die Devices der einzelnen Personen zusammen mit der Person mittels Eager Load zu laden. „Expand“ ist ein Begriff, den Sie vielleicht von OData oder NHibernate kennen. Er ist dem „Include“ im Entity Framework ähnlich. (Breeze unterstützt ebenfalls das einfache nachträgliche Laden zugehöriger Daten):
var query = new entityModel.EntityQuery()
.from("People")
.expand("Devices")
.orderBy("FirstName, LastName");
Anschließend ändere ich die Ansicht dahingehend, dass sie weiß, wie die Device-Daten angezeigt werden sollen (siehe Abbildung 5).
Abbildung 5: Ändern der Ansicht zur Anzeige der Device-Daten
<ul data-bind="foreach: people">
<li class="person" >
<form>
<label>First: </label><input
data-bind="value: FirstName" />
<label>Last: </label> <input
data-bind="value: LastName" />
<br/>
<label>Devices: </label>
<ul class="device" data-bind="foreach: Devices">
<li>
<input data-bind="value: DeviceName"/>
</li>
</ul>
</form>
</li>
</ul>
So, jetzt ist es passiert. In Abbildung 6 wird dargestellt, dass Breeze das Laden per Eager Loading und das Erstellen der Diagramme auf der Clientseite ausführt. Breeze koordiniert außerdem die Daten, die zu Aktualisierungszwecken zum Server zurückgeschickt werden sollen. Auf der Serverseite sortiert „EFContextProvider“ aus Breeze alle empfangenen Änderungsdaten aus und stellt sicher, dass das Entity Framework alles erhält, was benötigt wird, um die Daten in die Datenbank zu schreiben.
Abbildung 6: Nutzen und Speichern zugehöriger Daten
Bei einer 1:n-Beziehung war das kein Problem, doch zum Zeitpunkt der Erstellung dieses Artikels wurden von der Betaversion noch keine m:n-Beziehungen unterstützt.
Problemloser clientseitiger Datenzugriff
Bell berichtet mir, dass Breeze aus seiner eigenen leidvollen Erfahrung mit einem Projekt entstanden ist, das sowohl einen hohen JavaScript-Programmieraufwand als auch besonders viele Datenzugriffe erforderte. IdeaBlade, das Unternehmen, in dem er arbeitet, hat sich seit jeher auf die Erstellung von Lösungen spezialisiert, mit denen Probleme beim Umgang mit nicht verbundenen Daten gelöst werden können. Die Entwickler konnten dabei sehr viele Erfahrungen in dieses Open-Source-Projekt mit einbringen. Projekten, bei denen viel JavaScript zum Einsatz kommt, stehe ich stets zögerlich gegenüber, zum einen, da meine Kenntnisse noch verbesserungswürdig sind, und zum anderen, da ich weiß, dass mich der Datenzugriff ärgern würde. Ich war von Breeze sofort begeistert. Und auch wenn ich bisher nur an der Oberfläche gekratzt habe, konnte mich der zuletzt von mir in diesem Artikel beschriebene Teil – wie einfach sich zugehörige Daten nutzen und speichern lassen – letztendlich überzeugen.
Julie Lerman ist Microsoft MVP, .NET-Mentor und Unternehmensberaterin und lebt in den Bergen von Vermont. Sie hält bei User Groups und Konferenzen in der ganzen Welt Vorträge zum Thema Datenzugriff und anderen Microsoft .NET-Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Verfasserin von „Programming Entity Framework“ (2010) sowie der Ausgaben „Code First“ (2011) und „DbContext“ (2012). Alle Ausgaben sind im Verlag O’Reilly Media erschienen. Folgen Sie ihr auf Twitter unter twitter.com/julielerman.
Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Ward Bell