im ersten Teil habe ich eine groben Überblick über das Projekt gegeben.

im zweiten Schritt haben wir Windows Azure Cloud Services einbezogen.

Im dritten Teil haben wir angeschaut, was uns OData und WCF Data Services und das Entity Framework 4 (EF4) bringen können.

im vierten Teil haben wir mit einer ASP.NET MVC Seite die OData-Daten als Webservice aufgerufen und als kleine Photos auf eine Bing Map gezeichnet.

 

In diesem Beitrag wollen wir aus der ASP.NET MVC Web-Seite heraus die Daten unseres Azure-basierten Odata Services bearbeiten (CRUD Operiationen).

Dazu werden wir nicht den klassischen MVC-Controller/Model Weg gehen ( das würde natürlich auch funktionieren). Ich möchte vielmehr zeigen, wie wir die OData Schnittstelle per Ajax ansprechen können. So wird der Mashup-Charakter  unserer Applikation mehr betont: eine HTML Seite dient als Hülle für eine AJAX Anwendung, die Daten aus verschiedenen Cloud-Datenquellen kombiniert:  hier sind es Azure und Bing Maps.

Wir könnten unsere Applikation noch mit weiteren öffentlichen APIs erweitern , um z.B. Restaurants in der Nähe (z.B. Foursquare API) anzuzeigen, Schnäppchen zu machen  (z.B. Groupon API ),  oder um uns zu den Social Networks (Facebook Places, Google Latitude) zu verbinden. Alles was wir brauchen sind Longitude und Latitude Werte, damit können wir auf der Bing Map die entsprechende Information in unserer Nähe einzeichnen ( siehe letzter Beitrag) . Wenn man als Geo-Information “nur” eine postalische Adresse zur Verfügung  hat, kann man über das  Bing Services REST API  dazu die Longitude/Latitude-Daten  abfragen!

z.B. die Adresse von Microsoft in Redmond:

http://dev.virtualearth.net/REST/v1/Locations/US/WA/98052/Redmond/1 Microsoft Way?o=xml&key=BingMapsKey

Noch einfacher geht es direkt über das Map Control mit der .find(Suchadresse) Methode.

 

CRUD mit OData und Ajax Data-Binding

Als alter WinForms Programmierer träume ich immer noch den Traum, dass so etwas Banales wie Data-Binding eigentlich rein deklarativ, also ohne zusätzlichen, selbstgeschriebenen  Code funktionieren sollte. Dies gilt ( bei einfachen Anwendungen) soweit auch bei .NET Client Programmierung, aber wie sieht es damit bei Web-Applikationen aus ?

Es gibt(gab?) aus dem Hause Microsoft eine Lösung dafür, die aber wohl eher als experimentell zu bezeichnen ist:

Data Binding in ASP.NET AJAX 4.0

Dino Esposito  hat hier einen einführenden Artikel geschrieben, der mittlerweile überarbeitet wurde. Ich konnte leider nicht viel weiterführendes Material finden.

Das hat zum Einen damit zu tun, dass das damalige Produkt “ADO.NET Data Services”  jetzt als “freies” Projekt OData auf OData.org lebt, und MS der jungen Technologie einige Libraries als “Mitgift” spendiert hat. Dabei haben sich auch die Namespaces entsprechend geändert! Die ehemalige ADO.NET Ajax Client Library ist jetzt bei den OData APIs zu finden.

Möglicherweise ist diese Technologie (ASP.NET Ajax Data Binding) aber auch an einem Punkt angekommen, wo es sich mit dem großen Erfolg von JQuery überholt hat. Im JQuery Umfeld wird an dem Data Link Plugin gearbeitet (Microsoft trägt auch zu dem Projekt bei). Diese Library ist generell für Binding zu verwenden (ähnlich dem Silverlight Property Binding), ist aber nicht unbedingt geeignet, um schnell eine Datenquelle an ein paar Input Controls zu binden. Deshalb hatte ich mich bei unserem Beispiel für die OData AJAX Variante entschieden. Der Zugriff auf OData  Webservices funktioniert einwandfrei und das Databinding kann zumindest beispielhaft angewendet werden.

 

Wie funktioniert das ?

Zunächst müssen die AJAX Script Libraries referenziert werden:

<script src="../../Scripts/Start.debug.js" type="text/javascript"></script>

 

jetzt brauchen wir den initialen Aufruf der Library (lädt benötigte Komponenten nach) :

Sys.require([Sys.components.dataView, Sys.components.openDataContext]);

und wenn alles geladen ist:

Sys.onReady(function () {
            dataContext = Sys.create.openDataContext(
               {
                   serviceUri: "/WcfDS.svc",
                   mergeOption: Sys.Data.MergeOption.appendOnly
               });

            dataContext.initialize();
            oDataProxy = new Sys.Data.OpenDataServiceProxy('/WcfDS.svc');
            GetMap();
            AddMyLayer(VEDataType.GeoRSS);

        
        });

wir initialisieren ein dataContext Objekt auf unseren OData Service.

Dazu noch ein OpenDataServiceProxy, das brauchen wir für eine separate Abfrage (später im Text).

 

Aber wo ist das deklarative Data-Binding ?

Hier, bitteschön:

<div id='dataform' style="margin-left: 15px; float:left">
       
        <table sys:attach="dataview" id="Editor" class="sys-template" dataview:dataprovider="{{ dataContext }}"
            dataview:autofetch="true"
            dataview:onfetchsucceeded="{{ onFetchSucceededEditor }}"
            dataview:fetchoperation="TVDLunch?%24filter=Nickname%20eq%20'<%: Page.User.Identity.Name %>'">
            <tr>
                <td>
                    your short name:</td>
                <td>
                    <%= Html.SysTextBox("NickName", "{binding Nickname, mode=twoWay}")%>
                </td>
            </tr>
            <tr>
                <td>
                    Id
                </td>
                <td>
                    <%= Html.SysTextBox("Id", "{binding Id, mode=twoWay}")%>
                </td>
            </tr>
   … usw.

Man kann erkennen, dass die Zellen der HTML Tabelle direkt an Felder aus der Datenquelle verknüpft werden.

Die Datenquelle (dataContext)  ist ein Objekt, das wir in derSys. onReady Funktion initialisiert haben.

Es kennt die URI zu Web-Service, deshalb kann der String im Property dataview:fetchoperation als Abfrage direkt ausgeführt werden. Hier rufen wir die TVDLunchTabelle ab, mit dem Filter auf dem Nickname des angemeldeten Benutzers. Alle Input Felder in der HTML-Table sind an die Felder der Tabelle gebunden. Die Abfrage wird automatisch beim Laden der Seite ausgeführt ( autofetch = true) .

Ganz einfach, oder ?

Nun ja, den “bösen” Code habe ich noch nicht gezeigt. Alle “Sonder”-Fälle müssen selbst behandelt werden, z.B. wenn kein Datensatz gefunden wurde…

Und wie geht Insert, Update und Delete ?

Eigentlich ganz einfach.

function SaveLoc() {
            var pendingChanges = dataContext.get_hasChanges();
            if (pendingChanges !== true) {
                alert("No pending changes to save.");
                return;
            }

            var changes = dataContext.get_changes();
           
            if (confirm(buffer))
                dataContext.saveChanges(saveSucceeded, null, dataContext);


        }

        function DeleteLoc() {

            dv = $find("Editor");
            var data = dv.get_data()[0];

            dataContext.removeEntity(data);
            DeleteShape();
            dataContext.saveChanges(deleteSucceeded, null, dataContext);
           
         }

Es gibt noch ein bisschen mehr Code, z.B. um ein neues Datenobjekt zu erzeugen und dem dataContext anzuhängen (Insert). Außerdem muss die Bing Map nach jeder Aktion synchronisiert werden um die eigene Position anzuzeigen. Als alter Spaghetti-Programmierer hatte ich meine liebe Mühe mit dem asynchronen Programmiermodell ( was kommt nach was ? Welcher Code wird wann ausgeführt?) Das Ergebnis meiner Bemühungen zeige ich hier lieber nicht :-)

Wer sich ernsthaft dafür interessiert, möge mir bitte schreiben (Kontakt-Link im Menü).

Für die Liste der Kollegen in der Nähe habe ich das gleiche Prinzip angewendet:

Eine HTML Tabelle (der Body) ist an den dataContext gebunden.

 <tbody sys:attach="dataview" class="sys-template" dataview:dataprovider="{{ dataContext }}"
                    id="tbodycollegues">
                    <tr>
                        <td>
                            {{ Nickname }}
                        </td>
                        <td>
                            {{ LocationText }}
                        </td>
                        <td>
                            {{ Distance }}
                        </td>
                        <td>
                           <a href="mailto: {{ Email }}"> {{ Email }} </a>
                        </td>
                        <td>
                            {{ Phone }}
                        </td>
                    </tr>
                </tbody>

Beim erfolgreichen Ausführen der ersten Query (siehe oben) wird im Callback die Query für diese Tabelle ausgeführt.

function UpdateColleguesListPoint(LatLong) {

       var pointstring = LatLongToPoint(LatLong);

       var querystring = "FindColleguesNearPoint?Point=" 
           + "'" + pointstring + "'" + "&howFar='10000'" 
           + "&nickName='<%: Page.User.Identity.Name %>'";
       oDataProxy.query(querystring, querySuceededCallback, queryFailedCallback);
   }


function querySuceededCallback(result, context, operation) {

       if (result.toString() == "")
           $get("infotext").innerHTML = "no collegues found within 10km!";
       else
           $get("infotext").innerHTML = result.length.toString() + " collegues found within 10km!";

       $find("tbodycollegues").set_data(result);

   }

Hier sehen wir wieder einen Querystring, diesmal für eine Stored Procedure:

FindColleguesNearPoint?Point='POINT(7.5888276100158745 47.5491527235811)'&howFar='10000'

Wie kann unsere OData SP mit diesen Angaben die Distanz zum angemeldeten Benutzer berechnen ?

das Zauberwort heißt Spatial Data und ist ein Feature von SQL Server. Im nächsten Beitrag sehen wir wie das funktioniert!

Danach werden wir die ganze Funktionalität (Bing Map Darstellung, OData CRUD) entsprechend auf dem Windows Phone 7 implementieren.