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)

Von Administratoren geliebt, von Entwicklern verflucht: Das Transportsystem von SAP. Prinzipiell kann man sich das so vorstellen, dass dieses System vorsieht, dass der Quellcode nur an einer Stelle, nämlich im SAP Entwicklungssystem erstellt und modifiziert wird.

Anschließend wird ein sogenannter Transport (so was ähnliches wie ein Setuppaket) erstellt. Dieses kann dann je Konfiguration erst mal nur in das Testsystem eingespielt werden. Dort wird das Paket auf seine Funktionstüchtigkeit getestet. Wurden diese Tests bestanden, wird es in das Integrationssystem "transportiert". Dort wird das Zusammenspiel mit anderen Anpassungen getestet, es wird also geprüft, ob das Paket nichts anderes kaputt macht ;-)

War auch dieser Test erfolgreich, wird der Transport aus dem Integrationssystem in das Produktionssystem transportiert. Ein direktes Einspielen von Transporten in das Produktionssystem beziehungsweise sogar ein direktes Ändern der Quellcodes ist nicht vorgesehen.

Auch wenn diese Vorgehensweise zunächst recht streng, bürokratisch und viel zu aufwändig wirkt, hat sie durchaus ihren Sinn. Dieser zugegeben etwas starre Prozess sorgt dafür, dass SAP Produktionssysteme normalerweise immer stabil und möglichst fehlerfrei sein sollten. Er trägt also Maßgeblich zu SAPs gutem Ruf bei!

Warum erzähle ich das eigentlich?

Ok, nette Einführung, aber ist das hier nicht eigentlich ein auf .NET fokussierter Blog? Ja :-) Also keine Sorge, ich bin nicht ins Lager der SAP Consultants gewechselt ;-) Allerdings habe ich von ungefähr 2005 bis 2008 die SAP Schnittstelle meines ehemaligen Arbeitgebers gemeinsam mit einem Kollegen weiterentwickelt. Da blieb der ein oder andere Blick unter die Haube diverser SAP Systeme nicht aus.

Nun aber zurück zum Thema:

In meiner beruflichen Laufbahn war ich an einigen (ASP).NET Projekte beteiligt, die von einem SAP transportähnlichen System sehr profitiert hätten. Überspitzt gesagt sah es dort nämlich häufig so aus, dass ein Entwickler nach einer Quellcodeänderung Visual Studio genutzt hat um einen neuen Releasestand zu erstellen und diesen dann schnell auf den produktiven Webserver gepackt hat. Selbstverständlich wurde das ganze vorher nicht in einem Test- oder Integrationssystem installiert und unter Umständen wusste der Kunde und/oder Projektleiter sogar nichts von dem adhoc Update.

Agil != Chaos !!!

Der Entwickler handelte unter dem Deckmantel der agilen Entwicklung nach dem Prinzip "nach mir die Sinnflut", einfach drauf damit, wird schon klappen. In diesem Zusammenhang finde ich es übrigens immer wieder äußerst verwunderlich, wie viele Entwickler unkoordiniertes und schlampiges Vorgehen einfach als agil bezeichnen, um ihr Chaos damit zu entschuldigen. Agil ist allerdings alles andere als chaotisch, unprofessionell, unkoordiniert oder schlampig. Aber gut, dass ist ein anderes Thema - ich schweife schon wieder aus ;-)

In einem meiner aktuellen Projekte wollte ich einer solchen Vorgehensweise direkt einen Riegel vorschieben. Nicht dass die Entwickler dieses Projekts chaotisch wären, das ist ganz und gar nicht der Fall, aber wir wollen ja auch niemanden in Versuchung führen ;-)

Ich hatte einen Traum ...

Meine Ziele lautete also wie folgt:

  1. Releasestände einer Software sollen automatisch auf einem dedizierten Buildrechner und nicht auf einem Entwickler-PC erstellt werden.
  2. Jedes Release hat eine eindeutige Versionsnummer. Kompilate auf dem Entwicklerrechner haben jedoch immer die Nummer 1.0.0.0 (dazu später mehr)
  3. Jedes Release soll ein Set an Unit Tests durchlaufen haben.
  4. Jedes Release soll mit einer bestimmten Qualität als Metadatum versehen werden.
  5. In Abhängigkeit von dieser Qualität und der vorherigen Qualität soll die Software bei bestimmten Statusübergängen automatisch auf bestimmte Systeme verteilt werden.
  6. Das Qualitätsmetadatum wird durch einen dedizierten Personenkreis gesetzt (zum Beispiel QM-Manager oder Projektmanager, Softwarearchitekt oder Chefentwickler)

Realisiert werden sollte das ganze mit Visual Studio 2008 und dem Team Foundation Server. Die Punkte 1 - 3 stellen in einer solchen Umgebung kein Problem dar.

Punkt Nr. 1 erledigen wir durch Teambuild. Hier läuft ein Buildagent auf einem dedizierten Rechner. Durchgeführt werden sowohl Continuous Integration Builds, als auch Nightly Builds.

Punkt Nr. 2 haben wir durch ein einfaches Build Target in unserem Buildscript erledigt. Die Versionsnummer haben wir wie folgt aufgebaut:

  • Major: Fixer Wert
  • Minor: Fixer Wert
  • Build: Aktueller Changeset des Teamprojekts im TFS
  • Revision: Fortlaufende Nummer

Diese Nummer wird nur während des Builds durch Teambuild erzeugt. Entwicklerbuilds haben immer die Version 1.0.0.0. So fange ich ab, dass ein Entwickler doch einfach mal auf seinem Rechner eine Version baut und versucht zu deployen. Natürlich ist dies nur ein weicher Schutz. Mit ein wenig Aufwand lässt sich das System natürlich leicht umgehen indem ein Entwickler eine korrekte Versionsnummer erzeugt. Allerdings ist der Aufwand schon wieder so hoch, dass sich dann doch lohnt, den vorgesehenen Prozess einzuhalten ;-)

Sollte jemand Interesse an diesem Teil des Buildscripts haben: Einfach einen Kommentar posten.

Punkt Nr. 3 ist natürlich auch kein Problem. Aufgrund der guten VS Integration haben wir uns für MS Test für dieses Projekt entschieden. Diese Tests als Bestandteil des Buildprozess laufen zu lassen war demnach kein Problem.

Punkt Nr. 4 ist auch bereits von Haus aus mit dem TFS möglich. Jeder Build der durch Teambuild durchgeführt wird hat ein Metadatum Buildqualität. Die möglichen Werte der Buildqualität lassen sich frei definieren. Auch lässt sich über entsprechende Rechte einschränken, wer diese Qualität setzen darf. Wir haben bei uns die Werte "Entwicklung", "Test", "Integration", "Produktion" und "Abgelehnt" definiert.

Punkt Nr. 5 kann mit TFS Bordmitteln erst mal nicht realisiert werden. Hier kommt nun das Open Source Projekt TFS Deployer ins Spiel. TFS Deployer installiert man als Windowsdienst. Nach entsprechender Konfiguration lauscht er nun auf eine Änderung der Buildqualität. Dazu muss der Benutzer unter dem der Dienst läuft allerdings in der TFS Gruppe Valid Users sein und für das entsprechende Temaprojekt zumindest Leserechte haben. Bemerkt TFS Deployer nun einen Statusübergang, für den er ein Deploymentscript hat, führ er dieses automatisch aus.

Angelehnt an die Einführung von Dave Comfort und Scott Colestock kann man sich das ganze nun wie folgt vorstellen:

tfsdeployer

Konkret sieht dies so aus, dass innerhalb der TFS Quellcodeverwaltung unterhalb des Verzeichnisses der Builddefinition ein Verzeichnis Deployment angelegt wird, also zum Beispiel $/TeamProjekt/TeamBuildTypes/MeinProjektNightlyBuild/Deployment. In diesem Verzeichnis wird nun die Datei DeploymentMappings.xml angelegt. In dieser Datei wird für jeden gewünschten Statusübergang festgelegt, welches Powershellscript auf welchem Computer ablaufen soll. Die Datei DeploymentMappings kann zum Beispiel wie folgt aussehen:

<DeploymentMappings xmlns="http://www.readify.net/TFSDeployer/DeploymentMappings20061026">
  <Mapping xmlns=""
           Computer="tfsbuildserver"
           OriginalQuality="*"
           NewQuality="Entwicklung"
           Script="EntwicklerDrop.ps1"
           RunnerType="PowerShell"
           NotificationAddress="someone@mycompany.com;someoneelse@mycompany.com" />
  <Mapping xmlns=""
           Computer="tfsbuildserver"
           OriginalQuality="Entwicklung"
           NewQuality="Test"
           Script="EntwicklungNachTest.ps1"
           RunnerType="PowerShell"
           NotificationAddress="someone@mycompany.com;someoneelse@mycompany.com" />
  <Mapping xmlns=""
           Computer="tfsbuildserver"
           OriginalQuality="Test"
           NewQuality="Integration"
           Script="TestNachIntegration.ps1"
           RunnerType="PowerShell"
           NotificationAddress="someone@mycompany.com;someoneelse@mycompany.com" />
  <Mapping xmlns=""
           Computer="tfsbuildserver"
           OriginalQuality="Integration"
           NewQuality="Produktion"
           Script="TestNachIntegration.ps1"
           RunnerType="PowerShell"
           NotificationAddress="someone@mycompany.com;someoneelse@mycompany.com" />
</DeploymentMappings>

In diesem Script werden vier Statusübergänge definiert. Erst vom leeren Zustand nach Entwicklung, dann weiter nach Test, Integration und Produktion. Jeder dieser Status entspricht einem Build-Quality Eintrag im TFS. Diese sind wie angemerkt frei definierbar. Über die Eigenschaft Script wird nun definiert, welches Powershellscript ausgeführt werden soll. Diese Powershellscripte müssen übrigens im selben Ordner liegen, wie die Datei DeploymentMappings.xml.

Tritt nun einer der definierten Statusübergänge auf, ruft TFS Deployer das gewünschte Script im Kontext des Service Users auf und übergibt ein BuildData Objekt. Die verschiedenen des Eigenschaften BulidData Objekts können dann über die Variable $TfsDeployerBuildData zugegriffen werden.

In unserer Umgebung nutzen wir übrigens eine modifzierte Variante des Beispiels der Seite Team Foundation Server Build Recipes.

function publish-site( [string] $sourcepath, [string] $destinationpath, [bool] $renameconfig, [bool] $deleteexisting) {
	$droplocation = $TfsDeployerBuildData.DropLocation
	
	$websourcepath = $droplocation + $sourcepath
	$webdestinationpath = $destinationpath
	
	new-item -force -path $webdestinationpath -itemtype "directory"
	if ($deleteexisting) {
		get-childitem $webdestinationpath | remove-item -force -recurse
	}
	get-childitem $websourcepath | copy-item -force -recurse -destination $webdestinationpath

	if ($renameconfig) {
		$configFile = $webdestinationpath + "web.production.config"
		remove-item $configFile -force
		$configFile = $webdestinationpath + "web.development.config"
		remove-item $configFile -force
		$configFile = $webdestinationpath + integration.config"
		remove-item $configFile -force


		$configFile = $webdestinationpath + "web.test.config"
		$configFileDest = $webdestinationpath + "web.config"
		move-item $configFile $configFileDest -force
	}
}

publish-site "\Release\_PublishedWebsites\MyWebApplication\" "\\MeinWebServer\MeineFreigabe\" 1 1

Dies funktioniert in unserer Umgebung recht gut, da wir vom Build Server direkten Zugriff auf die verschiedenen Web Server (Entwicklung, Test, Integration, Produktion) haben. Für jede Webapplikation besteht auf den Servern eine Windows Freigabe, die direkt auf das Wurzelverzeichnis der Applikation zeigt. Zugriffsrechte hat hier nun der Benutzer, unter der TFS Deployer Dienst läuft. So stellen wir sicher, dass keiner der Entwickler "mal schnell" etwas patcht ;-) Noch sicherer wäre es natürlich gewesen, den TFS Deployer Dienst direkt auf dem Web Server laufen zu lassen, und ihm lesenden Zugriff auf die erstellen Binaries des Buildprozesses zu geben.

Als Erweiterung zum Standardscript wärmen wir nach dem Deployment Vorgang die Applikation noch auf, indem wir einige Seiten ansurfen. Somit ist sichergestellt, dass der Workerprozess beim ersten Zugriff durch einen Benutzer schon da ist und die entsprechenden Seiten auch schon kompiliert wurden.

Den Übergang von "Nichts" zu "Entwicklung" machen wir übrigens automatisch als letzten Schritt unseres Buildscripts. Somit wird der Nightly Build Automatisch in die Entwicklungsumgebung gejagt.

Und was hat man nun davon?

Mit dem TFS Deployer haben Nutzer des TFS eine einfache Möglichkeit, benannte Softwarestände automatisch in verschiedene Umgebungen zu verteilen. Die dadurch entstehende Nachvollziehbarkeit lässt aus meiner Erfahrung Kunden und somit auch die Projektverantwortlichen im internen Team besser schlafen.

Ein wenig schade ist, dass man innerhalb des TFS zwar verschiedene Buildqualitäten, jedoch keine Regeln für gültige Übergänge anlegen kann. Da unsere Scripte jedoch auf festen Übergängen definieren bedeutet dies, dass derjenige, der die Buildqualität setzt, genau wissen muss welche Übergänge gültig sind. Schaltet er nämlich zum Beispiel von Test direkt auf Produktion passiert nämlich nichts.

Angeregt zu diesem Blog Post hat mich übrigens Robert Mühsigs Posts Build / Deployment / WTF.


Kick it on dotnet-kicks.de
 
3/25/2010 - 07:11 AM | Comments [7] | Categories: .NET | ASP.NET | DotNetGerman Bloggers | TFS
© Andre Kraemer | RSS/Subscribe Feed your aggregator (RSS 2.0)

Cool, mein Blogeintrag zum Thema Viewstate hat es auf die offizielle Microsoft ASP.NET Seite geschaft. Nun ja, zumindest auf die deutsche Variante davon :-)


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

Vor einiger Zeit habe ich ein kurzes Tutorial zur Bedienung der Windows Debugging Tools hier in meinem Blog veröffentlicht.

Dieses Tutorial traf auf großen Anklang. Zumindest wenn ich meinen Statistiken trauen darf. Denn laut diesen kommen die meisten Besucher durch eine Google Suche nach WinDbg auf mein Blog.

Um dieser Tatsache gerecht zu werden, habe ich mich dazu entschlossen das ursprüngliche Tutorial nun auch als Video bereit zu stellen. Ich hoffe damit auch Gregor zufrieden zu stellen, der meinte dass die geschriebene Variante doch etwas schwierig zu befolgen wäre ;-)

The Camtasia Studio video content presented here requires a more recent version of the Adobe Flash Player. If you are you using a browser with JavaScript disabled please enable it now. Otherwise, please update your version of the free Flash Player by downloading here.

Über Kommentare und Rückfragen würde ich mich sowohl hier auf meinem Blog, als auch unter der im Video angegebenen E-Mail Adresse sehr freuen.

War dieser Eintrag hilfreich für dich? Dann kicke ihn doch bitte.


Kick it on dotnet-kicks.de
 
3/19/2010 - 01:19 AM | Comments [10] | Categories: .NET | DotNetGerman Bloggers | Tutorials | WinDbg
© 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)

Eigentlich altbekannt, trotzdem bin ich gerade mal wieder darauf hereingefallen und schreibe des deshalb hier auf:

Ich wollte in einer ASP.NET Webforms Anwendung ein Servercontrol serverseitig über

meinControl.Visible = false;

ausblenden, um es nach einer Nutzeraktion auf dem Client wieder einzublenden:

$(Id$="MeinControl").show();

Leider funktioniert das allerdings nicht, da ein serverseitiges Visible = false dafür sorgte, dass das Control erst garnicht gerendert wurde und somit auf dem Client nicht verfügbar war.

Die simple Lösung im Codebehind:

meinControl.Style.Add(HtmlTextWriterStyle.Display, "none");

Dann klappts auch mit dem Einblenden ;-)

War dieser Beitrag hilfreich? Dann kick ihn doch bitte.


Kick it on dotnet-kicks.de
 
3/11/2010 - 07:41 PM | Comments [2] | Categories: .NET | 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)