André Krämers Blog

Lösungen für Ihre Probleme

Im vierten Teil dieser Serie ASP.NET Webforms Anwendungen und Ajax (Teil 4) Scriptservices, Page Methods und das ASP.NET AJAX Framework habe ich gezeigt, wie Pagemethods und Scriptservices mit dem ASP.NET Ajax Framework angesprochen werden können. Im Vergleich zu den vorherigen Teilen, die auf Client Callbacks ASP.NET Webforms Anwendungen und Ajax (Teil 2) Client Callbacks bzw. das Updatepanel ASP.NET Webforms Anwendungen und Ajax (Teil 3) Das Updatepanel setzen, konnte über diesen Weg die übertragene Datenmenge erheblich verkleinert werden, da unter anderem der Viewstate nicht mehr übertragen werden musste. Außerdem wurde serverseitig nicht mehr der komplette Page Life Cycle durchlaufen, was weitere Performanceverbesserungen mit sich brachte.

Im Gegenzug zu diesen Verbesserungen mussten wir allerdings in Kauf nehmen, dass beim ersten Request drei zusätzliche JavaScript Dateien des Ajax Frameworks mit einer Gesamtgröße von 85 kb geladen wurden.

Hält man im Hinterkopf, dass der Viewstate einer Seite schnell 50 kb und mehr beträgt und dieser bei jedem Ajax Request der vorherigen Methoden hin und her übertragen wurde, lassen sich diese 85 kb jedoch sicherlich leicht verschmerzen.

Doch wie sieht es aus, wenn neben dem Ajax Framework auch jQuery in die Seite eingebunden wurde, um die Oberfläche zu tunen?

In diesem Fall stellt sich die Situation anders dar, nun ist das ASP.NET Ajax Framework clientseitig nämlich reiner Ballast. Zumindest wenn es darum geht Pagemethods und Scriptservcies aufzurufen. Das kann jQuery nämlich auch.

Also weg damit!

Wie müssen wir aber vorgehen, um Pagemethods und Scriptservices aus jQuery rufen zu können?

Zunächst kopieren wir den kompletten Code unseres letzten Beispiels. Wenn wir nun einen Blick auf den Code werfen sehen wir in der *.aspx Datei einen ScriptManager.

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

Dieser war notwendig, damit das ASP.NET Ajax Framework die JavaScript Proxies erstellte, die uns Aufrufe in der Form PageMethods.Methodenname bzw. WebServiceName.Methodenname erlaubten.

Der ScriptManager ist allerdings überflüssig wenn man Pagemethods und Scriptservices via jQuery rufen möchte. Daher fliegt er raus. Weiter entfernen wir den bestehenden inline JavaScript Code der Seite und räumen Sie noch ein wenig auf. Das Ergebnis sieht wie folgt aus:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Teil5.aspx.cs" Inherits="Teil5" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>ASP.NET Webforms Anwendungen und Ajax (Teil 5): Scriptservices mit jQuery</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <h1>
                ASP.NET Webforms Anwendungen und Ajax (Teil 5): Scriptservices mit jQuery</h1>
            <p>
                <a href="#" id="StaticFileLink">Hier klicken zum Request einer statischen Datei</a><br />
                <a href="#" id="HelloWorldLink">Hier für Hello World WebService klicken </a><br />
                <a href="#" id="EchoLink">Hier für Echo WebService klicken. Geben Sie bitte vorher eine
                    Zahl in nebenstehendem Feld ein: </a>&nbsp;
                <input type="text" id="EchoTextBox" value="4711" />
            </p>
            <div id="content">
                Bitte klicken Sie auf einen der Links, damit dieser Bereich gefüllt wird.
            </div>
        </form>
    </body>
</html>

Der serverseitige Code des vorherigen Beispiels wurde übrigens nicht verändert.

Unsere Seite umfasst nun schlanke 1,6 kb, wie die folgende Abbildung zeigt:

01_Seite

Allerdings kann sie auch noch nichts ;-)

Darum werden wir uns aber in den nächsten Schritten kümmern.

jQuery let’s go

Als erstes benötigen wir eine Referenz auf jQuery. Diese erhalten wir über folgenden Script Tag, den wir innerhalb des Head Bereichs der Seite einfügen:

<script src="scripts/jquery-1.3.2.min.js" type="text/javascript"></script>

Als nächstes fügen wir den Code ein, um den Link zur Anzeige der statischen Datei zum fliegen zu bekommen. Dieser sieht wie folgt aus:

<script type ="text/javascript">
$(document).ready(function() {
    $("#StaticFileLink").click(function(e) {
        e.preventDefault();
        $.ajax({
            type: "POST",
            url: "Teil5.aspx/ReadStaticFile",
            data: "{}",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(result) {
                $("#content").html(result.d);
            }
        });
    });
});
</script>

Relevant für uns sind die Zeilen 5 - 14. Da es jedoch vielleicht den ein oder anderen Leser gibt, der noch nie mit jQuery gearbeitet hat, möchte ich auch zu den anderen Zeilen ein paar Worte verlieren.

Auffällig sind zunächst die vielen $-Zeichen innerhalb des Codes. Dabei handelt es sich um einen Alias für die Funktion jQuery. Überall wo ein $ steht, könnte man sich also auch jQuery denken.

Die Funktion jQuery nimmt ein DOM Element bzw. einen CSS Selektor als Argument entgegen. In Zeile 2 wird das DOM Element document, also eine Referenz auf unser eigentliches HTML Dokument übergeben. Anschließend wird an das Ereignis ready eine anonyme Funktion gehangen. Das Ereignis ready tritt übrigens auf, sobald das DOM vollständig initialisiert wurde, jedoch ehe weitere Inhalte wie Beispielsweise Bilder herunter geladen wurden. Somit hat man innerhalb des ready Ereignisses zwar Zugriff auf das vollständig initialisierte DOM, muss aber nicht den kompletten Seitenaufbau abwarten.

In Zeile 3 wird die jQuery Funktion mit dem CSS Selektor #StaticFileLink gefüllt, um das DOM Element mit der ID StaticFileLink, also unseren Link, zu selektieren. An das Ereignis click des Links wird wieder eine anonyme Funktion gehangen.

In Zeile 4 wird über e.preventDefault(); die Standardaktion des Links verhindert. In unserem Fall also, dass beim Klick auf dem Link dem Inhalt des href Attributs gefolgt wird. Stattdessen wird ab der Zeile 5 definiert, dass ein ajax Aufruf gefeuert werden muss.

Dabei werden folgende Werte an die verschiedenen Parameter der Funktion ajax übergeben:

  <th valign="top" width="133">Wert</th>

  <th valign="top">Erklärung</th>
</tr>
  <td valign="top" width="133">POST (fix)</td>

  <td valign="top">Ajax Anfragen an Pagemethods oder Scriptsservices müssen aus Sicherheitsgründen immer vom Typ POST sein, um JSON hijacking Angriffe abzuwehren. Eine sehr gute Beschreibung hierzu gibt es [im Blog von Phil Haack](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx "Beschreibung einer JSON Sicherheitslücke").</td>
</tr>

<tr>
  <td valign="top" width="133">url</td>

  <td valign="top" width="133">{Seitenname.aspx}/{Methodenname}</td>

  <td valign="top">Ziel des Ajax Aufrufs. Im Falle einer Page Method nach dem Schema *{Seitenname.**aspx**}/{Methodenname}*, im Fall von ScriptServices *{Servicename.**asmx**}/{Methodenname}*. Wichtig ist, dass die entsprechende Methode im Falle von Pagemethods statisch ist und mit dem Attribut *WebMethod* dekoriert wurde. Im Falle eines ScriptServices ist auf die Dekorierung des Services mit dem Attribut *ScriptService* zu achten.</td>
</tr>

<tr>
  <td valign="top" width="133">data</td>

  <td valign="top" width="133">JSON String</td>

  <td valign="top">Erwartet die Methode Parameter, sind diese hier als JSON String, und nicht als JSON Objekt zu übergeben! Falls die Methode keine Parameter erwartet, sollte stets ein leeres JSON Objekt übergeben werden (also *"{}"*). Andernfalls übergibt jQuery als contentType statt *application/json* automatisch *text/html*.</td>
</tr>

<tr>
  <td valign="top" width="133">contentType</td>

  <td valign="top" width="133">application/json; charset=utf-8 (fix)</td>

  <td valign="top">Als Content Type ist stets *application/json; charset=utf-8* zu übergeben. Dies ist das Zeichen für das ASP.NET Ajax Framework, serverseitig die Anfrage an den ScriptService Handler und nicht an den Soap Webservice Handler weiterzuleiten.</td>
</tr>

<tr>
  <td valign="top" width="133">dataType</td>

  <td valign="top" width="133">json (fix).</td>

  <td valign="top">Gibt den Datentyp der Antwort an. Gültige Antworten sind unter anderem json, script, xml oder text. Im Fall von Pagemethods und ScriptServices kommt immer JSON zurück.</td>
</tr>

<tr>
  <td valign="top" width="133">succes</td>

  <td valign="top" width="133">Anonyme Funktion bzw. Funktionsname</td>

  <td valign="top">Eine Anonyme Funktion bzw. der Name einer Funktion, die die Rückgabe der Methode verarbeitet.
    Im Falle von PageMethods und ScriptServices wird die Antwort aus Sicherheitsgründen übrigens immer in ein Objekt Namens *d *verpackt (siehe auch Zeile 12 des Scripts). Die Ursache hierfür ist ähnlich wie beim Parameter type und wird auch näher in [Phil Haacks Blog Eintrag](http://haacked.com/archive/2009/06/25/json-hijacking.aspx "JSON Hijacking erklärt") erklärt.</td>
</tr>

Der Aufruf des Services sollte somit ausreichend erklärt sein. Einzig Zeile 12 bedarf noch einer kleinen Erklärung:

Via $("#Content") holt jQuery sich eine Referenz auf den Zielbereich in mithilfe der Funktion html die Antwort des Ajax Requests geschrieben wird.

Der Code zum Aufruf der beiden Webservice Methoden sieht erwartungsgemäß ähnlich aus:

$("#HelloWorldLink").click(function(e) {
    e.preventDefault();
    $.ajax({
        type: "POST",
        url: "AjaxDemoService.asmx/HelloWorld",
        data: "{}",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function(msg) {
            $("#content").html(msg.d);
        }
    });
});

$("#EchoLink").click(function(e) {
    e.preventDefault();
    var number = $("#EchoTextBox").val();
    var jsonData = "{ 'number' : '" + number + "'}";
    $.ajax({
        type: "POST",
        url: "AjaxDemoService.asmx/Echo",
        data: jsonData,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function(msg) {
            $("#content").html(msg.d);
        }
    });
});

Erklärenswert ist einzig Zeile 18. Hier wird ein Json String zusammen gesetzt, der später an die entsprechende Methode des Webservices übergeben wird. Dies geht im Falle eines Arguments noch ganz gut, wird aber spätestens bei zwei oder mehr Argumenten sehr lästig. Abhilfe schafft hier die Methode stringify des Objekts JSON. Die Methode überführt ein Json Objekt in einen entsprechenden String. Moderne Browser bringen dieses Objekt inkl. passender Methode gleich mit. In allen anderen Fällen hilft der JSON Parser / Stringifier Json2.

Das Ergebnis der Mühe sieht im Live-Betrieb dann übrigens wie folgt aus:

02_AjaxRequest

Zum Download der jQuery Library ist, wie auf der Abbildung gezeigt, ein weiterer Request notwendig, der knapp 58 kb umfasst. Dies sind fast 30 kb weniger als es im vorherigen Beispiel auf Basis des ASP.NET AJAX (client) Frameworks der Fall waren.

Fazit

Ajax Aufrufe werden mithilfe des jQuery Frameworks zum Kinderspiel. Gerade wenn man jQuery bereits für Oberflächenmanipulationen in einer Seite nutzt, lohnt sich die Anwendung von jQuery für Ajax Aufrufe anstatt der Nutzung des ASP.NET Ajax (Client) Frameworks.

Es gibt 2 Kommentare

Comment by Rene Drescher-Hackel
Von | 29.10.2011 21:11
Wenn man sich den Aufwand an Javascript ansieht, so erreicht man mit AJAX.NET Professional (AJAX.PRO) ein noch weit beeindruckenderes Ergebnis -> 22,359 Kb werden hier nur benötigt. Ein weiterer Vorteil bei AJAX.PRO ist dann auch die Parameterübergabe - die passende Konvertierung ist hier inkl. - ohne zusätzlichen Aufwand.
Comment by André Krämer
Von | 29.10.2011 21:11
Vielen Dank für den Hinweis René,ich werde mein Beispiel entsprechend erweitern und im übernächsten Teil veröffentlichen.GrußAndré