Grundlegendes zu Webdiensten von ASP.NET AJAX

von Scott Cate

Webdienste sind ein integraler Bestandteil von .NET Framework, das eine plattformübergreifende Lösung für den Austausch von Daten zwischen verteilten Systemen bietet. Obwohl Webdienste normalerweise verwendet werden, um verschiedenen Betriebssystemen, Objektmodellen und Programmiersprachen das Senden und Empfangen von Daten zu ermöglichen, können sie auch verwendet werden, um Daten dynamisch in eine ASP.NET AJAX-Seite einzufügen oder Daten von einer Seite an ein Back-End-System zu senden. All dies kann ohne Rückgriff auf Postbackvorgänge erfolgen.

Aufrufen von Webdiensten mit ASP.NET AJAX

Dan Wahlin

Webdienste sind ein integraler Bestandteil von .NET Framework, das eine plattformübergreifende Lösung für den Austausch von Daten zwischen verteilten Systemen bietet. Obwohl Webdienste normalerweise verwendet werden, um verschiedenen Betriebssystemen, Objektmodellen und Programmiersprachen das Senden und Empfangen von Daten zu ermöglichen, können sie auch verwendet werden, um Daten dynamisch in eine ASP.NET AJAX-Seite einzufügen oder Daten von einer Seite an ein Back-End-System zu senden. All dies kann ohne Rückgriff auf Postbackvorgänge erfolgen.

Während das ASP.NET AJAX UpdatePanel-Steuerelement eine einfache Möglichkeit zum Aktivieren von ASP.NET Seite bietet, kann es vorkommen, dass Sie dynamisch auf Daten auf dem Server zugreifen müssen, ohne ein UpdatePanel zu verwenden. In diesem Artikel erfahren Sie, wie Sie dies erreichen, indem Sie Webdienste innerhalb ASP.NET AJAX-Seiten erstellen und nutzen.

Dieser Artikel konzentriert sich auf funktionen, die im Kern ASP.NET AJAX-Erweiterungen verfügbar sind, sowie auf ein Webdienst-fähiges Steuerelement im ASP.NET AJAX Toolkit namens AutoCompleteExtender. Zu den behandelten Themen gehören das Definieren von AJAX-fähigen Webdiensten, das Erstellen von Clientproxys und das Aufrufen von Webdiensten mit JavaScript. Außerdem erfahren Sie, wie Webdienstaufrufe direkt an ASP.NET Seitenmethoden ausgeführt werden können.

Webdienstkonfiguration

Wenn mit Visual Studio 2008 ein neues Websiteprojekt erstellt wird, enthält die datei web.config eine Reihe neuer Ergänzungen, die Benutzern früherer Versionen von Visual Studio möglicherweise nicht vertraut sind. Einige dieser Änderungen ordnen das Präfix "asp" ASP.NET AJAX-Steuerelementen zu, sodass sie auf Seiten verwendet werden können, während andere erforderliche HttpHandler und HttpModule definieren. In der Liste 1 werden Änderungen <httpHandlers> am Element in web.config angezeigt, die sich auf Webdienstaufrufe auswirken. Der standardmäßige HttpHandler, der zum Verarbeiten von ASMX-Aufrufen verwendet wird, wird entfernt und durch eine ScriptHandlerFactory-Klasse ersetzt, die sich in der System.Web.Extensions.dll Assembly befindet. System.Web.Extensions.dll enthält alle Kernfunktionen, die von ASP.NET AJAX verwendet werden.

Auflistung 1: ASP.NET AJAX-Webdiensthandlerkonfiguration

<httpHandlers>
     <remove verb="*" path="*.asmx"/>
     <add verb="*" path="*.asmx" validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory,
          System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,
          PublicKeyToken=31bf3856ad364e35"/>
</httpHandlers>

Diese HttpHandler-Ersetzung erfolgt, damit JSON-Aufrufe (JavaScript Object Notation) von ASP.NET AJAX-Seiten an .NET-Webdienste mithilfe eines JavaScript-Webdienstproxys ausgeführt werden können. ASP.NET AJAX json-Nachrichten an Webdienste im Gegensatz zu den standardmäßigen SOAP-Aufrufen (Simple Object Access Protocol), die normalerweise Webdiensten zugeordnet sind. Dies führt insgesamt zu kleineren Anforderungs- und Antwortnachrichten. Es ermöglicht auch eine effizientere clientseitige Verarbeitung von Daten, da die ASP.NET AJAX JavaScript-Bibliothek für die Arbeit mit JSON-Objekten optimiert ist. Auflistung 2 und Auflistung 3 zeigen Beispiele für Webdienstanforderungs- und -antwortnachrichten, die in das JSON-Format serialisiert wurden. Die in Listing 2 angezeigte Anforderungsnachricht übergibt einen Country-Parameter mit dem Wert "Belgium", während die Antwortnachricht in Listing 3 ein Array von Customer-Objekten und den zugehörigen Eigenschaften übergibt.

Eintrag 2. In JSON serialisierte Webdienstanforderungsnachricht

{"country":"Belgium"}

> [! HINWEIS] der Vorgangsname wird als Teil der URL zum Webdienst definiert. Darüber hinaus werden Anforderungsmeldungen nicht immer über JSON übermittelt. Webdienste können das ScriptMethod-Attribut verwenden, wobei der UseHttpGet-Parameter auf true festgelegt ist, wodurch Parameter über die Abfragezeichenfolgenparameter übergeben werden.

Auflistung 3. In JSON serialisierte Webdienstantwortnachricht

[{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Maison
     Dewey","CustomerID":"MAISD","ContactName":"Catherine
     Dewey"},{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Suprêmes
     délices","CustomerID":"SUPRD","ContactName":"Pascale
     Cartrain"}]

Im nächsten Abschnitt erfahren Sie, wie Sie Webdienste erstellen, die JSON-Anforderungsmeldungen verarbeiten und sowohl mit einfachen als auch mit komplexen Typen reagieren können.

Erstellen AJAX-Enabled Webdienste

Das ASP.NET AJAX-Framework bietet verschiedene Möglichkeiten zum Aufrufen von Webdiensten. Sie können das AutoCompleteExtender-Steuerelement (verfügbar im ASP.NET AJAX Toolkit) oder JavaScript verwenden. Vor dem Aufrufen eines Diensts müssen Sie ihn jedoch AJAX aktivieren, damit er vom Clientskriptcode aufgerufen werden kann.

Unabhängig davon, ob Sie mit ASP.NET Webdiensten noch nicht fertig sind, ist es einfach, Dienste zu erstellen und AJAX zu aktivieren. Das .NET Framework unterstützt die Erstellung von ASP.NET-Webdiensten seit seiner ersten Veröffentlichung im Jahr 2002, und die ASP.NET AJAX-Erweiterungen bieten zusätzliche AJAX-Funktionen, die auf dem Standardfeaturesatz von .NET Framework aufbaut. Visual Studio .NET 2008 Beta 2 verfügt über integrierte Unterstützung zum Erstellen von ASMX-Webdienstdateien und leitet automatisch zugeordneten Code neben Klassen von der System.Web.Services.WebService-Klasse ab. Wenn Sie der Klasse Methoden hinzufügen, müssen Sie das WebMethod-Attribut anwenden, damit sie von Webdienstconsumern aufgerufen werden.

Die Auflistung 4 zeigt ein Beispiel für das Anwenden des WebMethod-Attributs auf eine Methode mit dem Namen GetCustomersByCountry().

Eintrag 4. Verwenden des WebMethod-Attributs in einem Webdienst

[WebMethod]
public Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}

Die GetCustomersByCountry()-Methode akzeptiert einen country-Parameter und gibt ein Customer-Objektarray zurück. Der an die -Methode übergebene Country-Wert wird an eine Geschäftsschichtklasse weitergeleitet, die wiederum eine Datenebenenklasse aufruft, um die Daten aus der Datenbank abzurufen, die Eigenschaften des Customer-Objekts mit Daten zu füllen und das Array zurückzugeben.

Verwenden des ScriptService-Attributs

Das Hinzufügen des WebMethod-Attributs ermöglicht zwar, dass die GetCustomersByCountry()-Methode von Clients aufgerufen wird, die Standard-SOAP-Nachrichten an den Webdienst senden, aber json-Aufrufe von ASP.NET AJAX-Anwendungen können nicht sofort ausgeführt werden. Damit JSON-Aufrufe ausgeführt werden können, müssen Sie das Attribut der ASP.NET AJAX-Erweiterung ScriptService auf die Webdienstklasse anwenden. Dadurch kann ein Webdienst Mithilfe von JSON formatierte Antwortnachrichten senden, und clientseitige Skripts können einen Dienst aufrufen, indem JSON-Nachrichten gesendet werden.

Die Auflistung 5 zeigt ein Beispiel für das Anwenden des ScriptService-Attributs auf eine Webdienstklasse namens CustomersService.

Auflistung 5. Verwenden des ScriptService-Attributs zum AKTIVIEREN eines Webdiensts

[System.Web.Script.Services.ScriptService]
[WebService(Namespace = "http://xmlforasp.net")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class CustomersService : System.Web.Services.WebService
{
     [WebMethod]
     public Customer[] GetCustomersByCountry(string country)
     {
          return Biz.BAL.GetCustomersByCountry(country);
     }
}

Das ScriptService-Attribut fungiert als Marker, der angibt, dass es über AJAX-Skriptcode aufgerufen werden kann. Es verarbeitet keine der JSON-Serialisierungs- oder Deserialisierungstasks, die im Hintergrund auftreten. Die ScriptHandlerFactory (konfiguriert in web.config) und andere verwandte Klassen führen den Großteil der JSON-Verarbeitung durch.

Verwenden des ScriptMethod-Attributs

Das ScriptService-Attribut ist das einzige ASP.NET AJAX-Attribut, das in einem .NET-Webdienst definiert werden muss, damit es von ASP.NET AJAX-Seiten verwendet werden kann. Ein anderes Attribut namens ScriptMethod kann jedoch auch direkt auf Webmethoden in einem Dienst angewendet werden. ScriptMethod definiert drei Eigenschaften, einschließlich UseHttpGet, ResponseFormat und XmlSerializeString. Das Ändern der Werte dieser Eigenschaften kann nützlich sein, wenn der von einer Webmethode akzeptierte Anforderungstyp in GET geändert werden muss, wenn eine Webmethode XML-Rohdaten in Form eines XmlDocument - oder XmlElement -Objekts zurückgeben muss oder wenn von einem Dienst zurückgegebene Daten immer als XML statt als JSON serialisiert werden sollen.

Die UseHttpGet-Eigenschaft kann verwendet werden, wenn eine Webmethode GET-Anforderungen im Gegensatz zu POST-Anforderungen akzeptieren soll. Anforderungen werden mithilfe einer URL gesendet, wobei Die Eingabeparameter der Webmethode in QueryString-Parameter konvertiert werden. Die UseHttpGet-Eigenschaft ist standardmäßig false und sollte nur auf true festgelegt werden, wenn Vorgänge als sicher gelten und wenn keine vertraulichen Daten an einen Webdienst übergeben werden. Die Auflistung 6 zeigt ein Beispiel für die Verwendung des ScriptMethod-Attributs mit der UseHttpGet-Eigenschaft.

Eintrag 6. Verwenden des ScriptMethod-Attributs mit der UseHttpGet-Eigenschaft.

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public string HttpGetEcho(string input)
{
     return input;
}

Ein Beispiel für die Header, die gesendet werden, wenn die in Listing 6 gezeigte HttpGetEcho-Webmethode aufgerufen wird, wird als Nächstes gezeigt:

GET /CustomerViewer/DemoService.asmx/HttpGetEcho?input=%22Input Value%22 HTTP/1.1

Zusätzlich zur Annahme von HTTP GET-Anforderungen durch Webmethoden kann das ScriptMethod-Attribut auch verwendet werden, wenn XML-Antworten von einem Dienst und nicht von JSON zurückgegeben werden müssen. Beispielsweise kann ein Webdienst einen RSS-Feed von einer Remotewebsite abrufen und als XmlDocument- oder XmlElement-Objekt zurückgeben. Die Verarbeitung der XML-Daten kann dann auf dem Client erfolgen.

Die Auflistung 7 zeigt ein Beispiel für die Verwendung der ResponseFormat-Eigenschaft, um anzugeben, dass XML-Daten von einer Webmethode zurückgegeben werden sollen.

Eintrag 7. Verwenden des ScriptMethod-Attributs mit der ResponseFormat-Eigenschaft.

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public XmlElement GetRssFeed(string url)
{
     XmlDocument doc = new XmlDocument();
     doc.Load(url);
     return doc.DocumentElement;
}

Die ResponseFormat-Eigenschaft kann auch zusammen mit der XmlSerializeString-Eigenschaft verwendet werden. Die XmlSerializeString-Eigenschaft hat den Standardwert false. Dies bedeutet, dass alle Rückgabetypen mit Ausnahme von Zeichenfolgen, die von einer Webmethode zurückgegeben werden, als XML serialisiert werden, wenn die ResponseFormat Eigenschaft auf ResponseFormat.Xmlfestgelegt ist. Wenn XmlSerializeString auf truefestgelegt ist, werden alle Typen, die von einer Webmethode zurückgegeben werden, als XML serialisiert, einschließlich Zeichenfolgentypen. Wenn die ResponseFormat-Eigenschaft einen Wert der ResponseFormat.Json XmlSerializeString-Eigenschaft hat, wird ignoriert.

Die Auflistung 8 zeigt ein Beispiel für die Verwendung der XmlSerializeString-Eigenschaft, um zu erzwingen, dass Zeichenfolgen als XML serialisiert werden.

Eintrag 8. Verwenden des ScriptMethod-Attributs mit der XmlSerializeString-Eigenschaft

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml,XmlSerializeString=true)]
public string GetXmlString(string input)
{
     return input;
}

Der Wert, der beim Aufrufen der GetXmlString-Webmethode in Listing 8 zurückgegeben wird, wird als Nächstes angezeigt:

<?xml version="1.0"?>
     <string>Test</string>

Obwohl das JSON-Standardformat die Gesamtgröße von Anforderungs- und Antwortnachrichten minimiert und von ASP.NET AJAX-Clients browserübergreifend genutzt wird, können die Eigenschaften ResponseFormat und XmlSerializeString verwendet werden, wenn Clientanwendungen wie Internet Explorer 5 oder höher erwarten, dass XML-Daten von einer Webmethode zurückgegeben werden.

Arbeiten mit komplexen Typen

In der Auflistung 5 wurde ein Beispiel für die Rückgabe eines komplexen Typs namens Customer aus einem Webdienst gezeigt. Die Customer-Klasse definiert intern mehrere verschiedene einfache Typen als Eigenschaften wie FirstName und LastName. Komplexe Typen, die als Eingabeparameter oder Rückgabetyp für eine AJAX-fähige Webmethode verwendet werden, werden automatisch in JSON serialisiert, bevor sie an die Clientseite gesendet werden. Geschachtelte komplexe Typen (die intern innerhalb eines anderen Typs definiert sind) werden dem Client jedoch nicht standardmäßig als eigenständige Objekte zur Verfügung gestellt.

In Fällen, in denen ein geschachtelter komplexer Typ, der von einem Webdienst verwendet wird, auch auf einer Clientseite verwendet werden muss, kann dem Webdienst das ASP.NET AJAX GenerateScriptType-Attribut hinzugefügt werden. Die in Listing 9 angezeigte CustomerDetails-Klasse enthält beispielsweise die Eigenschaften Address und Gender, die geschachtelte komplexe Typen darstellen.

Eintrag 9. Die hier gezeigte CustomerDetails-Klasse enthält zwei geschachtelte komplexe Typen.

public class CustomerDetails : Customer
{
     public CustomerDetails()
     {
     }
     Address _Address;
     Gender _Gender = Gender.Unknown;
     public Address Address
     {
          get { return _Address; }
          set { _Address = value; }
     }
     public Gender Gender
     {
          get { return _Gender; }
          set { _Gender = value; }
     }
}

Die in der CustomerDetails-Klasse in Listing 9 angezeigten Address- und Gender-Objekte werden nicht automatisch über JavaScript zur Verwendung auf Clientseite zur Verfügung gestellt, da es sich um geschachtelte Typen handelt (Address ist eine Klasse und Gender ist eine Enumeration). In Situationen, in denen ein geschachtelter Typ, der in einem Webdienst verwendet wird, clientseitig verfügbar sein muss, kann das zuvor erwähnte GenerateScriptType-Attribut verwendet werden (siehe Auflistung 10). Dieses Attribut kann mehrmals hinzugefügt werden, wenn verschiedene geschachtelte komplexe Typen von einem Dienst zurückgegeben werden. Sie kann direkt auf die Webdienstklasse oder oberhalb bestimmter Webmethoden angewendet werden.

Eintrag 10. Verwenden des GenerateScriptService-Attributs zum Definieren geschachtelter Typen, die für den Client verfügbar sein sollen.

[System.Web.Script.Services.ScriptService]
[System.Web.Script.Services.GenerateScriptType(typeof(Address))]
[System.Web.Script.Services.GenerateScriptType(typeof(Gender))]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class NestedComplexTypeService : System.Web.Services.WebService
{
     //Web Methods
}

Durch Anwenden des GenerateScriptType Attributs auf den Webdienst werden die Typen Address und Gender automatisch für die Verwendung durch clientseitige ASP.NET AJAX-JavaScript-Code zur Verfügung gestellt. Ein Beispiel für das JavaScript, das automatisch generiert und an den Client gesendet wird, indem das GenerateScriptType-Attribut zu einem Webdienst hinzugefügt wird, finden Sie in Listing 11. Die Verwendung geschachtelter komplexer Typen wird weiter unten in diesem Artikel beschrieben.

Eintrag 11. Geschachtelte komplexe Typen, die einer ASP.NET AJAX-Seite zur Verfügung gestellt werden.

if (typeof(Model.Address) === 'undefined')
{
     Model.Address=gtc("Model.Address");
     Model.Address.registerClass('Model.Address');
}
Model.Gender = function() { throw Error.invalidOperation(); }
Model.Gender.prototype = {Unknown: 0,Male: 1,Female: 2}
Model.Gender.registerEnum('Model.Gender', true);

Nachdem Sie nun erfahren haben, wie Sie Webdienste erstellen und für ASP.NET AJAX-Seiten zugänglich machen, sehen wir uns nun an, wie JavaScript-Proxys erstellt und verwendet werden, damit Daten abgerufen oder an Webdienste gesendet werden können.

Erstellen von JavaScript-Proxys

Beim Aufrufen eines Standardwebdiensts (.NET oder einer anderen Plattform) wird in der Regel ein Proxyobjekt erstellt, das Sie vor der Komplexität des Sendens von SOAP-Anforderungs- und Antwortnachrichten schützt. Mit ASP.NET AJAX-Webdienstaufrufen können JavaScript-Proxys erstellt und verwendet werden, um Dienste einfach aufzurufen, ohne sich Gedanken über die Serialisierung und Deserialisierung von JSON-Nachrichten machen zu müssen. JavaScript-Proxys können automatisch mithilfe des ASP.NET AJAX ScriptManager-Steuerelements generiert werden.

Das Erstellen eines JavaScript-Proxys, der Webdienste aufrufen kann, erfolgt mithilfe der Eigenschaft Services von ScriptManager. Mit dieser Eigenschaft können Sie einen oder mehrere Dienste definieren, die eine ASP.NET AJAX-Seite asynchron aufrufen kann, um Daten zu senden oder zu empfangen, ohne dass Postbackvorgänge erforderlich sind. Sie definieren einen Dienst mithilfe des ASP.NET AJAX-Steuerelements ServiceReference und weisen die Webdienst-URL der -Eigenschaft des Steuerelements Path zu. Listing 12 zeigt ein Beispiel für den Verweis auf einen Dienst namens CustomersService.asmx.

<asp:ScriptManager ID="ScriptManager1" runat="server">
     <Services>
          <asp:ServiceReference Path="~/CustomersService.asmx" />
     </Services>
</asp:ScriptManager>

Eintrag 12. Definieren eines Webdiensts, der in einer ASP.NET AJAX-Seite verwendet wird.

Durch das Hinzufügen eines Verweises auf CustomersService.asmx über das ScriptManager-Steuerelement wird ein JavaScript-Proxy dynamisch generiert und von der Seite referenziert. Der Proxy wird mithilfe des <Skripttags> eingebettet und dynamisch geladen, indem die Datei CustomersService.asmx aufgerufen und /js am Ende angefügt wird. Das folgende Beispiel zeigt, wie der JavaScript-Proxy in die Seite eingebettet wird, wenn das Debuggen in web.config deaktiviert ist:

<script src="CustomersService.asmx/js" type="text/javascript"></script>

>[! HINWEIS] Wenn Sie den tatsächlich generierten JavaScript-Proxycode anzeigen möchten, können Sie die URL für den gewünschten .NET-Webdienst in das Adressfeld des Internet-Explorer eingeben und /js am Ende anfügen.

Wenn das Debuggen in web.config wird eine Debugversion des JavaScript-Proxys in die Seite eingebettet, wie als nächstes gezeigt:

<script src="CustomersService.asmx/jsdebug" type="text/javascript"></script>

Der vom ScriptManager erstellte JavaScript-Proxy kann auch direkt in die Seite eingebettet werden, anstatt mit dem src-Attribut des <Skripttags> darauf zu verweisen. Hierzu können Sie die InlineScript-Eigenschaft des ServiceReference-Steuerelements auf true festlegen (der Standardwert ist false). Dies kann nützlich sein, wenn ein Proxy nicht über mehrere Seiten hinweg freigegeben wird und Wenn Sie die Anzahl der Netzwerkaufrufe an den Server reduzieren möchten. Wenn InlineScript auf true festgelegt ist, wird das Proxyskript nicht vom Browser zwischengespeichert, sodass der Standardwert false in Fällen empfohlen wird, in denen der Proxy von mehreren Seiten in einer ASP.NET AJAX-Anwendung verwendet wird. Als Nächstes wird ein Beispiel für die Verwendung der InlineScript-Eigenschaft gezeigt:

<asp:ServiceReference InlineScript="true" Path="~/CustomersService.asmx"/>

Verwenden von JavaScript-Proxys

Sobald eine ASP.NET AJAX-Seite mithilfe des ScriptManager-Steuerelements auf einen Webdienst verweist, kann ein Aufruf des Webdiensts erfolgen, und die zurückgegebenen Daten können mithilfe von Rückruffunktionen verarbeitet werden. Ein Webdienst wird aufgerufen, indem auf seinen Namespace (sofern vorhanden), den Klassennamen und den Namen der Webmethode verwiesen wird. Alle an den Webdienst übergebenen Parameter können zusammen mit einer Rückruffunktion definiert werden, die die zurückgegebenen Daten verarbeitet.

Ein Beispiel für die Verwendung eines JavaScript-Proxys zum Aufrufen einer Webmethode namens GetCustomersByCountry() finden Sie in Listing 13. Die GetCustomersByCountry()-Funktion wird aufgerufen, wenn ein Endbenutzer auf eine Schaltfläche auf der Seite klickt.

Eintrag 13. Aufrufen eines Webdiensts mit einem JavaScript-Proxy.

function GetCustomerByCountry()
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, OnWSRequestComplete);
}
function OnWSRequestComplete(results)
{
     if (results != null)
     {
          CreateCustomersTable(results);
          GetMap(results);
     }
}

Dieser Aufruf verweist auf den InterfaceTraining-Namespace, die CustomersService-Klasse und die im Dienst definierte GetCustomersByCountry-Webmethode. Sie übergibt einen Länderwert, der aus einem Textfeld abgerufen wurde, sowie eine Rückruffunktion namens OnWSRequestComplete, die aufgerufen werden soll, wenn der asynchrone Webdienstaufruf zurückgegeben wird. OnWSRequestComplete verarbeitet das Array von Customer-Objekten, die vom Dienst zurückgegeben werden, und konvertiert sie in eine Tabelle, die auf der Seite angezeigt wird. Die aus dem Aufruf generierte Ausgabe ist in Abbildung 1 dargestellt.

Binden von Daten, die durch einen asynchronen AJAX-Aufruf an einen Webdienst abgerufen werden.

Abbildung 1: Binden von Daten, die durch einen asynchronen AJAX-Aufruf an einen Webdienst abgerufen werden. (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

JavaScript-Proxys können auch unidirektionale Aufrufe an Webdienste durchführen, wenn eine Webmethode aufgerufen werden soll, der Proxy jedoch nicht auf eine Antwort warten sollte. Beispielsweise können Sie einen Webdienst aufrufen, um einen Prozess wie einen Workflow zu starten, aber nicht auf einen Rückgabewert vom Dienst zu warten. In Fällen, in denen ein unidirektionales Aufrufen an einen Dienst erfolgen muss, kann die in Listing 13 dargestellte Rückruffunktion einfach weggelassen werden. Da keine Rückruffunktion definiert ist, wartet das Proxyobjekt nicht, bis der Webdienst Daten zurückgibt.

Behandeln von Fehlern

Bei asynchronen Rückrufen an Webdienste können verschiedene Arten von Fehlern auftreten, z. B. das Netzwerk ausgefallen ist, der Webdienst nicht verfügbar ist oder eine Ausnahme zurückgegeben wird. Glücklicherweise ermöglichen JavaScript-Proxyobjekte, die vom ScriptManager generiert werden, das Definieren mehrerer Rückrufe zur Behandlung von Fehlern und Fehlern zusätzlich zum zuvor gezeigten erfolgreichen Rückruf. Eine Fehlerrückruffunktion kann unmittelbar nach der Standardrückruffunktion im Aufruf der Webmethode definiert werden, wie in Listing 14 gezeigt.

Eintrag 14. Definieren einer Fehlerrückruffunktion und Anzeigen von Fehlern

function GetCustomersByCountry() 
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, 
          OnWSRequestComplete, OnWSRequestFailed);
}
function OnWSRequestFailed(error)
{
     alert("Stack Trace: " + error.get_stackTrace() + "/r/n" +
          "Error: " + error.get_message() + "/r/n" +
          "Status Code: " + error.get_statusCode() + "/r/n" +
          "Exception Type: " + error.get_exceptionType() + "/r/n" +
          "Timed Out: " + error.get_timedOut());
}

Alle Fehler, die beim Aufrufen des Webdiensts auftreten, lösen den Aufruf der OnWSRequestFailed()-Rückruffunktion aus, die ein Objekt akzeptiert, das den Fehler als Parameter darstellt. Das Fehlerobjekt macht mehrere verschiedene Funktionen verfügbar, um die Ursache des Fehlers sowie das Timeout des Aufrufs zu bestimmen. Die Auflistung 14 zeigt ein Beispiel für die Verwendung der verschiedenen Fehlerfunktionen und Abbildung 2 zeigt ein Beispiel für die von den Funktionen generierte Ausgabe.

Ausgabe, die durch Aufrufen ASP.NET AJAX-Fehlerfunktionen generiert wird.

Abbildung 2: Ausgabe, die durch Aufrufen ASP.NET AJAX-Fehlerfunktionen generiert wird. (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Behandeln von XML-Daten, die von einem Webdienst zurückgegeben werden

Zuvor haben Sie gesehen, wie eine Webmethode xml-Rohdaten mithilfe des ScriptMethod-Attributs zusammen mit der ResponseFormat-Eigenschaft zurückgeben kann. Wenn ResponseFormat auf ResponseFormat.Xml festgelegt ist, werden die vom Webdienst zurückgegebenen Daten als XML und nicht als JSON serialisiert. Dies kann nützlich sein, wenn XML-Daten zur Verarbeitung mit JavaScript oder XSLT direkt an den Client übergeben werden müssen. Derzeit bietet Internet Explorer 5 oder höher aufgrund der integrierten Unterstützung für MSXML das beste clientseitige Objektmodell zum Analysieren und Filtern von XML-Daten.

Das Abrufen von XML-Daten aus einem Webdienst unterscheidet sich nicht vom Abrufen anderer Datentypen. Rufen Sie zunächst den JavaScript-Proxy auf, um die entsprechende Funktion aufzurufen und eine Rückruffunktion zu definieren. Sobald der Aufruf zurückgegeben wird, können Sie die Daten in der Rückruffunktion verarbeiten.

Die Auflistung 15 zeigt ein Beispiel für den Aufruf einer Webmethode mit dem Namen GetRssFeed(), die ein XmlElement-Objekt zurückgibt. GetRssFeed() akzeptiert einen einzelnen Parameter, der die URL für den abzurufenden RSS-Feed darstellt.

Liste 15. Arbeiten mit XML-Daten, die von einem Webdienst zurückgegeben werden.

function GetRss()
{
     InterfaceTraining.DemoService.GetRssFeed(
          "https://blogs.interfacett.com/dan-wahlins-blog/rss.xml",
          OnWSRequestComplete);
}
function OnWSRequestComplete(result)
{
     if (document.all) //Filter for IE DOM since other browsers are limited
     {
          var items = result.selectNodes("//item");
          for (var i=0;i<items.length;i++)
          {
               var title = items[i].selectSingleNode("title").text;
               var href = items[i].selectSingleNode("link").text;
               $get("divOutput").innerHTML +=
               "<a href='" + href + "'>" + title + "</a><br/>";
          }
     }
     else
     {
          $get("divOutput").innerHTML = "RSS only available in IE5+";
     }
}

In diesem Beispiel wird eine URL an einen RSS-Feed übergeben und die zurückgegebenen XML-Daten in der OnWSRequestComplete()-Funktion verarbeitet. OnWSRequestComplete() überprüft zunächst, ob der Browser Internet Explorer, um zu ermitteln, ob der MSXML-Parser verfügbar ist. Wenn dies der Grund ist, wird eine XPath-Anweisung verwendet, um alle <Elementtags> im RSS-Feed zu suchen. Jedes Element wird dann durchlaufen, und die zugeordneten <Titel> - und <Linktags> werden gefunden und verarbeitet, um die Daten jedes Elements anzuzeigen. Abbildung 3 zeigt ein Beispiel für die Ausgabe, die durch einen ASP.NET AJAX-Aufruf über einen JavaScript-Proxy an die GetRssFeed()-Webmethode generiert wird.

Behandeln komplexer Typen

Komplexe Typen, die von einem Webdienst akzeptiert oder zurückgegeben werden, werden automatisch über einen JavaScript-Proxy verfügbar gemacht. Auf geschachtelte komplexe Typen kann jedoch nicht direkt auf clientseitig zugegriffen werden, es sei denn, das GenerateScriptType-Attribut wird wie zuvor beschrieben auf den Dienst angewendet. Warum sollten Sie einen geschachtelten komplexen Typ auf clientseitiger Seite verwenden?

Um diese Frage zu beantworten, gehen Sie davon aus, dass eine ASP.NET AJAX-Seite Kundendaten anzeigt und Endbenutzern ermöglicht, die Adresse eines Kunden zu aktualisieren. Wenn der Webdienst angibt, dass der Address-Typ (ein komplexer Typ, der in einer CustomerDetails-Klasse definiert ist) an den Client gesendet werden kann, kann der Aktualisierungsprozess zur besseren Wiederverwendung von Code in separate Funktionen unterteilt werden.

Ausgabe beim Erstellen eines Webdiensts, der RSS-Daten zurückgibt.

Abbildung 3: Ausgabeerstellung durch Aufrufen eines Webdiensts, der RSS-Daten zurückgibt. (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Die Auflistung 16 zeigt ein Beispiel für clientseitigen Code, der ein in einem Model-Namespace definiertes Address-Objekt aufruft, es mit aktualisierten Daten auffüllt und es der Address-Eigenschaft eines CustomerDetails-Objekts zuweist. Das CustomerDetails-Objekt wird dann zur Verarbeitung an den Webdienst übergeben.

Eintrag 16. Verwenden geschachtelter komplexer Typen

function UpdateAddress()
{
     var cust = new Model.CustomerDetails();
     cust.CustomerID = $get("hidCustomerID").value;
     cust.Address = CreateAddress();
     InterfaceTraining.DemoService.UpdateAddress(cust,OnWSUpdateComplete);
}
function CreateAddress()
{
     var addr = new Model.Address();
     addr.Street = $get("txtStreet").value;
     addr.City = $get("txtCity").value;
     addr.State = $get("txtState").value;
     return addr;
}
function OnWSUpdateComplete(result)
{
     alert("Update " + ((result)?"succeeded":"failed")+ "!");
}

Erstellen und Verwenden von Seitenmethoden

Webdienste bieten eine hervorragende Möglichkeit, wiederverwendebare Dienste für eine Vielzahl von Clients verfügbar zu machen, einschließlich ASP.NET AJAX-Seiten. Es kann jedoch vorkommen, dass eine Seite Daten abrufen muss, die niemals von anderen Seiten verwendet oder freigegeben werden. In diesem Fall kann das Erstellen einer ASMX-Datei, um der Seite den Zugriff auf die Daten zu ermöglichen, wie übertrieben erscheinen, da der Dienst nur von einer einzelnen Seite verwendet wird.

ASP.NET AJAX bietet einen weiteren Mechanismus zum Ausführen von Webdienstaufrufen, ohne eigenständige ASMX-Dateien zu erstellen. Dies erfolgt mithilfe einer Technik, die als "Seitenmethoden" bezeichnet wird. Seitenmethoden sind statische (in VB.NET freigegebene) Methoden, die direkt in eine Seiten- oder Code-Nebendatei eingebettet sind und auf die das WebMethod-Attribut angewendet wird. Durch Anwenden des WebMethod-Attributs können sie mithilfe eines speziellen JavaScript-Objekts namens PageMethods aufgerufen werden, das zur Laufzeit dynamisch erstellt wird. Das PageMethods-Objekt fungiert als Proxy, der Sie vom JSON-Serialisierungs-/Deserialisierungsprozess abschirmt. Beachten Sie, dass Sie für die Verwendung des PageMethods-Objekts die EnablePageMethods-Eigenschaft von ScriptManager auf true festlegen müssen.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
</asp:ScriptManager>

Die Auflistung 17 zeigt ein Beispiel für die Definition von zwei Seitenmethoden in einer ASP.NET Code-Nebenklasse. Diese Methoden rufen Daten aus einer Geschäftsschichtklasse ab, die sich im Ordner App_Code der Website befindet.

Eintrag 17. Definieren von Seitenmethoden.

[WebMethod]
public static Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}
[WebMethod]
public static Customer[] GetCustomersByID(string id)
{
     return Biz.BAL.GetCustomersByID(id);
}

Wenn ScriptManager das Vorhandensein von Webmethoden auf der Seite erkennt, generiert es einen dynamischen Verweis auf das zuvor erwähnte PageMethods-Objekt. Das Aufrufen einer Webmethode erfolgt durch Verweisen auf die PageMethods-Klasse, gefolgt vom Namen der Methode und allen erforderlichen Parameterdaten, die übergeben werden sollen. Die Auflistung 18 zeigt Beispiele für das Aufrufen der beiden zuvor gezeigten Seitenmethoden.

Auflistung 18. Aufrufen von Seitenmethoden mit dem JavaScript-Objekt PageMethods.

function GetCustomerByCountry() 
{
     var country = $get("txtCountry").value;
     PageMethods.GetCustomersByCountry(country, OnWSRequestComplete);
}
function GetCustomerByID() 
{
     var custID = $get("txtCustomerID").value;
     PageMethods.GetCustomersByID(custID, OnWSRequestComplete);
}
function OnWSRequestComplete(results) 
{
     var searchResults = $get("searchResults");
     searchResults.control.set_data(results);
     if (results != null) GetMap(results[0].Country,results);
}

Die Verwendung des PageMethods-Objekts ähnelt der Verwendung eines JavaScript-Proxyobjekts. Sie geben zunächst alle Parameterdaten an, die an die page-Methode übergeben werden sollen, und definieren dann die Rückruffunktion, die aufgerufen werden soll, wenn der asynchrone Aufruf zurückgibt. Ein Fehlerrückruf kann auch angegeben werden (ein Beispiel für die Behandlung von Fehlern finden Sie unter Auflistung 14).

AutoCompleteExtender und ASP.NET AJAX Toolkit

Das ASP.NET AJAX-Toolkit (verfügbar über https://www.devexpress.com/Products/AJAX-Control-Toolkit) bietet mehrere Steuerelemente, die für den Zugriff auf Webdienste verwendet werden können. Insbesondere enthält das Toolkit ein nützliches Steuerelement namens AutoCompleteExtender , das verwendet werden kann, um Webdienste aufzurufen und Daten auf Seiten anzuzeigen, ohne überhaupt JavaScript-Code zu schreiben.

Das AutoCompleteExtender-Steuerelement kann verwendet werden, um vorhandene Funktionen eines Textfelds zu erweitern und Benutzern zu helfen, die gesuchten Daten einfacher zu finden. Während der Eingabe in ein Textfeld kann das Steuerelement verwendet werden, um einen Webdienst abzufragen und zeigt die Ergebnisse unter dem Textfeld dynamisch an. Abbildung 4 zeigt ein Beispiel für die Verwendung des AutoCompleteExtender-Steuerelements zum Anzeigen von Kunden-IDs für eine Supportanwendung. Wenn der Benutzer unterschiedliche Zeichen in das Textfeld eingibt, werden unterschiedliche Elemente basierend auf ihrer Eingabe darunter angezeigt. Benutzer können dann die gewünschte Kunden-ID auswählen.

Die Verwendung von AutoCompleteExtender auf einer ASP.NET AJAX-Seite erfordert, dass die AjaxControlToolkit.dll-Assembly dem Ordner bin der Website hinzugefügt wird. Nachdem die Toolkitassembly hinzugefügt wurde, sollten Sie in web.config darauf verweisen, damit die darin enthaltenen Steuerelemente für alle Seiten in einer Anwendung verfügbar sind. Dazu können Sie das folgende Tag innerhalb des Steuerelementtags> von <web.config hinzufügen:

<add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" tagPrefix="ajaxToolkit"/>

In Fällen, in denen Sie das Steuerelement nur auf einer bestimmten Seite verwenden müssen, können Sie darauf verweisen, indem Sie die Verweisdirektive wie als Nächstes am Anfang einer Seite hinzufügen, anstatt web.config zu aktualisieren:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" 
     TagPrefix="ajaxToolkit" %>

Verwenden des AutoCompleteExtender-Steuerelements.

Abbildung 4: Verwenden des AutoCompleteExtender-Steuerelements. (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Nachdem die Website für die Verwendung des ASP.NET AJAX Toolkits konfiguriert wurde, kann der Seite ein AutoCompleteExtender-Steuerelement hinzugefügt werden, ähnlich wie Sie ein reguläres ASP.NET Serversteuerelement hinzufügen würden. Die Auflistung 19 zeigt ein Beispiel für die Verwendung des -Steuerelements zum Aufrufen eines Webdiensts.

Eintrag 19. Verwenden des ASP.NET AJAX Toolkit AutoCompleteExtender-Steuerelement.

<ajaxToolkit:AutoCompleteExtender ID="extTxtCustomerID" runat="server"
     MinimumPrefixLength="1" ServiceMethod="GetCustomerIDs"
     ServicePath="~/CustomersService.asmx"
     TargetControlID="txtCustomerID" />

Der AutoCompleteExtender verfügt über mehrere verschiedene Eigenschaften, einschließlich der Standard-ID und der runat-Eigenschaften, die auf Serversteuerelementen zu finden sind. Darüber hinaus können Sie definieren, wie viele Zeichen ein Endbenutzer eingibt, bevor der Webdienst nach Daten abgefragt wird. Die MinimumPrefixLength-Eigenschaft, die in Listing 19 angezeigt wird, bewirkt, dass der Dienst jedes Mal aufgerufen wird, wenn ein Zeichen in das Textfeld eingegeben wird. Sie sollten diesen Wert sorgfältig festlegen, da der Webdienst jedes Mal aufgerufen wird, wenn der Benutzer ein Zeichen eingibt, um nach Werten zu suchen, die den Zeichen im Textfeld entsprechen. Der aufzurufende Webdienst sowie die Zielwebmethode werden mit den Eigenschaften ServicePath bzw. ServiceMethod definiert. Schließlich identifiziert die TargetControlID-Eigenschaft, mit welchem Textfeld das AutoCompleteExtender-Steuerelement eingebunden werden soll.

Auf den aufgerufenen Webdienst muss das ScriptService-Attribut angewendet werden, wie weiter oben beschrieben, und die Zielwebmethode muss zwei Parameter namens prefixText und count akzeptieren. Der parameter prefixText stellt die vom Endbenutzer eingegebenen Zeichen dar, und der count-Parameter gibt an, wie viele Elemente zurückgegeben werden sollen (der Standardwert ist 10). Listing 20 zeigt ein Beispiel für die GetCustomerIDs-Webmethode, die vom AutoCompleteExtender-Steuerelement aufgerufen wird, das weiter oben in Listing 19 gezeigt wird. Die Webmethode ruft eine Geschäftsebenenmethode auf, die wiederum eine Datenebenenmethode aufruft, die das Filtern der Daten und die Rückgabe der übereinstimmenden Ergebnisse verarbeitet. Der Code für die Datenebenenmethode wird in Listing 21 angezeigt.

Liste 20. Filtern von Daten, die vom AutoCompleteExtender-Steuerelement gesendet werden.

[WebMethod]
public string[] GetCustomerIDs(string prefixText, int count) 
{
     return Biz.BAL.GetCustomerIDs(prefixText, count);
}

Auflistung 21. Filtern von Ergebnissen basierend auf Endbenutzereingaben.

public static string[] GetCustomerIDs(string prefixText, int count)
{
     //Customer IDs cached in _CustomerIDs field to improve performance
     if (_CustomerIDs == null)
     {
          List<string> ids = new List<string>();
          //SQL text used for simplicity...recommend using sprocs
          string sql = "SELECT CustomerID FROM Customers";
          DbConnection conn = GetDBConnection();
          conn.Open();
          DbCommand cmd = conn.CreateCommand();
          cmd.CommandText = sql;
          DbDataReader reader = cmd.ExecuteReader();
          while (reader.Read())
          {
               ids.Add(reader["CustomerID"].ToString());
          }
          reader.Close();
          conn.Close();
          _CustomerIDs = ids.ToArray();
     }
     int index = Array.BinarySearch(_CustomerIDs, prefixText, new CaseInsensitiveComparer());
     //~ is bitwise complement (reverse each bit)
     if (index < 0) index = ~index;
     int matchingCount;
     for (matchingCount = 0; matchingCount < count && index + matchingCount < _CustomerIDs.Length; matchingCount++)
     {
          if (!_CustomerIDs[index + matchingCount].StartsWith(prefixText, StringComparison.CurrentCultureIgnoreCase))
          {
               break;
          }
     }
     String[] returnValue = new string[matchingCount];
     if (matchingCount > 0)
     {
          Array.Copy(_CustomerIDs, index, returnValue, 0, matchingCount);
     }
     return returnValue;
}

Zusammenfassung

ASP.NET AJAX bietet hervorragende Unterstützung für das Aufrufen von Webdiensten, ohne viel benutzerdefinierten JavaScript-Code zur Verarbeitung der Anforderungs- und Antwortnachrichten zu schreiben. In diesem Artikel haben Sie erfahren, wie Sie .NET-Webdienste AJAX aktivieren, damit sie JSON-Nachrichten verarbeiten können, und wie Sie JavaScript-Proxys mithilfe des ScriptManager-Steuerelements definieren. Sie haben auch gesehen, wie JavaScript-Proxys verwendet werden können, um Webdienste aufzurufen, einfache und komplexe Typen zu behandeln und Fehler zu beheben. Schließlich haben Sie erfahren, wie Seitenmethoden verwendet werden können, um den Prozess zum Erstellen und Ausführen von Webdienstaufrufen zu vereinfachen und wie das AutoVervollständigenExtender-Steuerelement Endbenutzern bei der Eingabe helfen kann. Obwohl das in ASP.NET AJAX verfügbare UpdatePanel aufgrund seiner Einfachheit sicherlich die Kontrolle der Wahl für viele AJAX-Programmierer sein wird, kann es in vielen Anwendungen nützlich sein, Webdienste über JavaScript-Proxys aufzurufen.

Biografie

Dan Wahlin (Microsoft Most Valuable Professional for ASP.NET and XML Web Services) ist .NET Development Instructor und Architekturberater bei Interface Technical Training (http://www.interfacett.com). Dan gründete die Website XML for ASP.NET Developers (www.XMLforASP.NET), ist im INETA Speaker es Bureau und spricht auf mehreren Konferenzen. Dan ist Mitautor von Professional Windows DNA (Wrox), ASP.NET: Tipps, Tutorials und Code (Sams), ASP.NET 1.1 Insider Solutions, Professional ASP.NET 2.0 AJAX (Wrox), ASP.NET 2.0 MVP Hacks und authored XML for ASP.NET Developers (Sams). Wenn er keinen Code, keine Artikel oder Bücher schreibt, schreibt und spielt Dan mit seiner Frau und seinen Kindern Gerne Musik und spielt Golf und Basketball.

Scott Cate arbeitet seit 1997 mit Microsoft-Webtechnologien und ist President von myKB.com (www.myKB.com), wo er sich auf das Schreiben von ASP.NET basierten Anwendungen spezialisiert hat, die sich auf Knowledge Base-Softwarelösungen konzentrieren. Scott kann per E-Mail unter scott.cate@myKB.com oder seinem Blog unter ScottCate.com