EINFÜHRUNG
Eine meiner Lieblingsmethoden, um die Benutzerfreundlichkeit von Webanwendungen zu studieren, besteht darin, meiner Frau beim Navigieren auf einer Website zuzusehen. Sie kann sich recht gut im Internet zurechtfinden, weiß aber wenig über die einfachen technischen Aspekte (die sie „die langweiligen Dinge" nennt), die dafür sorgen, dass alles funktioniert.
An einem Abend sah ich meiner Frau zu, wie sie einen E-Commerce-Antrag von einem der großen Jungs durchging. Sie ging in eine Produktliste ein, indem sie mehrere Drop-down-Menüs nutzte, die sich jeweils auf die zuvor getroffene Auswahl stützten. Als sie in jedem Dropdown-Menü auf ein Element klickte, wurde die Seite zurückgesendet, um Daten für das nächste Dropdown-Menü abzurufen. Die Erfahrung war für sie frustrierend, da sie den Eindruck hatte, dass es aufgrund der Postbacks lange dauern würde.
Der Grad der Frustration, den sie empfand, hätte von den Entwicklern der Anwendung leicht gemildert werden können, wenn sie nur XMLHTTP zum Abrufen der Daten verwendet hätten, anstatt sie zurückzusenden. Darum geht es in der Kolumne dieses Monats. Ich zeige Ihnen, wie Sie XMLHTTP verwenden, um einen Teil einer Webseite mit Daten von einem Microsoft ASP.NET-Webdienst zu aktualisieren, ohne einen Postback durchzuführen. Das wird wirklich cool! Vertrau mir.
Allgemeiner Überblick
XMLHTTP funktioniert, indem es vom Client eine Anfrage an den Webserver sendet und eine XML-Dateninsel zurückgibt. Abhängig von der Struktur des empfangenen XML können Sie XSLT oder das XML-DOM verwenden, um es zu manipulieren und Teile der Seite an diese Daten zu binden. Dies ist eine äußerst leistungsstarke Technik.
HinweisMicrosoft bietet ein Webdienstverhalten für Internet Explorer, das asynchrone Aufrufe von ASP.NET-Webdiensten schnell und einfach macht. Dieses Verhalten wird jedoch nicht unterstützt und ist nicht die beste Möglichkeit, eine Seite asynchron zu aktualisieren. Sie sollten stattdessen XMLHTTP verwenden!
In dem Beispiel, das ich in dieser Spalte durcharbeite, werde ich über XMLHTTP drei Webdienstaufrufe an einen ASP.NET-Webdienst durchführen. Der Webdienst fragt die Northwind-Datenbank auf dem lokalen SQL Server ab und gibt ein DataSet in Form eines XML-Diffgramms an den Client zurück. Anschließend verwende ich das XML-DOM, um diese XML-Daten zu analysieren und Teile meiner Seite dynamisch zu aktualisieren. Dies alles erfolgt ohne Rücksendung.
Der Webdienst
Der Webdienst, den ich verwenden werde, heißt DynaProducts. Es handelt sich um einen grundlegenden ASP.NET-Webdienst, der in C# geschrieben ist und die folgenden drei Methoden enthält.
GetCategories – Gibt ein DataSet zurück, das alle Kategorien in der Kategorientabelle enthält.
GetProducts – Gibt ein DataSet zurück, das alle Produkte der Kategorie enthält, die an die Methode übergeben werden.
GetProductDetails – Gibt ein DataSet zurück, das Details zum Produkt enthält, dessen ProductID an die Methode übergeben wird.
Die HTML-Seite
Das erste, was Ihnen an diesem Beispiel auffällt, ist, dass die Seite, die ich über den ASP.NET-Webdienst aktualisiere, keine ASP.NET-Seite ist. Es ist nur eine normale HTML-Seite. Allerdings habe ich der Seite eine ganze Menge clientseitiges JavaScript hinzugefügt, und es ist dieses Skript, das die Aufrufe an den Webdienst durchführt.
Schauen wir uns den ersten Codeausschnitt der HTML-Seite an.
var objHttp;
var objXmlDoc;
function getDataFromWS(methodName, dataSetName, wsParamValue, wsParamName)
{
// create the XML object
objXmlDoc = new ActiveXObject("Msxml2.DOMDocument");
if (objXmlDoc == null)
{
alert("Unable to create DOM document!");
} else {
// create an XmlHttp instance
objHttp = new ActiveXObject("Microsoft.XMLHTTP");
// Create the SOAP Envelope
strEnvelope = "<soap:Envelope xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
" xsd=\"http://www.w3.org/2001/XMLSchema\"" +
" soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
" <soap:Body>" +
" <" + methodName + " xmlns=\"http://jimcoaddins.com/DynaProducts\">" +
" </" + methodName + ">" +
" </soap:Body>" +
"</soap:Envelope>";
// Set up the post
objHttp.onreadystatechange = function(){
// a readyState of 4 means we're ready to use the data returned by XMLHTTP
if (objHttp.readyState == 4)
{
// get the return envelope
var szResponse = objHttp.responseText;
// load the return into an XML data island
objXmlDoc.loadXML(szResponse);
if (objXmlDoc.parseError.errorCode != 0) {
var xmlErr = objXmlDoc.parseError;
alert("You have error " + xmlErr.reason);
} else {
switch(dataSetName)
{
case "CategoriesDS":
processCategory();
break;
case "ProductsDS":
processProducts();
break;
case "ProductDetailDS":
processProductDetails();
break;
}
}
}
}
var szUrl;
szUrl = "http://dadatop/wsXmlHttp/DynaProducts.asmx/" + methodName;
if (wsParamValue != null)
{
szUrl += "?" + wsParamName + "=" + wsParamValue;
}
// send the POST to the Web service
objHttp.open("POST", szUrl, true);
objHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
objHttp.send(strEnvelope);
}
}
Dies ist der umfangreichste Codeabschnitt auf der Seite, und ich möchte ihn im Detail durchgehen, damit Sie verstehen, was vor sich geht.
Oben in diesem Skriptblock habe ich zwei Variablen erstellt: objHttp und objXmlDoc. Dies sind die Variablen, die ich für mein XMLHTTP-Objekt und mein XML-DOM-Objekt verwenden werde. Unmittelbar danach folgt die Funktionsdefinition für die Funktion getDataFromWS. Dies ist die Funktion, die für den clientseitigen Aufruf des Webdienstes verantwortlich ist. Es benötigt die folgenden vier Argumente, von denen zwei optional sind:
methodName – Der Name der Methode, die im Webdienst aufgerufen werden soll.
dataSetName – Der Name des DataSets, der vom Webdienst zurückgegeben wird.
wsParamValue – Der Wert des Parameters, der gegebenenfalls an den Webdienst übergeben wird. (Optional)
wsParamName – Der Name des Parameters, der gegebenenfalls an den Webdienst übergeben wird. (Optional)
Lassen Sie uns die Funktion getDataFromWS in Teile aufteilen und jeden einzelnen besprechen. Hier ist der erste Ausschnitt:
// create the XML object
objXmlDoc = new ActiveXObject("Msxml2.DOMDocument");
if (objXmlDoc == null)
{
alert("Unable to create DOM document!");
} else {
// create an XMLHTTP instance
objHttp = new ActiveXObject("Microsoft.XMLHTTP");
Dieser Codeblock erstellt das XMLHTTP-Objekt und das XML-Dokumentobjekt. Als nächstes beginne ich mit der Erstellung des SOAP-Umschlags.
// Create the SOAP Envelope
strEnvelope = "<soap:Envelope xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
" xsd=\"http://www.w3.org/2001/XMLSchema\"" +
" soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
" <soap:Body>" +
" <" + methodName + " xmlns=\"http://jimcoaddins.com/DynaProducts\">" +
" </" + methodName + ">" +
" </soap:Body>" +
"</soap:Envelope>";
In diesem Code weise ich den SOAP-Umschlag einer String-Variablen zu, damit ich ihn an den Webdienst weitergeben kann. Es ist eigentlich ganz einfach herauszufinden, wie Sie den SOAP-Umschlag für Ihren Webdienst formatieren. Navigieren Sie einfach zum Webdienst und klicken Sie auf eine der Methoden, um einen SOAP-Umschlag für diese Methode anzuzeigen. Folgendes sehe ich beispielsweise, wenn ich zur GetCategories-Methode des wsXMLHTTP-Webdiensts navigiere, den ich für diesen Artikel erstellt habe:
ASP.NET teilt Ihnen mit, wie der SOAP-Umschlag für einen HTTP POST und einen HTTP GET formatiert werden soll. In dem in diesem Artikel vorgestellten Beispiel verwende ich HTTP POST.
So weit, ist es gut. Schauen wir uns nun den nächsten Codeabschnitt an.
// Set up the post
objHttp.onreadystatechange = function(){
// a readyState of 4means we're ready to use thedata returned byXMLHTTP
if (objHttp.readyState == 4)
{
// getthe return envelope
varszResponse= objHttp.responseText;
// loadthe return into an XML data island
objXmlDoc.loadXML(szResponse);
if (objXmlDoc.parseError.errorCode != 0) {
var xmlErr =objXmlDoc.parseError;
alert("You have error " + xmlErr.reason);
}
else
{
switch(dataSetName)
{
case "CategoriesDS":
processCategory();
break;
case "ProductsDS":
processProducts();
break;
case "ProductDetailDS":
processProductDetails();
break;
}
}
Wenn eine Anfrage über XMLHTTP gestellt wird, verwendet das XMLHTTP-Objekt eine readyState-Eigenschaft, um den Status der Anfrage zu verfolgen. Wenn alle Daten vom Webdienst zurückerhalten wurden, ändert sich die Eigenschaft „readyState" in den Wert 4. Mit der Eigenschaft „onreadystatechange" für das XMLHTTP-Objekt können Sie eine Rückruffunktion einrichten, die aufgerufen wird, wenn sich die Eigenschaft „readyState" ändert. Indem ich sicherstelle, dass die Daten vollständig empfangen wurden, kann ich keine Maßnahmen auf der Grundlage dieser Daten ergreifen, bis ich dazu bereit bin.
Sobald alle Daten empfangen wurden, erstelle ich mithilfe der Eigenschaft „responseText" eine XML-Dateninsel mit der Antwort. Wie Sie wahrscheinlich wissen, erfolgt die Antwort eines Webdienstes im XML-Format. In diesem Fall gebe ich ein Microsoft ADO.NET DataSet zurück.
Der nächste Abschnitt dieses Codeblocks verwendet eine Switch-Anweisung, um die entsprechende Funktion basierend auf dem Namen des DataSet aufzurufen, der vom Webdienst zurückgegeben wird. Auf den Code für diese Funktionen werde ich etwas später im Detail eingehen.
Schauen wir uns nun den Code an, der die XMLHTTP-Anfrage tatsächlich ausführt.
var szUrl;
szUrl = "http://dadatop/wsXmlHttp/DynaProducts.asmx/" + methodName;
if (wsParamValue != null)
{
szUrl += "?" + wsParamName + "=" + wsParamValue;
}
// send the POST to the Web service
objHttp.open("POST", szUrl, true);
objHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
objHttp.send(strEnvelope);
Die Variable szUrl enthält der Übersichtlichkeit halber die URL, über die der Webservice aufgerufen wird. Ich habe dann eine if-Anweisung, die alle Parameter berücksichtigt, die als QueryString-Wert übergeben werden. In Ihrer Umgebung möchten Sie die Parameter möglicherweise zum SOAP-Umschlag hinzufügen. So oder so wird es gut funktionieren.
Als nächstes wird die open-Methode des XMLHTTP-Objekts aufgerufen. Ich habe die ersten drei Argumente für die open-Methode verwendet; die Methode, die URL und einen booleschen Wert, der angibt, ob der Aufruf asynchron ist oder nicht.
Wichtig Wenn Sie wie ich hier einen asynchronen Aufruf tätigen, müssen Sie über die Eigenschaft onreadystatechanged eine Rückruffunktion einrichten.
Nachdem der Anforderungsheader für den Inhaltstyp festgelegt wurde, sende ich die Anforderung als SOAP-Umschlag unter Verwendung der zuvor ausgefüllten Zeichenfolgenvariablen.
Wir haben jetzt den gesamten Code durchgesehen, der die XMLHTTP-Anfrage stellt. Schauen wir uns nun den Code an, der die Schnittstelle im Browser und die Antwort des Webdienstaufrufs verwaltet.
Zuerst schauen wir uns die Funktion an, die aufgerufen wird, wenn die Seite zum ersten Mal geladen wird.
function getCategories()
{
var func = "getDataFromWS('GetCategories', 'CategoriesDS')";
document.all.lblCategoryDropdown.innerText =
"Please wait while data is retrieved...";
window.setTimeout(func, 1);
}
Das erste, was ich in dieser Funktion mache, ist, eine Variable zu erstellen, um die Funktionssignatur für getDataFromWS zu speichern. Ich mache das, weil ich window.setTimeout am Ende dieser Funktion aufrufen werde, um die Funktion getDataFromWS aufzurufen. Der Zweck dieses Ansatzes besteht darin, dass ich dem Benutzer den Status anzeigen kann, während ich auf den Abschluss des Webdienstaufrufs warte. Beachten Sie, dass ich den innerText eines DIV ändere, um eine Meldung anzuzeigen, die angibt, dass Daten abgerufen werden. Anschließend plane ich die Funktion „getDataFromWS" über den Aufruf „window.setTimeout" und stelle sie so ein, dass sie in einer Millisekunde ausgeführt wird.
Verarbeiten der Webdienst-Antwort
Denken Sie daran, dass ich vorhin die Eigenschaft onreadystatechanged verwendet habe, um eine Rückruffunktion zu konfigurieren. Denken Sie auch daran, dass die Callback-Funktion eine Switch-Anweisung enthält, die eine bestimmte Funktion basierend auf dem DataSet-Namen aufruft. In diesem Fall lautet unser DataSet-Name CategoryDS. Daher wird die Funktion „processCategory" von der Rückruffunktion aufgerufen. Schauen wir uns diese Funktion an, um zu sehen, wie das XML-DOM zum Parsen der Antwort vom Webdienst verwendet wird.
function processCategory()
{
// get an XML data island with the category data
objNodeList = objXmlDoc.getElementsByTagName("Categories");
// add default value to the drop-down
document.forms[0].drpCategory.options[0] = new Option("Select a Category", 0);
// walk through the nodeList and populate the drop-down
for (var i = 0; i < objNodeList.length; i++)
{
var dataNodeList;
var textNode;
var valueNode;
dataNodeList = objNodeList[i].childNodes;
valueNode = dataNodeList.item(0);
textNode = dataNodeList.item(1);
document.forms[0].drpCategory.options[i + 1] =
new Option(textNode.text, valueNode.text);
document.all.lblCategoryDropdown.innerText = "Select a Category:";
document.forms[0].drpCategory.style.visibility = "visible";
}
}
Denken Sie daran, dass die Funktion getDataFromWS XML aus der Antwort in das objXmlDoc-Objekt geladen hat. In der Funktion „processCategory" nehme ich dieses XML und analysiere es, um das Dropdown-Menü „Kategorie" zu füllen.
Als Erstes erstelle ich ein IXMLDOMNodeList-Objekt unter Verwendung eines Teils der XML-Antwort. Das DataSet, das ich vom Webdienstaufruf zurückgebe, wird als Diffgram zurückgegeben, und der einzige Teil dieser Antwort, der mich wirklich interessiert, sind die Daten aus der DataTable, die ich in das DataSet eingefügt habe. Ich kann das erreichen, indem ich ein IXMLDOMNodeList-Objekt aus dem XML-Block erstelle, der die DataTable enthält.
Wenn Sie sich den Code für den Webdienst ansehen, werden Sie feststellen, dass ich eine Datentabelle mit dem Namen „Kategorien" erstelle und sie dem DataSet hinzufüge. Wenn das XML vom Webdienst zurückgegeben wird, ist das DataSet in einem <CategoriesDS>-Block enthalten, und jede Zeile aus der DataTable ist in separaten <Categories>-Blöcken enthalten, wie in der XML-Datei unten gezeigt.
Die folgenden Dateien stehen im Microsoft Download Center zum Download zur Verfügung:
Laden Sie jetzt das Paket GetCategories.xml herunter.
Laden Sie jetzt das Paket WSXMLHTTP.exe herunter. Für weitere Informationen zum Herunterladen von Microsoft-Supportdateien klicken Sie auf die folgende Artikelnummer, um den Artikel in der Microsoft Knowledge Base anzuzeigen:
119591 So erhalten Sie Microsoft-Supportdateien von Onlinediensten
Microsoft hat diese Datei auf Viren überprüft. Microsoft verwendete die aktuellste Virenerkennungssoftware, die zum Zeitpunkt der Veröffentlichung der Datei verfügbar war. Die Datei wird auf Servern mit erhöhter Sicherheit gespeichert, die dazu beitragen, unbefugte Änderungen an der Datei zu verhindern.
Um den XML-Block zu erhalten, der diese Datentabelle enthält, verwende ich den folgenden Code:
objNodeList = objXmlDoc.getElementsByTagName("Categories");
Dies gibt ein IXMLDOMNodeList-Objekt zurück, das jeden <Categories>-Knoten enthält. Dann iteriere ich diese Liste mithilfe einer for-Schleife.
// walk through the nodeList and populate the drop-down
for (var i = 0; i < objNodeList.length; i++)
{
var dataNodeList;
var textNode;
var valueNode;
dataNodeList = objNodeList[i].childNodes;
valueNode = dataNodeList.item(0);
textNode = dataNodeList.item(1);
document.forms[0].drpCategory.options[i + 1] =
new Option(textNode.text, valueNode.text);
document.all.lblCategoryDropdown.innerText = "Select a Category:";
document.forms[0].drpCategory.style.visibility = "visible";
}
Ich weiß bereits, dass jeder <Categories>-Knoten zwei Knoten haben wird, die ich benötige: den <ID>-Knoten und den <CategoryName>-Knoten. Daher erstelle ich zunächst eine neue IXMLDOMNodeList und fülle sie mit den untergeordneten Knoten des aktuellen <Categories>-Knotens.
dataNodeList = objNodeList[i].childNodes;
Anschließend verwende ich die Item-Methode, um auf beide Knoten zuzugreifen, die ich zum Füllen meines Dropdown-Menüs benötige. Der erste Knoten enthält das Feld „CategoryID" aus der Datenbank und der zweite Knoten enthält das Feld „CategoryName" aus der Datenbank. Ich erstelle ein neues Option-Objekt, setze den Text auf den CategoryName, setze den Wert auf die CategoryID und füge ihn zum drpCategory-Dropdown hinzu. Der Code, der in den übrigen Funktionen verwendet wird, verwendet dieselbe Methode, um die benötigten Daten aus der XML-Antwort abzurufen und Teile der Seite zu füllen.
HinweisDa es sich hier um kleine Datenmengen handelt, ist die Verwendung des DOM eine hervorragende Möglichkeit, die benötigten Daten abzurufen. Wenn Sie mit einer großen Datenmenge zu tun haben, können Sie stattdessen XSLT verwenden.
Wie man alles zum Laufen bringt
Nachdem ich nun die wichtigsten Details zur Funktionsweise des Ganzen besprochen habe, ist es an der Zeit, zu besprechen, wie Sie die mitgelieferten Beispieldateien verwenden können, um selbst zu sehen, wie es funktioniert.
Bereitstellen des Webdienstes
Um den ASP.NET-Webdienst bereitzustellen, entpacken Sie einfach das angehängte Webdienst-Beispiel in das Stammverzeichnis Ihres Webservers. Anschließend müssen Sie den Code für DynaProducts.asmx öffnen und die Verbindungszeichenfolge ändern. Zumindest müssen Sie das SA-Passwort eingeben. Nachdem Sie diese Änderung vorgenommen haben, kompilieren Sie den Webdienst neu.
Bereitstellen der HTML-Datei
Die HTML-Datei enthält eine Variable namens szUrl, die eine URL zum Webdienst enthält. Sie finden diese Variable in der Funktion getDataFromWS am unteren Rand der Funktion. Sie müssen dies in die URL für den Webdienst ändern, den Sie oben bereitgestellt haben.
Nachdem Sie sowohl den Webdienst als auch die HTML-Datei bereitgestellt haben, navigieren Sie zur HTML-Datei. Beim Laden wird das Dropdown-Menü „Kategorie" mit der ersten XMLHTTP-Anfrage an den Webdienst gefüllt. Sobald diese ausgefüllt ist, wählen Sie eine Kategorie aus, um die nächste XMLHTTP-Anfrage zu starten, die das Dropdown-Menü „Produkte" ausfüllt. Wenn Sie ein Produkt aus der Dropdown-Liste „Produkte" auswählen, wird eine Tabelle mit Daten zu diesem Produkt gefüllt.
Beachten Sie, dass die Seite bei keiner dieser XMLHTTP-Anfragen zurückgesendet wird. Das ist das Schöne an XMLHTTP-Anfragen. Wenn ich dies auf einer großen Seite getan hätte, hätte die Seite auch ihre Scroll-Position beibehalten, ohne den Benutzer anzublinzeln. Wenn Sie mich fragen, ist das ein ziemlich mächtiges Zeug!
Noch etwas: In diesem Artikel habe ich XMLHTTP verwendet, um einen Webdienst abzufragen. Ich hätte es genauso gut verwenden können, um eine Anfrage für eine ASPX-Seite oder eine ASP-Seite zu stellen. Die Möglichkeiten, wie Sie diese Technologie nutzen können, sind endlos. Ich hoffe, dass Sie XMLHTTP bei der Entwicklung Ihrer zukünftigen Webanwendungen nützlich finden.
Wie immer können Sie gerne Ideen zu Themen einreichen, die Sie in zukünftigen Kolumnen oder in der Microsoft Knowledge Base behandeln möchten, indem Sie das Formular „Ask For It" verwenden.