In Teil 5 meiner ASP.NET Webforms und Ajax Serie wies René Drescher-Hackel mich darauf hin, dass ich einen Big Player vollkommen außer acht gelassen habe: AjaxPro von Michael Schwarz.

Das wollte ich natürlich nicht auf mir sitzen lassen. Daher befassen wir uns heute mit:

AjaxPro :-)

AjaxPro ist eine freie Library von Michael Schwarz, die Ajax unter ASP.NET ohne Postbacks ermöglicht. Vergleichbar ist AjaxPro somit am ehesten mit den in Teil 4 vorgestellten PageMethods. Ein großer Vorteil gegenüber diesen ist jedoch die Möglichkeit, AjaxPro auch unter ASP.NET 1.1 einsetzen zu können (ja - ich kenne wirklich noch aktive ASP.NET 1.1 Projekte ;-)). Angemerkt werden sollte noch, dass AjaxPro keiner Weiterentwicklung mehr unterliegt. Michael Schwarz schrieb in in seinem Blog, dass er die Arbeit an der Library einstellt. Als Alternative empfiehlt er den Einsatz von PageMethods.

Lost gehts

AjaxPro ist relativ einfach eingebunden. Es genügt einen Verweis auf die Datei AjaxPro.2.dll hinzuzufügen.

01addreference

Weiter muss AjaxPro in der Datei web.config des Webprojekts bekannt gemacht werden. Dies geschieht über das Hinzufügen eines entsprechenden Http-Handlers:

<httpHandlers>
   ...
   <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro.2"/>
</httpHandlers>

Anschließend müssen die Methoden einer Seite, die später via Ajax aufgerufen werden sollen, mit dem Attribut AjaxMethod annotiert werden. Dies funktioniert also ähnlich wie die bereits vorgestellten PageMethods. Der Quellcode dieses Beitrags basiert übrigens wieder auf dem Quellcode der vorherigen Teile dieser Serie.

public partial class Teil7 : Page
{
    [AjaxPro.AjaxMethod]
    public string ReadStaticFile()
    {
        string fileContent;
        using (var reader = 
            new StreamReader(
                System.Web.HttpContext.Current.Server.MapPath("~/static.html")))
        {
            fileContent = reader.ReadToEnd();
        }
        return fileContent;
    }
}

Ein netter Nebeneffekt im Vergleich zu Pagemethods ist übrigens, dass die entsprechende Methode zwar statisch sein kann, dies aber nicht muss. Außerdem muss sich die Methode noch nicht einmal zwingend innerhalb einer Webseite (ASPX-Datei) befinden. Stattdessen kann Sie Mitglied einer beliebigen öffentlichen Klasse sein.

Um diese Methode nun komfortabel per JavaScript aufrufen zu können, muss die zugehörige Klasse noch als AjaxTyp registriert werden. Dies geschieht in der Methode Page_Load der Klasse Teil7.

protected void Page_Load(object sender, System.EventArgs e)
{
    AjaxPro.Utility.RegisterTypeForAjax(typeof(Teil7));
}

Der Aufruf innerhalb der Seite kann nun wie folgt erfolgen:

<a href="#" id="StaticFileLink" onclick="Teil7.ReadStaticFile(ajaxdemo_callback)">Hier klicken zum Request einer statischen Datei</a><br />

Bei dem übergebenen Parametger ajaxdemo_callback handelt es sich um eine Callback Funktion, die die Antwort des Servers verarbeitet. In unserem Beispiel sieht sie wie folgt aus:

<script type="text/javascript">
    function ajaxdemo_callback(result) {
       var content = document.getElementById('content');
       content.innerHTML = result.value;
    }
</script>

Die Seite ist nun funktionsfähig und kann gestartet werden. Ein Blick in die Firebug Ausgabe zeigt, dass neben der eigentlichen Seite automatisch noch 4 JavaScript Dateien geladen wurden. Diese sind für die Serverkommunikation zuständig und haben eine Gesamtgröße von nur 25 kb! Im Vergleich zu den zuvor vorgestellten Lösungen ist dies das beste Ergebnis!

02_initialesladen

Auch beim Aufruf der serverseitigen Methode schlägt sich AjaxPro gut. Hier gehen lediglich 111 Byte über die Leitung:

03_ajaxcall

Der Aufruf selbst hat eine Datei mit der Endung *.ashx innerhalb des Pfads /ajaxpro zum Ziel. In meinem Beispiel lautet die URL: http://localhost:2800/AjaxDemo/ajaxpro/Teil7,App_Web_mke8f-yp.ashx. Er wird somit durch den zuvor in der web.config eingetragenen Http-Handler behandelt. Außerdem ist innerhalb der URL, wie man sehen kann, der Name der Klasse in der sich die entsprechende Methode befindet kodiert. Die Methode selbst wird im Header des Aufrufs als Attribut X-AjaxPro-Method übergeben.

Webservices

Nachdem sich PageMethods relativ einfach aufrufen ließen, stellt sich nun natürlich die Frage, wie die Methoden eines Webservices aufgerufen werden können.

Eine Variante wäre es, die Methoden innerhalb des Webservices nicht nur mit dem Attribut [WebMethod] zu anotieren, sondern zusätzlich auch mit [AjaxPro.AjaxMethod] zu versehen. Diese Variante ist selbstverständlich nur für eigene und nicht für externe Webservices möglich.

Ein allgemeingültiger Ansatz besteht darin, händisch Proxy-Methoden innerhalb der Seite für die gewünschten Webservice Methoden anzulegen.

[AjaxPro.AjaxMethod]
public string CallHelloWorldService()
{
    return new AjaxDemoService().HelloWorld();
}

[AjaxPro.AjaxMethod]
public string CallEchoService(int value)
{

    return new AjaxDemoService().Echo(value);
}

Auf dem Client birgt der zusätzliche Code keine Überraschungen. Der Aufruf der serverseitigen Methoden erfolgt analog des ersten Beispiels. Die einzige Besonderheit ist der Aufruf der Methode CallEchoService, da diese einen Parameter erwartet.

<script type="text/javascript">
    function callEchoService() {
        var echoTextBox = document.getElementById('EchoTextBox');
        var value = echoTextBox.value;
        Teil7.CallEchoService(value, ajaxdemo_callback);
    }

    function ajaxdemo_callback(result) {
        var content = document.getElementById('content');
        content.innerHTML = result.value;
    }
</script>

Wie der Beispielcode zeigt, wird der erwartete Parameter einfach vor die Angabe der JavaScript Callback Funktion gepackt.

Fazit

AjaxPro ist eine kleine und leichtgewichtige Ajax Library, die ohne unnötigen Ballast daher kommt. Durch die Ähnlichkeit zu Pagemethods und Scriptservices fiel mir die Einarbeitung ziemlich leicht und nahm kaum mehr als eine halbe Stunde in Anspruch.

Trotz dieser Vorteile kann ich mir nicht vorstellen, AjaxPro als Standardwerkzeug in aktuellen Projekten zu nutzen. Stattdessen werde ich wohl weiterhin auf jQuery setzen. Der Grund für diese Entscheidung liegt ganz einfach darin, dass ich jQuery zu UI Manipulationszwecken meist sowieso schon auf meinen Seiten referenziert habe. Daher ist es naheliegend für AjaxRequests auch zu jQuery zu greifen.

Sehr gut vorstellen könnte ich mir jedoch, AjaxPro in ASP.NET 1.1 Projekten zu nutzen, die sich noch in Pflege befinden. In diesen fehlt die Möglichkeit, PageMethods und Scriptservices einzusetzen, so das AjaxPro dort das perfekte Werkzeug wäre.

An dieser Stelle auch noch einmal vielen Dank an René für den hilfreichen Tipp.


Kick it on dotnet-kicks.de
 
3/29/2010 - 10:12 PM | Comments [0] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

Wie Stephen Walter in seinem Blog angekündigt hat, stoppt Microsoft die Entwicklung des eigenen clientseitigen ASP.NET Ajax Library und steuert stattdessen Code zu jQuery bei. Bedeutet Microsoft hat jQuery nicht "übernommen", sondern steuert wie jeder andere einfach nur Quellcode bei bzw. schlägt Features vor, die anschließend durch das jQuery Team geprüft werden.

Da die Beta der ASP.NET Ajax Library nun bereits seit November verfügbar war, überraschte mich diese Ankündigung ein wenig. Andererseits fragte ich mich, je tiefer ich mich mit der Library beschäftigte, sowieso weshalb man den clientseitigen Teil neben jQuery überhaupt brauchen würde.

Jedem der sich nun genötigt fühlt, jQuery zu lernen, dem kann ich nur wärmstens Dave Wards Blog empfehlen. Außerdem ist seine Tekpub jQuery Serie die er gemeinsam mit James Avery aufzeichnet natürlich auch Pflichtprogramm.


Kick it on dotnet-kicks.de
 
3/17/2010 - 10:41 AM | Comments [0] | Categories: .NET | Ajax | DotNetGerman Bloggers | jQuery
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

In den letzten 5 Einträgen meines Blogs habe ich über verschiedene Möglichkeiten geschrieben Ajax in einer ASP.NET Webforms zu implementieren.

Angefangen mit dem manuellen Weg über das XmlHttpRequest Objekt ging es weiter zu Client Callbacks, dem Updatepanel, dem ASP.NET Ajax Framework sowie jQuery.

Der Fokus meiner Beiträge lag darauf nicht nur einfach zu zeigen welche Möglichkeiten es gibt, sondern zusätzlich auch zu zeigen, welche Datenmengen über die Leitung gehen und ob bzw. welche Teile des ASP.NET Page Life Cycles durchlaufen werden.

Ich hoffe dass ich bei dem ein oder anderen Leser für manchen Aha Effekt sorgen konnte. Zumindest ging es mir persönlich bei der ersten detaillierten Auseinandersetzung mit dem Thema so. Schließlich ist man als ASP.NET Webforms Entwickler traditionell doch eher auf dem Server zu Hause und realisiert vorerst garnicht welchen Overhead Client Callbacks oder das Updatepanel mit sich bringen.

Mein persönliches Fazit ist, dass Updatepanel und Client Callbacks korrekt eingesetzt in einigen Fällen vielleicht berechtigte Alternativen sind, in den meisten Fällen jedoch zum Ajax Framework oder jQuery gegriffen werden sollte.

Meine zurzeit favorisierte Lösung ist - ähnlich wie bei vielen anderen sicherlich auch - der Einsatz von jQuery.

Da mein letztes Beispiel noch ein wenig aufgebläht war, möchte ich an dieser Stelle zwei kurze Tipps geben, die die Arbeit mit jQuery und Ajax ein wenig erleichtern.

Der erste Punkt beschäftigt sich mit der großen Redundanz zwischen den verschiedenen Ajax Aufrufen:

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

$("#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);
        }
    });
});

Bereits beim ersten Blick auf den Quellcode fällt auf, dass die Werte einiger Parameter statisch zu sein scheinen. So haben folgende Parameter stets einen fixen Wert:

  • type
  • contentType
  • dataType

Außerdem ist zumindest in den ersten beiden Aufrufen auch der Eintrag für data gleich.

Ein Weg um diese Redundanz herum zu kommen wäre eine eigene Funktion, die nur die Variablen Parameter entgegen nimmt.

Standardwerte setzen

Eine andere Alternative besteht darin, Standardwerte für jQuery Ajax Aufrufe zu setzen. Dies sähe dann so aus:

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

        $("#HelloWorldLink").click(function(e) {
            e.preventDefault();
            $.ajax({
                url: "AjaxDemoService.asmx/HelloWorld",
                success: function(msg) {
                    $("#content").html(msg.d);
                }
            });
        });

        $("#EchoLink").click(function(e) {
            e.preventDefault();
            var number = $("#EchoTextBox").val();
            var jsonData = "{ 'number' : '" + number + "'}";
            $.ajax({
                url: "AjaxDemoService.asmx/Echo",
                data: jsonData,
                success: function(msg) {
                    $("#content").html(msg.d);
                }
            });
        });
    });
</script>

Neu hinzugekommen sind die Zeilen 2 - 8. Diese setzen Standardwerte für alle folgenden Ajax Aufrufe. Diese Variante kann einiges an Code sparen, ist allerdings mit Vorsicht zu genießen. Sollte nämlich zum Beispiel ein jQuery Plug-In auf der Seite genutzt werden, dass auch die $.ajax Funktion nutzt, könnte es zu Seiteneffekten kommen. Konkret wäre dies der Fall, wenn einer der per .ajaxSetup gesetzten Parameter nicht überschrieben, aber mit einem anderen Wert erwartet wäre. Typischerweise würde dies für den contentType oder oder dataType geschehen.

Serialisieren - einfach gemacht

Eine weitere Unschönheit des gezeigten Quellcodes besteht darin, dass die String Variante des in JSON notierten Objekts data von Hand zusammen gebaut wurde. Dies ist natürlich nicht sonderlich schick. Abhilfe schafft die Funktion stringify des Objekts JSON. Einige Browser wie Firefox ab Version 3.5 oder IE ab der Version 8 haben bereits ein eingebautes Objekt JSON. Für alle anderen gibt es unter http://www.json.org/js.html eine JavaScript Library zum Download, die entsprechenden Support nachrüstet, falls noch nicht vorhanden.

Konkret sähe dies dann wie folgt aus:

<script src="scripts/json2.js" type="text/javascript"></script>

[ ... ]


$("#EchoLink").click(function(e) {
    e.preventDefault();
    var number = $("#EchoTextBox").val();
    var jsonData = { 'number': number };
    var jsonString = JSON.stringify(jsonData);
    $.ajax({
        url: "AjaxDemoService.asmx/Echo",
        data: jsonString,
        success: function(msg) {
            $("#content").html(msg.d);
        }
    });

Wie man sieht, wird nun in Zeile 9 zuerst ein JavaScript Objekt jsonData in JSON Notation erzeugt. Dies wird anschließend über JSON.stringify in Zeile 10 in einen String konvertiert. Bei diesem konkreten Beispiel mag der Vorteil noch nicht auf der Hand liegen, spätestens bei komplexen Objekten lernt man die Funktion stringify jedoch schnell zu schätzen.

Bei der Recherche zu den Beiträgen dieser Serie bin ich übrigens auf einen sehr gut geschriebenen Eintrag von Roberto Bez gestolpert. Roberto stellt in seinem Blog Post die verschiedenen Varianten kompakt gegenüber. Die Lektüre des Artikels kann ich jedem Ajax interessierten Webforms Entwickler wärmstens empfehlen.

Weiter hat René Drescher-Hackel in einem Kommentar zu meinem letzen Beitrag, dass mit Ajax.NET Professional (AJAX.PRO) eine weitere effiziente Alternative zur Verfügung steht. Ich muss gestehen, dass mir die Existenz dieses Frameworks bis jetzt vollkommen entgangen ist. Gelobe allerdings Besserung und werde es mir gerne ansehen und mein Beispiel damit umsetzen.


Kick it on dotnet-kicks.de
 
3/11/2010 - 09:44 PM | Comments [0] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

Im vierten Teil dieser Serie 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 bzw. 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:

Parameter Wert Erklärung
type POST (fix) 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.
url {Seitenname.aspx}/{Methodenname} 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.
data JSON String 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.
contentType application/json; charset=utf-8 (fix) 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.
dataType json (fix). 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.
succes Anonyme Funktion bzw. Funktionsname 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 erklärt.

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.


Kick it on dotnet-kicks.de
 
3/1/2010 - 11:33 PM | Comments [2] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

Nachdem ich in Teil 1 der Serie den manuellen Ansatz zur AJAX Implementierung gezeigt habe und in den Teilen 2 und 3 auf (halb-)automatische Alternativen einging, möchte ich dieses Mal einen anderen Weg zeigen, ASP.NET Scriptservices in Verbindung mit ASP.NET AJAX.

Rekapitulieren wir noch einmal:

Teil 1 der Serie zeigte einen manuellen Ansatz, der hauptsächlich damit kämpfte, dass man innerhalb des JavaScript Codes selbst darauf achten musste, die verschiedenen Besonderheiten der unterschiedlichen Browser zu berücksichtigen. Dies geschah in den Teilen 2 und 3 zwar automatisch hinter den Kulissen, dafür hatten wir bei diesen Alternativen jedoch das Problem, dass bei jedem AJAX Request sowohl der komplette ViewState übertragen wurde und vor allem auf dem Server auch der komplette Page Life Cycle durchlebt wurde.

Leichtgewichtig durch die Ajax Welt

Heute möchte ich deshalb einen Ansatz zeigen, der die Vorteile der bisher vorgestellten Ansätze vereint, jedoch nicht über die selben Nachteile verfügt.

Konkret bedeutet dies, dass die Implementierung auf Clientseite browserübergreifend stattfinden wird, zwischen Client und Server nur die wirklich notwendigen Daten übertragen werden und der Page Life Cycle auf der Serverseite nicht durchlaufen wird.

Page Methods

Der erste Schritt Richtung einer leichtgewichtigen Implementierung führt über die sogenannten Page Methods. Page Methods sind statische Methoden einer *.aspx Seite, die mit dem [WebMethod] Attribut annotiert werden. Für den Client erscheinen Page Methods wie normale Methoden eines Web Services.

Ausgehend vom Beispielcode der letzten drei Teile der Serie passe ich den Servercode für die Methode, die den Inhalt einer Datei zurückliefert also wie folgt an:

using System.IO;
using System.Web.UI;
using System.Web.Services;

public partial class Teil4 : Page
{
	[WebMethod]
    public static string ReadStaticFile()
    {

	     string fileContent;
         using (var reader =
             new StreamReader(
                 System.Web.HttpContext.Current.Server.MapPath("~/static.html")))
         {

             fileContent = reader.ReadToEnd();
         }
         return fileContent;
     }
}

Als einzige Neuerung im Vergleich zur vorherigen Version fällt das zuvor beschriebene Attribut [WebMethod] auf.

Um die Methode ReadStaticFile nun clientseitig über das ASP.NET Ajax Framwork nutzen zu können, müssen wir innerhalb der *.aspx Seite bei unserem ScriptManager noch die Eigenschaft EnablePageMethods mit dem Wert true versehen.

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

Als Ergebnis rendert der Server bei einem Aufruf der Seite nun folgenden Java Script Code inline in die Seite hinein:

 <script type="text/javascript">
         //<![CDATA[
         var PageMethods = function() {

            PageMethods.initializeBase(this);
            this._timeout = 0;
            this._userContext = null;
            this._succeeded = null;
            this._failed = null;
         }
         PageMethods.prototype = {
             _get_path: function() {
                 var p = this.get_path();
                 if (p) return p;
                 else return PageMethods._staticInstance.get_path();
             },
             ReadStaticFile: function(succeededCallback, failedCallback, userContext) {
                 /// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
                 /// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
                 /// <param name="userContext" optional="true" mayBeNull="true"></param>
                 return this._invoke(this._get_path(), 'ReadStaticFile', false, {}, succeededCallback, failedCallback, userContext);
             } 
         }
         // und vieles mehr ;-)

Wie man sieht, wird ein JavaScript Objekt PageMethods angelegt, welches über eine Funktion ReadStaticFile verfügt. Die Funktion hat also genau denselben Namen wie unsere serverseitige Funktion. Weiter nimmt Sie drei optionale Parameter entgegen:

  • succeededCallBack: Funktion, die aufgerufen wird, wenn die Ajax Anfrage erfolgreich beendet wurde
  • failedCallBack: Funktion, die aufgerufen wird, wenn die Ajax Anfrage fehlerhaft beendet wurde
  • userContext: Der Inhalt dieser Variable wird an die Funktionen succeededCallBack bzw. faildCallBack durchgereicht.

Der clientseitige Aufruf der Page Method gestaltet sich nun relativ einfach. Als erstes fügen wir der *.aspx Seite einen neuen ClientScriptBlock hinzu:

<script type="text/javascript">       
  function getStaticFile() {
    PageMethods.ReadStaticFile(onSuccess, onError, "MeinKontext");
  }

  function onSuccess(result, userContext, methodName) {
    var contentDiv = $get('content');
    if (userContext) {
      result += "<p>Der UserContext war: <strong>" + userContext + "</strong></p>";
    }

    result += "<p>Der Methoden Name war: <strong>" + methodName + "</strong></p>";
    contentDiv.innerHTML = result;
  } 

  function onError(result) {
    var contentDiv = $get('content');
    contentDiv.innerHTML = result;
  }

</script>

Einstiegspunkt für unseren späteren Code (Click Ereignis unseres Links) ist die Funktion getStaticFile. Sie ruft die Methode ReadStaticFile des Objekts PageMethods. Als Argumente übergeben wir zum einen die Funktionen für die erfolgreiche bzw. fehlerhafte Behandlung der Ajax Anfrage sowie als drittes die Zeichenfolge "MeinKontext".

Die Funktion onSuccess ist nun die Funktion, die die (erfolgreiche) Antwort des Servers verarbeitet. Sie nimmt neben der eigentlichen Antwort des Servers, auch den zuvor übergebenen userContext entgegen. Außerdem wird der Name der aufgerufenen Servermethode im Parameter methodName übergeben. Da JavaScript relativ flexibel ist, was die Anzahl der Parameter angeht und Funktionen einzig durch ihren Namen und nicht anhand ihrer Signatur identifiziert, können die beiden letzten Parameter übrigens auch problemlos weg gelassen werden.

Innerhalb der Methode onSuccess holen wir uns via $get zunächst eine Referenz auf das Div Element in das wir später unser Ergebnis schreiben möchten. $get ist übrigens eine Funktion des ASP.NET Ajax Client Frameworks. Sie ist (vereinfacht gesagt) die Kurzschreibweise für die JavaScript Funktion document.getElementById.

Weiter werden noch userContext (falls vorhanden) und der aufgerufene Methodenname an das Ergebnis angehangen. Im letzten Schritt wird das komplette Ergebnis schließlich in unser Container Div geschrieben.

Der Aufruf  der Funktion getStaticFile geschieht schließlich im Event onclick eines HTML Links (a Tag).

   17     <p>

   18         <a href="#" onclick="getStaticFile()">Hier klicken zum Request einer statischen Datei

   19         </a>

  // ....

   27     </p>

   28     <div id="content">

   29         Bitte klicken Sie auf einen der Links, damit dieser Bereich gefüllt wird.

   30      </div>

Schweres Leichtgewicht

Ruft man die Seite nun im Browser auf, fällt auf, dass neben der eigentlichen Seite auch drei JavaScript Dateien des Microsoft ASP.NET Ajax Client Frameworks geladen werden.

Erster Request der Seite

Die erste Anfrage umfasst nun also 93 kb, von denen sich 85 kb auf die MS AJAX Script Dateien verteilen.

Seinen Vorteil spielt die Herangehensweise jedoch bei den Ajax Anfragen aus. Wie man im folgenden Screenshot sieht, gehen hier gerade einmal 127 Byte über die Leitung. Weiter erkannt man deutlich, dass während des Posts kein ViewState mehr zum Server übertragen wird.

02_ajaxpost

Auch die Antwort ist relativ schmal und beschränkt sich auf das nötigste:

03_ajaxantwort

Die Antwort ist übrigens im JSON (Java Script Object Notation) Format serialisiert. Hätte unser Post Daten in Form von Argumenten für unsere Page Method enthalten, wären diese auch automatisch im JSON Format serialisiert worden. Eine etwas besser lesbare Variante der JSON formatierten Antwort enthalt der Tab JSON des Firebugs:

04_ajaxjson

In unserem Beispiel ist die Antwort zwar noch recht übersichtlich, so dass wir den Tab JSON eigentlich nicht benötigen würden, spätestens bei der Rückgabe komplexer Objektstrukturen lernt an den Tab jedoch schnell zu schätzen ;-)

Es geht noch besser!

Obwohl die bisherige Implementierung bereits ganz gut ist, gibt es noch zwei Kritikpunkte. Zum einen erscheint die Plazierung einer statischen Methode innerhalb der *.aspx Seite ein wenig eigenartig. Da sie weder auf Instanz Exemplar-Felder, noch auf Exemplar-Methoden der Seite zugreifen kann, ist sie innerhalb der Seite eigentlich fehl am Platz.

Außerdem stört mich der automatisch generierte JavaScript Script Block innerhalb der Seite, der die Proxies für die PageMethod Aufrufe zur Verfügung stellt. Lieber wäre mir die Variante einer eingelinkten externen JavaScript Datei.

Beide Punkte können wir glücklicherweise recht einfach lösen, nämlich durch den Einsatz von Script Services. Script Services sind normale Web Services, die mit dem ScriptService Attribut annotiert werden. Erstellt man einen Web Service mit Visual Studio 2008, ist dieses Attribut sogar bereits über der Klasse enthalten. Allerdings wird es automatisch auskommentiert.

Um einen Web Service nun also als Script Service zur Verfügung zu stellen, genügt es den Kommentar, wie in Zeile 13 des folgenden Listings gezeigt, zu entfernen.

   11 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

   12 // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.

   13 [System.Web.Script.Services.ScriptService]

   14 public class AjaxDemoService : WebService {

   15 

   16     [WebMethod]

   17     public string HelloWorld() {

   18         return "Hello World";

   19     }

   20 

   21     [WebMethod]

   22     public string Echo(int number)

   23     {

   24         return string.Format("Sie haben {0} eingegeben.", number);

   25     }  

   26 }

Prinzipiell reicht dies aus, um den Service via Ajax anzusprechen. Um uns das Leben jedoch ein wenig einfacher zu machen, lassen wir das ASP.NET Ajax Framework für uns - analog zur Page Method Lösung - JavaScript Proxies für unsere Methoden Aufrufe erstellen. Dazu fügen wir innerhalb des ScriptManager Tags der Seite ein Kind Element <Services> ein. Diesem Element fügen wir als weiteres Kind Element eine ServiceReference ein, deren Pfad auf unseren Service verweißt.

   10     <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True">

   11         <Services>

   12             <asp:ServiceReference Path="~/AjaxDemoService.asmx" />

   13         </Services>

   14     </asp:ScriptManager>

An dieser Stelle sei noch einmal darauf hingewiesen, dass dieser Schritt reine Bequemlichkeit und nicht zwingend erforderlich ist. Ohne ihn könnten wir unseren Service genauso via JavaScript rufen und würden genauso JSON als Serialisierungsformat nutzen können. Allerdings müssten wir den clientseitigen Aufruf dann komplett selbst entwickeln und hätten keine Proxies zur Verfügung.

Da wir jedoch bequem sind, lassen wir uns Proxymethoden generieren. Diese können wir dann in unserem bestehenden Client Script Block innerhalb der Seite aufrufen.

Wie die folgende Abbildung zeigt, erhalten wir dann sogar Intellisense und erfahren somit, welche Argumente eine bestimmte Methode entgegen nimmt.

05_VS

Der Rest gestaltet sich ähnlich der Page Method Implementierung. Als wichtiger Unterschied wäre noch zu erwähnen, dass die generierten Proxies standardmäßig in eine externe JavaScript Datei geschrieben werden und somit nicht mehr als Client Script Block in der Seite auftauchen.

Die Kommunikation zwischen Client und Server läuft analog der Page Method Implementierung.

Der Client sendet in seiner Anfrage unter anderem einen Header Content-Type mit dem Wert "application/json; charset=utf-8". Dieser ist besonders wichtig, denn er steuert den Serialisierungsmechanismus des Servers. Hat dieser Header einen anderen Wert, wird die Antwort XML Serialisiert. Somit ist sichergestellt, dass JavaScript Clients JSON erhalten, wohingegen andere Clients XML erhalten.

06_ajaxheader2

Die nächste Abbildung zeigt, dass auch die Post Parameter via JSON serialisiert werden:

07_ajaxpost2

Für die Antwort ist dies natürlich auch weiterhin der Fall:

07_ajaxantwort2

Fazit:

Dieser Teil der Serie hat gezeigt, dass sich die Menge der übertragenen Daten während eines Ajax Requests mit der Hilfe von Page Methods, Script Services und dem ASP.NET Ajax Framework auf ein Minimum reduzieren lassen. Außerdem wird bei dieser Variante der ASP.NET Page Life Cycle im Gegensatz zu den vorherigen Beispielen nicht durchlaufen.

Beides in Kombination kann zu gewaltigen Performancevorteilen verglichen mit Client Callbacks oder dem Updatepanel führen.

Einziger Wermutstropfen ist die Übertragung der 85 kb großen ASP.NET Ajax Framework Java Script Dateien während des ersten Requests.

Eine Abhilfe für dieses Problem werden wir uns im nächsten Teil der Serie ansehen.

Wie immer freue ich mich natürlich über Kommentare zu diesem Blog Beitrag.

 

Dieser Beitrag war hilfreich? Dann bitte nicht vergessen Ihn zu kicken!


Kick it on dotnet-kicks.de
 
2/18/2010 - 10:14 PM | Comments [0] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

Zu Anfang hoch gelobt, später ausgepfiffen und verpönt. Was sich im ersten Moment nach der typischen Geschichte deutscher Castingshow Teilnehmer anhört, ist in Wirklichkeit das Schicksal des ASP.NET AJAX Updatepanels.

Woll kaum ein anderes Control hat nach seiner Einführung einen solchen Hype verursacht und ist später so tief gefallen.

Der Reihe nach

Ehe wir im dritten Teil meiner ASP.NET Ajax Serie prüfen werden, ob das Updatepanel seinen schlechten Ruf zu recht oder unrecht hat, möchte ich zunächst jedoch kurz ein paar allgemeine Worte über das Steuerelement verlieren.

Das Updatepanel kam mit den ASP.NET Ajax Extensions in der Version 1.0 für das .NET Framework 2.0 auf den Markt und musste für Visual Studio 2005 noch separat heruntergeladen und installiert werden. Ab Visual Studio 2008 ist das UpdatePanel sofort von Hause aus in der Version 3.5 des ASP.NET Ajax Frameworks enthalten.

Ziel des Updatepanels ist es, ASP.NET Entwicklern die Möglichkeit zu geben Ajax zu implementieren, ohne selbst clientseitigen Code schreiben zu müssen. Im Gegensatz zu den Client Callbacks, die ich im Teil 2 der Serie vorgestellt habe, können ASP.NET Entwickler also in Ihrer Komfortzone - dem Server - bleiben.

Der klassische Ansatz über ASP.NET Postbacks

Basis für den heutigen Blogpost wird wieder eine leicht modifizierte Variante des Beispielcodes der vorherigen beiden Teile der Serie sein. Da das Updatepanel aus Entwicklersicht rein serverseitig arbeitet, sind zunächst einige Veränderungen am bestehenden Quellcode notwendig.

Zum einen sind sämtliche HTML Links (a Tags) durch ASP.NET LinkButtons zu ersetzen. Außerdem habe ich den HTML Div Tag, der das Ergebnis der Click-Aktionen anzeigte, durch ein ASP.NET Literal Control ersetzt. Den zuvor in die Seite integrierten JavaScript Code des letzten Beispiels habe ich komplett entfernt.

    1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Teil3.aspx.cs" Inherits="Teil2" %>

    2 

    3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    4 <html xmlns="http://www.w3.org/1999/xhtml">

    5 <head runat="server">

    6     <title></title>

    7 </head>

    8 <body>

    9     <form id="form1" runat="server">

   10     <h1>

   11         ASP.NET Webforms Anwendungen und Ajax (Teil3): Das Updatepanel</h1>

   12     <div>

   13         <p>

   14             <asp:LinkButton ID="StaticLinkButton" runat="server" OnClick="StaticLinkButton_Click">Hier klicken zum Request einer

   15                 statischen Datei</asp:LinkButton>

   16             <br />

   17             <asp:LinkButton ID="HelloWorldLinkButton" runat="server" OnClick="HelloWorldLinkButton_Click">Hier

   18                 für Hello World WebService klicken</asp:LinkButton>

   19             <br />

   20             <asp:LinkButton ID="EchoLinkButton" runat="server" OnClick="EchoLinkButton_Click">Hier für Echo WebService klicken.

   21                 Geben Sie bitte vorher eine Zahl in nebenstehendem Feld ein:</asp:LinkButton>

   22             &nbsp;<asp:TextBox ID="EchoTextBox" runat="server" Width="40px">4711</asp:TextBox>

   23         </p>

   24     </div>

   25     <asp:Literal ID="contentLiteral" runat="server">Bitte klicken Sie auf einen der Links,

   26     damit dieser Bereich gefüllt wird.</asp:Literal>

   27     </form>

   28 </body>

   29 </html>

Serverseitig hat der Code keine Überraschungen parat. Je HTML Link Button gibt es einen Handler für das Click Event, in dem der Inhalt des Literal Controls gesetzt wird.

    1     protected void StaticLinkButton_Click(object sender, EventArgs e)

    2     {

    3         this.contentLiteral.Text = this.ReadStaticFile();

    4     }

    5 

    6     protected void HelloWorldLinkButton_Click(object sender, EventArgs e)

    7     {

    8         this.contentLiteral.Text = this.CallHelloWorldService();

    9     }

   10 

   11     protected void EchoLinkButton_Click(object sender, EventArgs e)

   12     {

   13         this.contentLiteral.Text = this.CallEchoService();

   14     }

   15 

   16     private string ReadStaticFile()

   17     {

   18         string fileContent;

   19         using (var reader = new StreamReader(Server.MapPath("~/static.html")))

   20         {

   21             fileContent = reader.ReadToEnd();

   22         }

   23         return fileContent;

   24     }

   25 

   26     private string CallHelloWorldService()

   27     {

   28         return new AjaxDemoService().HelloWorld();

   29     }

   30 

   31     private string CallEchoService()

   32     {

   33         int value;

   34         int.TryParse(this.EchoTextBox.Text, out value);

   35         return new AjaxDemoService().Echo(value);

   36     }

Startet man die Seite nun, sieht man dass ein GET Request auf die Seite Teil3.aspx abgesetzt wird. Die Antwort umfasst 2.1 kb.

01ersterRequest

Klickt man nun auf einen der Link Buttons, wird ein Postback ausgeführt und die komlette Seite neu vom Server auf den Client übertragen. Dies überrascht auch nicht, schließlich wurde noch keinerlei Ajax Funktionalität implementiert.

02normalerPostback 

Ein erster Versuch mit dem Updatepanel

Um die Seite nun zu "ajaxifizieren", benötigen wir zwei Controls. Das erste ist der ASP.NET Scriptmanager. Seine Hauptaufgabe ist es, Scripte zum Client zu liefern. Er muss vor allen AJAX Controls innerhalb der Seite erscheinen. Außerdem ist noch anzumerken, dass der Scriptmanager nach dem Highlander prinzip arbeitet: "Es kann nur einen geben!" - nun ja, zumindest pro Seite. Andernfalls würde zur Laufzeit eine InvalidOperationException ausgelöst werden.

Dies bedeutet übrigens, dass ich beim Einsatz von Masterpages keinen ScriptManager auf einer Contentseite platzieren darf, falls  auf der Masterpage bereits ein Scriptmanager genutzt wurde. Stattdessen müsste ich auf der Contentseite einen ScriptmanagerProxy einsetzen. Diesen benötige ich allerdings nur, falls ich auf der Contentseite deklarativ noch weitere Scripte hinzufügen möchte, die auf der Masterpage nicht gebraucht werden.

Da ich langsam etwas ausschweifend werde: Zurück zum Thema!

Das zweite Control, das wir auf unserer Seite brauchen ist ein Updatepanel. Es dient als Container des Bereichs, der später aktualisiert werden soll. Updatepanels dürfen im Gegensatz zum Scriptmanager übrigens mehrfach auf einer Seite vorkommen.

 03toolbox

Nachdem ich Scriptmanager und Updatepanel auf die Seite gezogen, und die die Clientcontrols in das Updatepanel verschoben habe, sieht das Markup meiner Seite wie folgt aus.

    1 <body>

    2     <form id="form1" runat="server">

    3     <asp:ScriptManager ID="ScriptManager1" runat="server">

    4     </asp:ScriptManager>

    5     <h1>

    6         ASP.NET Webforms Anwendungen und Ajax (Teil3): Das Updatepanel</h1>

    7     <asp:UpdatePanel ID="UpdatePanel1" runat="server">

    8         <ContentTemplate>

    9             <p>

   10                 <asp:LinkButton ID="StaticLinkButton" runat="server" OnClick="StaticLinkButton_Click">Hier klicken zum Request einer

   11                 statischen Datei</asp:LinkButton>

   12                 <br />

   13                 <asp:LinkButton ID="HelloWorldLinkButton" runat="server" OnClick="HelloWorldLinkButton_Click">Hier

   14                 für Hello World WebService klicken</asp:LinkButton>

   15                 <br />

   16                 <asp:LinkButton ID="EchoLinkButton" runat="server" OnClick="EchoLinkButton_Click">Hier für Echo WebService klicken.

   17                 Geben Sie bitte vorher eine Zahl in nebenstehendem Feld ein:</asp:LinkButton>

   18                 <asp:TextBox ID="EchoTextBox" runat="server" Width="40px">4711</asp:TextBox>

   19             </p>

   20             <asp:Literal ID="contentLiteral" runat="server">Bitte klicken Sie auf einen der Links,

   21     damit dieser Bereich gefüllt wird.</asp:Literal>

   22         </ContentTemplate>

   23     </asp:UpdatePanel>

   24     </form>

   25 </body>

Da keine Anpassungen im Code Behind meiner Seite notwendig sind, kann ich die Anwendung nun direkt starten.

Ein Blick auf den Netzwerkverkehr zeigt, dass bei der ersten Anfrage der Seite neben der eigentlichen Seite noch drei JavaScript Dateien übertragen wurden. Die Verweise auf diese drei Script Dateien wurden durch den ScriptManager automatisch in die Seite eingetragen. Allerdings werden nun statt der initialen 2.1 kb knapp 90 kb zum Client übertragen.

04ersterRequestMitUpdatePanel

Den eigentlichen Vorteil sehen wir, sobald wir auf einen der Links klicken. Nicht nur, dass die Seite nicht komplett neu geladen wird und somit das lästige flackern beim Seitenneuaufbau entfällt, auch die Menge der übertragenen Daten hat sich auf 1,5 kb reduziert.

05AsyncRequest1Post

Anscheinend haben wir also alles richtig gemacht. Leider aber auch nur anscheinend. Ein näherer Blick auf die Antwort des Servers zeigt, dass neben dem aktualisierten Inhalt auch das Markup für unsere drei Aktionslinks sowie die Textbox erneut übertragen wurden.

06AsyncRequest1Antwort

Das liegt daran, dass wir einen klassichen Updatepanel Anfängerfehler gemacht haben. Wir haben den kompletten Inhalt der Seite in das Updatepanel geschoben. Somit wird dieser auch vollkommen korrekt wieder vollständig vom Server zum Client übertragen.

Das ist selbstverständlich nicht das, was wir wollten. Schließlich ist unsere Ajax Implementierung nur augenwischerei wenn im Hintergrund doch wieder (fast) alles zum Client übertragen wird.

Ein zweiter Anlauf

Was können wir also tun? Nun, da wir nur den Inhalt des Steuerelements contentLiteral aktualisieren möchten, sollten wir offensichtlich auch nur diesen in das Updatepanel aufnehmen. Dazu schieben wir einfach die drei Aktionslinks wieder über das Updatepanel.

Wenn wir die Seite nun starten, stellen wir jedoch fest, dass ein Klick auf einen der Aktionslinks wieder einen vollständigen Postback inklusive komplettem Rendern der Seite auslöst.

Die Ursache hierfür ist, dass ein Updatepanel standardmäßig nur einen partiellen Postback verursacht und sich somit erneuert, wenn eines seiner Childcontrols einen Postback initiert.

Glücklicherweise lässt sich dieses Verhalten jedoch leicht ändern. Für jedes Updatepanel können sogenannte Trigger registriert werden, die einen partiellen Postback auslösen. Ein Trigger ist dabei nichts anderes, als ein Event eines Servercontrols.

Das Registrieren eines solchen Triggers kann entweder über den Designer oder über das ASP.NET Markup geschehen. Um es über das Markup zu lösen, müssen wir unser Beispiel wie folgt anpassen:

    1     <asp:UpdatePanel ID="UpdatePanel1" runat="server">

    2         <ContentTemplate>

    3             <asp:Literal ID="contentLiteral" runat="server">Bitte klicken Sie auf einen der Links,

    4     damit dieser Bereich gefüllt wird.</asp:Literal>

    5         </ContentTemplate>

    6        <Triggers>

    7             <asp:AsyncPostBackTrigger ControlID="StaticLinkButton" EventName="Click" />

    8             <asp:AsyncPostBackTrigger ControlID="HelloWorldLinkButton" EventName="Click" />

    9             <asp:AsyncPostBackTrigger ControlID="EchoLinkButton" EventName="Click" />

   10         </Triggers>

   11     </asp:UpdatePanel>

Lädt man die Seite nun erneut, stellt man fest, dass die Aktionslinks wieder partielle Postbacks auslösen. Außerdem werden auch nur die notwendigen Daten übertragen.

07AsyncRequest2Antwort

Zu viel des Guten

Das Ergebnis sieht eigentlich schon ganz gut, so das man meinen könnte, dass wir nun fertig wären. Leider gibt es aber noch ein kleines Problem. Um dies zu verdeutlichen lege ich ein weiteres Updatepanel auf der Seite an. Dieses Panel erhält ein Literal-Control, welches serverseitig mit der aktuellen Uhrzeit gefüllt wird.

    1     <asp:UpdatePanel ID="UpdatePanel2" runat="server">

    2         <ContentTemplate>

    3             <asp:Literal ID="TimeLiteral" runat="server"></asp:Literal>

    4         </ContentTemplate>

    5     </asp:UpdatePanel>

 

    1     protected void Page_Load(object sender, EventArgs e)

    2     {

    3         this.TimeLiteral.Text = DateTime.Now.ToLongTimeString();

    4     }

Meine Erwartungshaltung wäre, dass bei einem Klick auf einen der drei Aktionslinks nur das ursprüngliche Updatepanel aktualisiert wird, da die Links nur hier als Trigger registriert wurden. Das neue Updatepanel sollte nicht aktualisiert werden.

Ein kurzer Test zeigt dass dem nicht so ist:

08_2UpdatePanels1 08_2UpdatePanels2

Wie die beiden Abbildungen zeigen, wurde nicht nur der Bereich des ersten Updatepanels, sondern auch der des zweiten Updatepanels aktualisiert.

Glücklicherweise lässt sich dieses Verhalten jedoch steuern! Das Updatepanel verfügt über eine Eigenschaft UpdateMode. Diese gibt an, wann sich das Updatepanel aktualisieren soll. Standardmässig ist diese Eigenschaft mit dem Wert Always belegt. Stellt man den Wert jedoch auf Contidional, wird das Updatepanel nur noch aktualisiert, wenn entweder eines seiner Childcontrols einen Postback initiert hat, einer der registrierten Trigger ausgelöst wurde, oder explizit die Methode Update serverseitig gerufen wurde.

Die folgenden Abbildungen zeigen die Auswirkung der Änderung des UpdateModes:

08_2UpdatePanels3

08_2UpdatePanels4

08_2UpdatePanels5

Fazit

Richtig eingesetzt ist das Updatepanel meiner Meinung nach besser als sein Ruf und für einfache Szenarien durchaus geeignet. Ajax Funktionalitäten können mit wenig Aufwand und ohne manuellen Scriptcode implementiert werden.

Achtet man darauf, nicht die komplette Seite in ein Updatepanel zu integrieren und den UpdateMode auf Conditional zu setzen, kann das Updatepanel in vielen Szenarien guten Gewissens genutzt werden. Man sollte sich allerdings darüber im Klaren sein, dass serverseitig bei jeder Anfrage der komplette Page-Lifecycle durchlaufen wird. Zeitaufwändige Operationen im Page_Load können - ähnlich wie bei den Client Callbacks - die Ajax Anfragen demnach erheblich verlangsamen!

Was man machen kann, wenn man den Pagelifecycle nicht komplett durchlaufen möchte, werde ich übrigens im nächsten Teil dieser Serie zeigen.


Kick it on dotnet-kicks.de
 
2/12/2010 - 12:51 AM | Comments [0] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

In meinem letzten Blog Eintrag habe ich erklärt, wie Ajax von Hand mit Hilfe des XMLHttpRequest Objekts implementiert werden kann.

Eine Alternative zu dieser händischen Implementierung ist die Nutzung des ASP.NET Client Callback Frameworks, welches mit der Version 2.0 von ASP.NET erschien.

Um Client Callbacks nutzen zu können muss eine ASP.NET Seite bzw. ein ASP.NET Control das Interface ICallbackEventHandler implementieren. Das Interface definiert die beiden Methoden:

RaiseCallbackEvent ist die Methode, den Aufruf des Clients entgegen nimmt. GetCallbackResult gibt das Ergebnis der serverseitigen Verarbeitung an den Client zurück.

Startet der Client eine Anfrage an eine Seite / ein Control welches ICallBackEventHandler implementiert, wird eine reduzierte Variante des ASP.NET Page Lifecycle / ASP.NET Control Lifecycle ausgeführt. Im wesentlichen entspricht die Callback Variante der "klassichen" Postback Variante, es fehlen jedoch die Methoden, die sich um das Rendering des Outputs kümmern. Außerdem werden keine Postback Events ausgeführt.

Das ganze sieht bezogen auf die Phasen also ungefähr so aus:

04lifecycle

Los gehts

Sehen wir uns das ganze nun einmal an einem konkreten Beispiel an. Als Ausgangsbasis soll eine modifizierte Kopie der Seite des letzten Beispiels mit folgendem Markup dienen:

    1     <div>

    2         <p>

    3             <a href="#">Hier klicken zum Request einer

    4                 statischen Datei </a>

    5             <br />

    6             <a href="#">Hier für Hello World WebService klicken </a>

    7             <br />

    8             <a href="#">Hier für Echo WebService klicken. Geben Sie bitte vorher eine Zahl in nebenstehendem Feld ein: </a>&nbsp;

    9             <asp:TextBox ID="TextBox1" runat="server" Width="40px">4711</asp:TextBox>

   10         </p>

   11     </div>

   12     <div id="content">

   13         Bitte klicken Sie auf einen der Links, damit dieser Bereich gefüllt wird.

   14     </div>

 

Der Server

Auf Serverseite müssen wir nun in unserer Seite das Interface ICallbackEventHandler implementieren. Ausgehend von unserem Markup gilt es drei Operationen zu unterstützen:

  • Zurückgeben des Inhalts einer statischen Datei
  • Aufruf einer parameterlosen Methode eines Webservices, die "Hello World" zurück gibt
  • Aufruf einer Methode Echo eines Webservices, welche einen numerischen Wert entgegen nimmt und einen String zurück gibt.

Welche Operation auszuführen ist, unterscheiden wir anhand des einzigen Arguments der Methode RaiseCallbackEvent: eventArgument.

    1 public partial class Teil2 : Page, ICallbackEventHandler

    2 {

    3     private string callbackResult;

    4 

    5     public void RaiseCallbackEvent(string eventArgument)

    6     {

    7         switch (eventArgument)

    8         {

    9             case "static":

   10                 this.ReadStaticFile();

   11                 break;

   12             case "HelloWorld":

   13                 this.CallHelloWorldService();

   14                 break;

   15             case "Echo":

   16                 this.CallEchoService();

   17                 break;

   18             default:

   19                 this.ReturnUnknownOperationError();

   20                 break;

   21         }

   22     }

   23 

   24     public string GetCallbackResult()

   25     {

   26         return this.callbackResult;

   27     }

Je nach Wert des Arguments eventArgument wird eine andere Methode zur weiteren Verarbeitung aufgerufen. Jede dieser Methoden schreibt ihr Ergebnis wiederum in das Feld callbackResult. Dieses wird wiederum als Ergebnis der Methode GetCallbackResult an den Client zurück gegeben.

Der Inhalt der verarbeitenden Methoden sieht wie folgt aus:

Auslesen der statischen Datei:

    1 private void ReadStaticFile()

    2 {

    3     using (var reader = new StreamReader(Server.MapPath("~/static.html")))

    4     {

    5         this.callbackResult = reader.ReadToEnd();

    6     }

    7 }

Aufruf der Methode HelloWorld des Webservices:

    1 private void CallHelloWorldService()

    2 {

    3     this.callbackResult = new AjaxDemoService().HelloWorld();

    4 }

Aufruf der Methode Echo des Webservices:

    1 private void CallEchoService()

    2 {

    3     int value;

    4     int.TryParse(this.TextBox1.Text, out value);

    5     this.callbackResult = new AjaxDemoService().Echo(value);

    6 }

Wie man beim Aufruf der Webservice Methoden sieht, geschieht dieser Aufruf nun vom Server aus und nicht vom Client. Dadurch dass im Gegensatz zum vorherigen Beispiel der Client nun nicht mehr direkt den Service ruft, musste ich mir einen Proxy auf Serverseite erstellen.

Schön, aber wie komme ich nun auf den Server?

Bisher haben wir zwar geklärt, was auf dem Server alles geschehen muss, damit Client Callbacks funktionieren, offen ist allerdings noch, wie man überhaupt vom Client auf den Server kommt.

Der Aufruf des Servers geschieht über die JavaScript Funktion WebForm_DoCallback. Einen entsprechenden Aufruf der Methode kann man sich über den ClientScriptManager der Seite anhand der Methode GetCallbackEventReference erzeugen lassen. Zeile 5 des folgenden Listing demonstriert dies.

    1 protected void Page_Load(object sender, EventArgs e)

    2 {

    3     var clientScript = Page.ClientScript;

    4     string callbackReference =

    5         clientScript.GetCallbackEventReference(this, "argument", "requestFinished", "context", true);

    6 

    7     string script = string.Format("function sendAjaxRequest(argument, context){{\n {0}; }}", callbackReference);

    8 

    9     if (!clientScript.IsClientScriptBlockRegistered("callback"))

   10     {

   11         clientScript.RegisterClientScriptBlock(this.GetType(), "callback", script, true);

   12     }

   13 }

Die Methode GetCallbackEventReference hat in der höchsten Überladung folgende Parameter:

  Parameter Bedeutung
control Servercontrol, dass den Callback behandelt.
argument Argument, das an die Methode RaiseCallbackEvent des behandelnden Controls geschickt wird.
clientCallback Name der Javascript Funktion, die die Rückgabe des Servers im Erfolgsfall behandelt.
context Javascsript Code, der ausgeführt wird, ehe die Anfrage an den Server geschickt wird. Achtung, hierbei handelt es sich nicht um einen JavaScript String, sondern um einen Befehl / eine Funktion
clientErrorCallback Name der Javascript Funktion, die die Rückgabe des Servers im Fehlerfall behandelt. In meinem Beispiel habe ich diesen Paramter nicht gesetzt.
useAsync Boolscher Wert, der angibt, ob die Anfrage asynchron (true) oder synchron (false) abgesetzt werden soll.

Der Einfachheit halber verpackt man den generierten Aufruf der Methode WebForm_DoCallback in eine eigene JavaScript Funktion, die nur noch die wirklichen variablen Parameter argument und context entgegen nimmt, wie in Zeile 7 geschehen. Anschließend registriert man wie in Zeile 9 - 11 noch einen ClientScriptBlock für diese Funktion, der wie folgt in die Seite gerendert wird.

    1 <script type="text/javascript">

    2     //<![CDATA[

    3     function sendAjaxRequest(argument, context) {

    4         WebForm_DoCallback('__Page', argument, requestFinished, context, null, true);

    5     } //]]>

    6 </script>

Die generierte Methode sendAjaxRequest kann man anschließend wie folgt aufrufen:

    1             <a href="#" onclick="sendAjaxRequest('static', showMessage());">Hier klicken zum Request einer

    2                 statischen Datei </a>

    3             <br />

    4             <a href="#" onclick="sendAjaxRequest('HelloWorld', '');">Hier

    5                 für Hello World WebService klicken </a>

    6             <br />

    7             <a href="#" onclick="sendAjaxRequest('Echo', null);">Hier

    8                 für Echo WebService klicken. Geben Sie bitte vorher eine Zahl in

    9             nebenstehendem Feld ein: </a>

Zeile 1 zeigt, wie der Parameter context genutzt wird. Ich übergebe einfach die JavaScript Funktion showMessage als Argument. Diese wird nun automatisch aufgerufen, ehe der Request gestartet wird. Dies ist zum Beispiel bei länger dauernden Ajax Aufrufen sinnvoll, bei denen man dem Benutzer einen Hinweis anzeigen möchte, wie folgendes Listing zeigt:

    1 <script type="text/javascript">

    2     function showMessage() {

    3         var contentDiv = document.getElementById('content');

    4         contentDiv.innerHTML = 'Lade...';

    5     }

    6 </script>

Wichtig ist, dass der Parameter keinen String entgegen nimmt (wie leider in vielen Beispielen fälschlicherweise gezeigt), sondern eine JavaScript Funktion!

Eigentlich Interessant für unser Beispiel ist jedoch nicht der Parameter context, sonder der Parameter argument. Der Inhalt dieses Parameters (und nur dieser!) wird an die Methode RaiseCallbackEvent des Servers übergeben. Er steuert somit die auszuführende Funktionalität.

Alles beim Alten?

Spielt man nun ein wenig mit der vorgestellten Lösung herum, bemerkt man schnell, dass der Aufruf der Methode Echo stets ausgibt, das man 4711 eingegeben hat - auch wenn der Wert der Textbox zwischenzeitlich geändert wurde.

Ursache hierfür ist, dass ASP.NET Client Callbacks für alle Formularfelder stets die initialen Werte zurücksendet.

Screenshot der Beispielanwendung

Abhilfe schafft folgender JavaScript Code:

    1 __theFormPostData = '';

    2 WebForm_InitCallback();

Diesen packt man einfach in eine JavaScript Funktion, z. B. mit dem Namen repostForm.

Gibt man diese nun als context während des Aufrufs an:

    1 <a href="#" onclick="sendAjaxRequest('Echo', repostForm());">Hier [...]

werden auch die geänderten Eingaben des Benutzers übertragen.

Screenshot zeigt dass die geänderten Werte übertragen wurden

Fazit

ASP.NET Client Callbacks sind eine schnelle und einfache Lösung um Ajax Funktionalitäten unter ASP.NET ab der Version 2.0 zu implementieren.

Bei aller Einfachheit sollte man jedoch beachten, dass diese Lösung auch einige Schwächen hat:

  1. Auf dem Server wird fast der komplette Page Lifecycle ausgeführt. Daher sollten Codebestandteile, die bei einem Callback nicht zwingend ausgeführt werden müssen immer durch ein if(!Page.IsCallback) ... geklammert werden, um unnötige Wartezeiten zu verhindern.
  2. Bei jedem Callback wird der komplette Viewstate zum Server übertragen. Gerade bei großem Viewstate und schlechter Internetanbindung verlangsamt dieser Umstand die Ajax Anfrage enorm.
  3. Es kann immer nur ein Argument an den Server übergeben werden. Möchte man mehr als eins übergeben, muss man die Werte durch ein Trennzeichen trennen und auf der Serverseite auseinanderpflücken
  4. Das übergebene Argument wird nicht wieder vom Server auf den Client zurück übertragen. Somit ist es in der verarbeitenden Javascript Funktion auf dem Client unmöglich herauszufinden, welche Operation ursprünglich gestartet wurde. Abhilfe schafft auch hier ein zusammengesetzter Rückgabewert, oder aber eine eigene verarbeitende Funktion je Operation.

Hat man diese Schwächen jedoch im Blick, lassen sich mit ASP.NET Client Callbacks wunderbare Ajax Applikationen implementieren.

Unter der Haube arbeiten diese Client Callbacks übrigens genauso wie das händische Beispiel aus dem letzten Blogpost - nämlich mit dem XMLHttpRequest Objekt:

Screenshot des JavaScript Debuggers, zeigt Zugriff auf XMLHttpRequest Objekt


Kick it on dotnet-kicks.de
 
2/5/2010 - 01:20 AM | Comments [7] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

Das man mit statischen, oder auch dyanamischen Seiten, die bei jeder Anfrage die komplette Seite neu aufbauen, heute keinen Blumentopf mehr gewinnt, dürfte jedem klar sein. Verwöhnt durch Webanwendungen wie zum Beispiel Google-Maps oder Outlook Web Access erwarten Endanwender Webanwendungen, die einen ähnlichen Bedienkomfort wie Desktop Anwendungen aufweisen.

Als Entwickler solcher Webanwendungen bringt uns dies in die Situation, dass wir unsere Entwicklungstätigkeiten nun nicht mehr rein auf den Server beschränken können, sondern auch auf dem Client aktiv werden müssen, um die hohen Erwartungen unserer Anwender zu erfüllen.

Was "auf dem Client aktiv werden" konkret bedeutet möchte ich in einer kleinen Artikelreihe in meinem Blog beantworten.

Der Client - das unbekannte Wesen

Heutzutage sind eine Reihe Ajax Frameworks verfügbar, die uns die "schmutzige" Arbeit auf dem Weg zur dynamischen Webseite abnehmen wollen. Da es meiner Meinung nach aber nie schaden kann, wenn man weiss was diese magischen Toolsets und Frameworks unter der Haube machen, werden wir heute die Ärmel hoch krempeln und die "Drecksarbeit" auf dem Client selbst erledigen.

Ehe wir los legen, sollten wir zuvor jedoch einen Blick auf die notwendigen Zutaten werfen und kurz klären, worum es sich dabei im einzelnen handelt.

  1. JavaScript

    Das J in AJAX. Eine von vielen Entwicklern zu unrecht gefürchtete und/oder belächelte Client Script Sprache ;-) Der Scriptcode wird entweder direkt in eine Seite eingebettet, oder aber in eine externe Datei ausgelagert. JavaScript wird zur Manipulation des DOM genutzt

  2. das Document Object Model (DOM) des Browsers 

    Das DOM des Browsers ist eine Schnittstelle, die den Zugriff auf das zugrundeliegende (X)HTML Dokument bereitstellt. Über das DOM habe ich die Möglichkeit, Aussehen und Struktur der einer Webseite zu verändern.

  3. das XMLHttpRequest Objekt

    Eines der Objekte des window Objekts des Browsers(zumindest in modernen Browsern. Andernfalls ein ActiveX Objekt). Es ermöglicht einem Script das Absetzen einer (asynchronen) Anfrage an einen Webserver. 

Im Überblick sieht das Zusammenspiel der drei Komponenten ungefähr so aus:

Zusammenspiel zwischen JavaScript, DOM und XMLHttpRequest

Los gehts!

Wir wissen zwar jetzt, was wir alles brauchen, allerdings fehlt immer noch die Antwort, wie die Komponenten nun genutzt werden müssen, um AJAX in die eigene Webseite zu bekommen.

Starten wir dazu mit einem kleinen und sehr einfachen Beispiel.

Wir erstellen eine einfache ASPX Seite, die aus einem Link und einem Platzhalter Bereich bestehen soll. Sobald der Anwender auf den Link klickt, soll dieser Platzhalter mit dem Inhalt einer statischen Datei, welche auf dem Server liegt, gefüllt werden.

Der zugehörige HTML Code der ASPX Seite sieht wie folgt aus:

    1     <form id="form1" runat="server">

    3     <div>

    4         <p>

    5             <a href="#">Hier klicken zum Request einer

    6                 statischen Datei </a>

    7         </p>

    8     </div>

    9     <div id="content">

   10         Bitte klicken Sie den Link, damit dieser Bereich gefüllt wird.

   11     </div>

   12     </form>

Der Link, welcher später die Ajax Aktion initiieren soll, wird in den Zeilen 5 und 6 definiert. Der Bereich welcher später ersetzt werden soll, ist der Inhalt des DIV Tags aus Zeile 9.

Noch ist das ganze jedoch nicht sonderlich dynamisch. Dazu benötigen wir noch etwas JavaScript:

    1     <script type="text/javascript">

    2         var xmlHttp;

    3         window.onload = function() {

    4             initializeXmlHttp();

    5         }

    6 

    7         function initializeXmlHttp() {

    8             if (window.XMLHttpRequest) { // IE7, IE8, Mozilla, Safari, Opera

    9                 xmlHttp = new XMLHttpRequest();

   10             } else if (window.ActiveXObject) {

   11                 try {

   12                     xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); //IE 5.x, 6

   13                 }

   14                 catch (e) {

   15                     alert('Ajax Zugriffe sind aufgrund der aktuellen Sicherheitseinstellungen leider nicht möglich');

   16                 }

   17             }

   18         }

   19 

   20         function sendAjaxRequest(url) {

   21             if (xmlHttp) {

   22                 xmlHttp.open("GET", url, true);

   23                 xmlHttp.onreadystatechange = requestFinished;

   24                 xmlHttp.send(null);

   25             }

   26         }

   27 

   28         function requestFinished() {

   29             if (xmlHttp.readyState==4) {

   30               if (xmlHttp.status==200) {

   31                 var contentDiv = document.getElementById('content');

   32                 contentDiv.innerHTML = xmlHttp.responseText;

   33               }

   34             }

   35         }

   36    </script>

Das Script deklariert zunächst in Zeile 2 eine Variable, die einen Verweis auf das XMLHhtpRequest Objekt hält. Wie zuvor beschrieben ermöglicht dieses das Absetzen einer asynchronen Abfrage an den Server.

Die Funktion initializeXmlHttp ab Zeile 7 füllt dieses Objekt nun mit Leben. Der Code sollte recht selbsterklärend sein. Zeile 8 prüft, ob das XMLHttpRequest Objekt am window Objekt hängt. Falls ja, kann ein neues XMLHttpRequest Objekt erstellt werden. Ist dies nicht der Fall, muss es als ActiveX Objekt erstellt werden.

Die Funktion sendAjaxRequest ab Zeile 20 setzt die eigentliche Abfrage ab. Dazu prüft Sie in Zeile 21 zuerst, ob die Variable xmlHttp initialisiert werden konnte. Wäre dies nicht der Fall, könnte keine Abfrage abgesandt werden. Anschließend wird in Zeile 22 eine asynchrone Verbindung geöffnet mit der Methode open geöffnet. Diese Methode erhält als Parameter die Methode ("GET" oder "POST"), die URL, sowie ein Flag, ob die Anfrage asynchron abgesetzt werden soll.

Zeile 23 gibt eine CallBack-Funktion an (in unserem Fall requestFinished), die aufgerufen werden soll, sobald eine Antwort auf die Anfrage zurück gekommen ist.

Zeile 24 sendet die Anfrage nun schließlich ab.

Die Funktion requestFinished ab Zeile 28 verarbeitet die Antwort des Webservers. Da eine Anfrage verschiedene Phasen durchlebt, prüft die Funktion in Zeile 29 anhand der Eigenschaft readyState ab, ob die Anfrage tatsächlich komplett beendet wurde.

Da zu Ende nicht immer "hat auch geklappt" heißen muss, wird in der nächsten Zeile geprüft, ob die Anfrage auch erfolgreich beendet wurde (HTTP Statuscode 200).

Die eigentliche Verarbeitung findet nun in den Zeilen 31 und 32 statt. Zeile 31 holt sich eine Referenz auf den zu ersetzenden Bereich (unser Div mit der ID Content). Zeile 32 überschreibt diesen Inhalt schlussendlich mit der Antwort des Servers.

Damit das ganze auch funktioniert muss die Funktion sendAjaxRequest aus Zeile 20 noch aus unserem HTML Code heraus aufgerufen werden. Dazu erweitern wir unseren Link aus den Zeilen 5/6 einfach um das Attribut onclick.

    5             <a href="#" onclick="sendAjaxRequest('static.html');">Hier

    6                 klicken zum Request einer statischen Datei </a>

Ausgeführt sieht das ganze dann wie folgt aus:

Erster Aufruf der Seite Nach Aufruf des Links

Ganz nett ...

wir wissen nun also, wie wir statische Dateien nachladen können, aber steht das X in AJAX nicht für XML Webservices?

Genau! Deshalb habe ich das Demoprojekt auch um einen kleinen Webservice mit zwei sinnlosen Methoden erweitert:

    1 public class AjaxDemoService : System.Web.Services.WebService {

    2 

    3     [WebMethod]

    4     public string HelloWorld() {

    5         return "Hello World";

    6     }

    7 

    8     [WebMethod]

    9     public string Echo(int number)

   10     {

   11         return string.Format("Sie haben {0} eingegeben.", number);

   12     }  

   13 }

Wie rufen wir einen solchen Service nun aber per JavaScript auf?

Die Antwort findet sich relativ schnell, wenn man die URL des Webservices im Browser eingibt. In der nun erscheinenden Seite werden sämtliche Methoden des Webservices aufgelistet. Ein Klick auf eine dieser Methoden verrät schlussendlich, wie die Methode aufgerufen werden muss. Im Beispiel der Methode HelloWorld sieht die Ausgabe für eine HTTP Post Anfrage zum Beispiel wie folgt aus:

Beschreibung des POST Aufrufs der Webservice Methode

Für die Methode Echo wird folgende Ausgabe produziert:

4_echo_http_post

Im Vergleich zu unserer bisherigen Vorgehensweise beim dynamischen Nachladen der statischen Datei fällt auf, dass die Methode, über die die Anfrage gestartet wird, nun nicht mehr GET ein kann, sondern POST sein muss.

Außerdem wird nun auch ein bestimmter Content-Type, nämlich application/x-www-form-urlencoded benötigt. Speziell bei der Methode Echo fällt auf, dass außerdem nun auch Daten mit an den Server gesendet werden müssen.

Um die veränderten Anforderungen abdecken zu können, erweitere ich das bestehende JavaScript um eine neue Funktion:

    1         function postAjaxRequest(url, data) {

    2             if (xmlHttp) {

    3                 xmlHttp.open("POST", url, true);

    4                 xmlHttp.onreadystatechange = requestFinished;

    5                 xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    6                 xmlHttp.send(data);

    7             }

    8         }

 

Die neuen Funktion, postAjaxRequest ähnelt der vorherigen Funktion sendAjaxRequest. Die veränderten Anforderungen ließen sich relativ einfach integrieren. In Zeile 3 wurde aus einem GET ein POST. In Zeile 5 wird der zusätzlich benötigte Content-Type gesetzt. In Zeile 6 wird der Methode send nun nicht mehr fix null als Datenparameter übergeben. Stattdessen wird das neue Argument data weiter gereicht.

Der HTML Code musste auch nur unwesentlich erweitert werden. Er erhält zwei neue Links zum Aufruf der beiden Webmethoden:

    1             <a href="#" onclick="postAjaxRequest('AjaxDemoService.asmx/HelloWorld', null);">Hier

    2                 für Hello World WebService klicken </a>

    3             <br />

    4             <a href="#" onclick="postAjaxRequest('AjaxDemoService.asmx/Echo', 'number=1');">Hier

    5                 für Echo WebService klicken </a>

Das Ergebnis sieht anschließend wie folgt aus:

Abbildung zeigt den Rückgabewert des Hello World Webservices Abbildung zeigt die Rückgabe des Echo Web Services

Fazit

Ajax in eine Webanwendung zu integrieren ist aus technischer Sicht nicht sonderlich schwierig. Der vorliegende Blog Post hat gezeigt, dass sich selbst ohne "magische" Frameworks mit wenigen Zeilen JavaScript Ajax Funktionalitäten integrieren lassen. Natürlich ist die gezeigte Implementierung nur sehr rudimentär. So wurde das zurückgegeben XML des Webservices zum Beispiel noch nicht geparsed und es fand außerdem so gut wie keine Fehlerbehandlung statt. Dieses in das Beispiel zu integrieren hätte den Rahmen eines Blogposts jedoch vollständig gesprengt. Außerdem gibt es genau aus diesen Gründen auch bereits fertige Frameworks, wie ASP.NET AJAX oder jQuery, die einem genau diese Detailarbeit abnehmen.

Ehe man ein solches Framework einsetzt, sollte man jedoch die Grundlagen dessen kennen, was hinter den Kulissen geschieht. Und genau diese Grundlagen haben wir in diesem Blog Post geschaffen.

Das vollständige Beispiel werde ich übrigens am Wochenende zum Download bereit stellen.


Kick it on dotnet-kicks.de
 
1/29/2010 - 12:51 AM | Comments [4] | Categories: .NET | Ajax | ASP.NET | DotNetGerman Bloggers
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)