MSDN Magazin > Home > Ausgaben > 2008 > November >  Datenpunkte: Die Cloudperspektive in Silverligh...
Datenpunkte
Die Cloudperspektive in Silverlight 2
John Papa

Codedownload verfügbar unter: MSDN Code Gallery (151 KB)
Code online durchsuchen
Dieser Artikel basiert auf einer Vorabversion von Silverlight 2. Änderungen an allen Informationen sind vorbehalten.
Entwickler, die mit Silverlight arbeiten, haben den Kopf in den Wolken, aber wer kann es ihnen verdenken? Das Abrufen von Daten für umfangreiche Internetanwendungen aus verschiedenen Webdiensten rückt immer mehr in den Mittelpunkt des Interesses. Silverlight-Anwendungen können mit ASMX-Webdiensten, WCF-Webdiensten (Windows Communication Foundation), REST-Diensten (Representational State Transfer) und den herkömmlichen XML-Diensten (POX) kommunizieren. Unabhängig davon, ob diese Dienste von Drittanbietern bereitgestellt werden oder benutzerdefinierte Dienste sind, die auf dem gleichen Server gehostet werden, auf dem sich die Silverlight-Anwendung befindet, kann Silverlight Daten anfordern und nutzen und sie zwischen der Silverlight-Clientanwendung und den Webdiensten übergeben.
Aus den mir zugesendeten Fragen wird deutlich, dass Entwickler mehr darüber wissen möchten, wie Dienste von Silverlight 2-Anwendungen aus aufgerufen werden, welche Unterschiede es beim Verwenden der WCF-, ASMX- und REST-Dienste gibt und wie die von diesen Diensten verfügbar gemachten Daten genutzt werden können. Diese Ausgabe von „Datenpunkte“ beschäftigt sich mit einigen dieser Fragen und veranschaulicht den Umgang mit Datendiensten.
Es gibt jede Menge von Drittanbieterwebdiensten, die eine Vielzahl von Diensten bieten, angefangen vom Zugriff auf ihre APIs bis hin zum Speichern clientspezifischer und benutzerdefinierter Daten. Im Folgenden wird das Verarbeiten und Übergeben von Daten zwischen Silverlight-Anwendungen und der Cloud-Dienste besprochen. Außerdem wird gezeigt, wie Daten mithilfe einer Reihe von XML-Analysebibliotheken genutzt werden können. Zum Beispiel können Sie XML-Dokumente öffnen und durchlaufen oder XML mithilfe von LINQ to XML abfragen. Dienste wie Flickr, Amazon, Twitter und Live Search machen APIs verfügbar, mit denen über verschiedene Webdienstverfahren kommuniziert werden kann. Im Folgenden werden einige dieser APIs besprochen, und es wird erläutert, wie Sie über REST- und SOAP-basierte Dienste mit ihnen kommunizieren können. Alle Beispiele in diesem Artikel sind sowohl in C# als auch in Visual Basic verfügbar und können von der MSDN Magazin-Website heruntergeladen werden.

Wie werden Webdienste in Silverlight 2 behandelt?
Dies ist für den Anfang eine sehr gute Frage, denn durch ihre Beantwortung wird deutlich, warum so viele Entwickler Silverlight 2 verwenden, um mit Webdiensten zu interagieren. In Silverlight 1.x gab es keine Unterstützung für Code auf der Basis von Microsoft .NET Framework, und es waren außerdem keine .NET-Steuerelemente enthalten. Mit Silverlight 2 wurden viele umfassende Features eingeführt, die früheren Einschränkungen ein Ende bereiteten. Silverlight 2 ermöglicht Ihnen, Code in C# oder Visual Basic zu schreiben und all Ihre vorhandene Erfahrung mit der .NET CLR zu nutzen. Obwohl die Bibliotheken in Silverlight nur einen Teil der gesamten .NET-Bibliotheken darstellen, ist in Silverlight 2 jede Menge Funktionalität vorhanden. Zum Beispiel sind die WebClient- und die HttpWeb­Request-Klassen von Silverlight aus zugänglich und können für die Interaktion mit webbasierten Diensten durch Aufrufen von URIs verwendet werden. Die Daten können dann mithilfe von XmlReader-Objekten oder durch LINQ to XML genutzt werden.
In Silverlight 2 wurde ein umfangreicher Satz von Features für das Weiterleiten von Daten zwischen Diensten hinzugefügt. Die folgenden Features sind neu in Silverlight 2:
  • Zugriff auf SOAP-basierte Webdienste über Proxyklassen
  • Zugriff auf REST-basierten Webdienst
  • Zugriff auf ADO.NET Data Services, einen REST-basierten Dienst, der Remote-LINQ-Abfragen ermöglicht
  • Abrufen von Ergebnissen aus Webdiensten sowohl mit JavaScript Object Notation (JSON) als auch mit XML
  • Unterstützung von Duplexkommunikation durch WCF (mithilfe von Serverpush)
  • Unterstützung des domänenübergreifenden Zugriffs entweder durch die client­accesspolicy.xml-Datei oder die crossdomain.xml-Datei
  • Verfügbarkeit domänenübergreifender Netzwerkunterstützung über HTTP und Sockets
  • Asynchron initiierte Webdienstaufrufe

Wie werden Daten von Diensten an Silverlight 2 übergeben?
Abbildung 1 zeigt mehrere Arten von Diensten, auf die Silverlight 2 zugreifen kann. Daten können zwischen Silverlight 2 und diesen Diensten als XML, JSON oder als Skalarwerte übertragen werden. SOAP-basierte Dienste ermöglichen ihre eigene Beschreibung, was wiederum die Beschreibung der verfügbar gemachten Daten ermöglicht. Ein Silverlight 2-Client kann auf die Daten von SOAP-basierten Diensten durch Erstellen eines Proxys für den Dienst zugreifen, der eine Klassenbeschreibung für die verfügbar gemachten Klassen generiert.
Abbildung 1 Zugriff auf Dienste von Silverlight aus
SOAP-basierte Dienste beschreiben sich selbst, sodass Silverlight 2-Clientanwendungen Daten an und von den Diensten übermitteln können, indem eine verfügbar gemachte Entität verwendet wird. Sowohl ASMX-Webdienste als auch WCF-Webdienste ermöglichen, dass Entitäten als Teil ihres Vertrags mit einem Clientverweis versehen werden. Der Clientverweis generiert eine Proxyklasse auf dem Client, der eine Definition für die verfügbar gemachten Klassen und Dienstmethoden enthält (für Silverlight 2-Clients werden alle Dienstmethoden in asynchrone Aufrufe umgewandelt).
Dienste, die sich nicht selbst beschreiben, z. B. POX- und REST-basierte Dienste, ermöglichen einer Clientanwendung, ihre Dienstmethoden aufzurufen und Daten im Skalarformat, als XML oder als JSON abzurufen. Diese Dienstarten machen keine Web Services Description Language (WSDL) verfügbar, und deshalb generiert die Clientanwendung keine Proxyklasse für sie. Die Dienste werden mithilfe eines URI durch Klassen wie WebClient oder HttpWeb­Request abgefragt.

Wie werden Daten von ASMX-Webdiensten genutzt?
Wie ASMX-Dienste beschreiben sich SOAP-basierte WCF-Webdienste durch WSDL. Wenn der Client dem Dienst einen Verweis hinzufügt, generiert er einen Clientproxy für den Dienst, was dem Client ermöglicht, dem Dienst Daten in ihrer systemeigenen Form zu übergeben (d. h. als Klassen). Beispielsweise wird eine Dog-Entität von einem SOAP-basierten Webdienst (ASMX oder WCF) an einen Silverlight 2-Client zurückgegeben. Der Silverlight 2-Client kann dann eine Instanz der Dog-Klasse erstellen. Methoden werden den Clients in ASMX-Webdiensten verfügbar gemacht, indem die öffentlichen Dienstmethoden mit dem WebMethod-Attribut versehen werden und die ASMX-Webdienstklasse mit dem WebService-Attribut ausgestattet wird. Abbildung 2 zeigt dies in C#.
[WebService(Namespace = "http://www.microsoft.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class TestService : System.Web.Services.WebService {
  [WebMethod]
  public List<Dog> GetDogList() {
   return new List<Dog> {new Dog {Name = "Spot", Age = 10},      new Dog {Name = "Kadi", Age = 13}};
  }
}

[Serializable]
public class Dog {
  public string Name { get; set; }
  public int Age { get; set; }
}
Dieses Beispiel zeigt den auf .NET abzielenden Code in einer Silverlight 2-Clientanwendung, die den Proxy für den SOAP-Dienst generiert. Ein Ereignishandler wird dem GetDogListCompleted-Ereignis zugewiesen, gefolgt vom asynchronen Dienstaufruf. Alle von Silverlight 2 ausgehenden Webdienstaufrufe erfolgen asynchron, um zu verhindern, dass die Dienste den Benutzeroberflächenthread blockieren und Benutzeraktionen sperren. Hier sehen Sie den Ereignishandler, der die Ergebnisse erhält, als ein Array des Entitätstyps:
TestServiceSoapClient proxy = new TestServiceSoapClient();
proxy.GetDogListCompleted +=   new EventHandler<GetDogListCompletedEventArgs>(proxy_  GetDogListCompleted);
proxy.GetDogListAsync();
Es folgt der C#-Code für den Empfang der Entitäten:
void proxy_GetDogListCompleted(object sender,   
  GetDogListCompletedEventArgs e)
{
  Dog[] list = e.Result;
}

Wie werden Daten von WCF-Webdiensten genutzt?
So wie ASMX-Dienste beschreiben sich WCF-Webdienste durch WSDL. WCF-Webdienste machen ihre Dienste und die Daten, die sie übergeben können, durch Verwendung einer Reihe von Attributen verfügbar. Die Clientanwendungen können somit auf einen WCF-Webdienst verweisen und eine Proxyklasse generieren, um mit den Diensten und ihren verfügbar gemachten Datenverträgen zu interagieren.
WCF definiert einen Datenvertrag als einen benutzerdefinierten .NET-Typ, der von einem WCF-Webdienst zurückgegeben werden kann. Diese Typen sind mit dem DataContract-Attribut versehen. Die Eigenschaften der Klasse müssen mit dem DataMember-Attribut versehen werden. Abbildung 3 zeigt eine Beispielklasse, die mit den geeigneten Attributen versehen ist.
[DataContract]
public class Employee {
  int _employeeID;
  string _firstName;
  string _lastName;
  string _title;
  DateTime _hireDate;
  byte[] _photo;

  [DataMember]
  public int EmployeeID {
    get { return _employeeID; }
    set { _employeeID = value; }
  }
  [DataMember]
  public string FirstName {
    get { return _firstName; }
    set {_firstName = value; }
  }
  [DataMember]
  public string LastName {
    get { return _lastName; }
    set { _lastName = value; }
  }
}
Wenn die Datenformate für den WCF-Webdienst mithilfe der DataContract- und DataMember-Attribute definiert wurden, müssen der Dienst und die entsprechenden Dienstmethoden eingerichtet werden. Die Dienste werden mit dem ServiceContract-Attribut und die jeweiligen Methoden mit dem OperationContract-Attribut versehen. Bei Bedarf kann der Dienst eine Schnittstelle implementieren. In diesem Fall werden die Schnittstelle (und nicht die Dienstklasse) und die Schnittstellenmethoden mit den Attributen versehen. Hier sehen Sie IEmployeeService und die entsprechenden Mitglieder, die mit diesen Attributen versehen sind:
   [ServiceContract(Namespace = "")]
   public interface IEmployeeService
   {
    [OperationContract]
    List<Employee> FindEmployees();
   }
Beachten Sie, dass Silverlight 2-Anwendungen nur SOAP-basierte WCF-Dienste nutzen können, die basicHttpBinding verwenden.
Eine Silverlight 2-Clientanwendung kann diesem Dienst einen Dienstverweis hinzufügen, der eine Proxyklasse erstellt. Der Dienst kann dann asynchron, so wie mit einem ASMX-Webdienst, aufgerufen werden, und ein Handler für die Erfassung der Ergebnisse eingesetzt werden. Der Code, mit dem der Proxy implementiert wird, und der abgeschlossene Ereignishandler verwenden genau das gleiche Format, wie weiter oben für ASMX-Webdienste aufgeführt. Nachdem die Daten als eine Liste<Mitarbeiter> zurückgegeben werden, können die Entitäten ggf. durchlaufen, an Daten gebunden, geändert und durch eine Dienstmethode zurückgegeben werden.

Wie werden Daten von einem REST-Webdienst empfangen?
REST-Dienste können mithilfe einer Abfragezeichenfolge mit Parametern durch einen URI aufgerufen werden. Dieser kann von Silverlight 2 mithilfe der Web­Client-Klasse oder HttpWebRequest aufgerufen werden. Aufrufe durch WebClient sind einfacher, aber HttpWebRequest bietet mehr Kontrolle darüber, wie die Anforderungen erfolgen. Außerdem gibt WebClient auf dem Benutzeroberflächenthread zurück, während HttpWebRequest auf dem Hintergrundthread zurückgibt. Beim Rückruf von HttpWebRequest muss der Verteiler zur Interaktion mit der Benutzeroberfläche verwendet werden. Der folgende Code zeigt. wie System.Net.WebClient neue Artikel vom Digg-Dienst abruft:
string baseUri = "http://services.digg.com/stories/topic";
string topic = txtTopic.Text;
string appKey = "http%3A%2F%2Fwww.microsoft.com";
int count = int.Parse(txtTopicCount.Text);
string url = String.Format("{0}/{1}?appkey={2}&count={3}", baseUri, topic, appKey, count);
WebClient svc = new WebClient();
svc.DownloadStringAsync(new Uri(url));
Hier werden die Daten vom WebClient-Aufruf als reines XML zurückgegeben, das dann analysiert oder abgefragt werden kann. Wenn der REST-Dienst Parameter enthält, können sie als Teil der Abfragezeichenfolge übergeben werden.

Wie lässt sich XML abfragen?
Wenn ein REST-basierter oder ein POX-Dienst aufgerufen wird, kann er Daten als XML zurückgeben (REST-Dienste können Daten außerdem als JSON zurückgeben). Das XML kann dann mit einer Reihe von XML-Analysebibliotheken analysiert werden. Das XML kann jedoch auch mit LINQ to XML abgefragt werden, das eine reichhaltige Abfrageschnittstelle zum Extrahieren von Daten aus XML-Strukturen bietet, ohne komplexe XML-Hierarchien durchlaufen zu müssen.
Der REST-Dienst im letzten Beispiel gibt reines XML vom Digg-Dienst zurück, der die neuesten Artikel anzeigt. Dieses XML kann dann mit einer XML-Bibliothek analysiert werden, oder es kann mit LINQ to XML abgefragt werden (siehe Abbildung 4). Die Parse-Methode der X­Document-Klasse kann das XML des weiter oben angegebenen REST-Diensts nutzen. Das XML kann dann unter Verwendung von LINQ to XML abgefragt werden. Der Code in Abbildung 4 veranschaulicht die LINQ to XML-Syntax für das Abfragen des XML, das in Abbildung 5 angezeigt wird.
XDocument xml = XDocument.Parse(rawXml);
var storiesQuery = from story in xml.Descendants("story")
  select new DiggStory
    {
       Id = (int)story.Attribute("id"),
       Title = ((string)story.Element("title")).Trim(),
       Description = ((string)story.Element("description")).Trim(),
       ThumbNail =
         (story.Element("thumbnail") == null
              ? string.Empty
              : story.Element("thumbnail").Attribute("src").Value),
       Link = new Uri((string)story.Attribute("link")),
       DiggCount = (int)story.Attribute("diggs")
    };
<?xml version="1.0" encoding="utf-8" ?>
<stories timestamp="1222547017" min_date="1219955010" total="2541"
                                                 offset="0" count="10">
  <story link="http://news.msn.com.us/2/hi/technology/7540282.stm"    
       submit_date="1222545893" diggs="1" id="8725656" comments="0" 
       href="http://digg.com/microsoft/Microsoft_releases_SQL_Server_
       2005_recently" status="upcoming" media="news">
    <description>Microsoft has released SQL Server 2005 recently
       as an upgrade to the popular SQL Server database.</description>
    <title>Microsoft releases SQL Server 2005</title>
    <user name="D3a1i0" icon="http://digg.com/img/udl.png" 
      registered="1104422772" profileviews="5616" />
    <topic name="Microsoft" short_name="microsoft" />
    <container name="Technology" short_name="technology" />
    <thumbnail originalwidth="226" originalheight="170" contentType=
      "image/jpeg" src="http://digg.com/microsoft/Microsoft_releases_SQL_
      Server_2005_recently/t.jpg" width="80" height="80" />
  </story>
  <story link="http://navigatetrends.blogspot.com/2008/09/microsofts-
      virtual-receptionist.html" submit_date="1222545326" diggs="1"
      id="8725560" comments="0" href="http://digg.com/microsoft/
      Microsoft_s_virtual_receptionist" status="upcoming" media="videos">
    <description>Microsoft's virtual receptionist</description>
    <title>Microsoft's virtual receptionist</title>
    <user name="billarunk" icon="http://digg.com/img/udl.png" 
      registered="1216563445" profileviews="341" fullname="bill.arunk"
    />
    <topic name="Microsoft" short_name="microsoft" />
    <container name="Technology" short_name="technology" />
    <thumbnail originalwidth="400" originalheight="254" contentType="image/
      jpeg" src="http://digg.com/microsoft/Microsoft_s_virtual_
      receptionist/t.jpg" width="80" height="80" />
  </story>
...
...
</stories>
Die Abfrage startet bei den Artikeln bzw. bei der Artikelhierarchie und fragt alle Artikelelemente ab. Für dieses XML ist kein Namespace angegeben. Die Namespace-Variable (ns) ist aber hier aufgeführt, um zu zeigen, wie alle XML-Pfade mit einem Präfix versehen werden, falls die Ergebnisse auf einen Namespace verweisen. Die Steuerung von Artikelelementen wird durch die Verwendung der Descendants-Methode der XDocument-Instanz erreicht. Die Artikelelementeigenschaften können dann mithilfe der Artikelvariable und ihrer Element-Methode abgerufen werden. Zum Beispiel erfasst die folgende Codezeile den Wert des Titelelements vom Artikelelement und legt ihn in eine Eigenschaft namens „Title“:
Title = ((string)story.Element("title")).Trim()
Die LINQ to XML-Abfrage kann die Ergebnisse durch eine Projektion oder in eine Klassenstruktur zurückgeben. Der in Abbildung 4 gezeigte Code erstellt eine DiggStory-Klasse, um die Ergebnisse der einzelnen Statuselemente zu speichern, die von den XML-Daten abgerufen werden. Aufgeführt ist die vom Entwickler definierte DiggStory-Klasse, in der alle Eigenschaften (der Einfachheit halber) als Zeichenfolge definiert wurden. LINQ to XML könnte außerdem andere Aspekte enthalten, z. B. die Sortierung oder Filterung von Kriterien, falls erforderlich.
Das Verwenden von LINQ to XML für die Abfrage und Analyse von XML-Daten von REST- oder POX-Diensten ermöglicht Entwicklern, ihre bereits vorhandenen Kenntnisse anderer LINQ-Formulare zu nutzen, da sich die Syntax sehr ähnelt. Vielleicht besteht der größte Vorteil beim Verwenden von LINQ to XML für die Datenanalyse in den leistungsstarken und einfachen Datenabfragen, ohne Elemente in geschachtelten foreach-Schleifen durchlaufen zu müssen.

Welche Rolle spielen domänenübergreifende Richtlinien beim Zugriff auf Drittanbieterwebdienste?
Wenn eine Silverlight 2-Anwendung einen Webdienst aufruft, hängt der Aufruf von einer Überprüfung einer domänenübergreifenden Richtliniendatei auf dem Server des Webdiensts ab. Diese Überprüfung erfolgt, wenn die Silverlight-Anwendung auf einer anderen Domäne gehostet wird als der Webdienst. Zum Beispiel zeigt Abbildung 6, dass eine Silverlight 2-Anwendung, die auf dem Webserver johnpapa.net gehostet wird, einen Webdienst ebenfalls auf johnpapa.net aufrufen kann, da sich beide in derselben Domäne befinden. Wenn jedoch der auf johnpapa.net gehostete Silverlight 2-Client einen Webdienst auf microsoft.com aufrufen will, vergewissert sich Silverlight 2, dass die domänenübergreifende Richtliniendatei auf microsoft.com existiert, bevor der Aufruf zugelassen wird.
\\msdnmagtst\MTPS\MSDN\issues\en\08\11\Papa - DataPoints.1108\figures\fig06.gif
Abbildung 6 Domänenübergreifende Überprüfung
Weitere Informationen zu domänenübergreifenden Richtlinien erhalten Sie in meinem Artikel Dienstgesteuerte Anwendungen mit Silverlight 2 und WCF in der Ausgabe vom September 2008. In diesem Artikel werden die Dateiformate und die Funktionsweise von Richtlinien behandelt.
Unabhängig davon, ob ein WCF-Webdienst, ein REST-basierter Dienst oder eine Art von Webdienst aufgerufen wird, Silverlight 2 ermöglicht nur domänenübergreifende Aufrufe, wenn der Zielwebserver den Aufruf durch seine domänenübergreifende Richtliniendatei ermöglicht. Zum Beispiel werden mit dem Code, der weiter oben aufgeführt ist,
string baseUri = "http://services.digg.com/stories/topic";
string topic = txtTopic.Text;
string appKey = "http%3A%2F%2Fwww.microsoft.com";
int count = int.Parse(txtTopicCount.Text);
string url = String.Format("{0}/{1}?appkey={2}&count={3}", baseUri,  
  topic, appKey, count);
WebClient svc = new WebClient();
svc.DownloadStringAsync(new Uri(url));
die Digg-Webdienste aufgerufen, um auf die neuesten Artikel zuzugreifen. Digg enthält eine domänenübergreifende Richtliniendatei, die das Flash-Format am Speicherort http://services.digg.com/crossdomain.xml verwendet.
Der Inhalt dieser domänenübergreifenden Digg-Datei zum Zeitpunkt der Abfassung dieses Artikels ist im Folgenden aufgeführt:
<cross-domain-policy>
  <allow-access-from domain="*"/>
</cross-domain-policy>
Beachten Sie, dass die domänenübergreifende Digg-Datei Aufrufe von einer beliebigen Domäne ermöglicht. Dies könnte jedoch eingeschränkt werden, ähnlich wie bei den Einschränkungen durch eine domänenübergreifende Twitter-Datei. Beachten Sie die domänenübergreifende Twitter-Datei im folgenden Codebeispiel:
<?xml version="1.0" encoding="UTF-8" ?> 
<cross-domain-policy  
  xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:
  noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/ 
  PolicyFile.xsd">
  <allow-access-from domain="*.twitter.com" /> 
  <site-control permitted-cross-domain-policies="master-only" /> 
  <allow-http-request-headers-from domain="*.twitter.com" /> 
</cross-domain-policy>
Durch die domänenübergreifende Datei für Twitter werden alle Aufrufe auf die Aufrufe eingeschränkt, die von der *.twitter.com-Domäne stammen. Ihre Silverlight 2-Anwendung wird wahrscheinlich nicht auf der twitter.com-Domäne gehostet. Daher kann ein Silverlight-Client keine Webdienstanforderung direkt an die REST-basierten Dienste von Twitter senden. Hier kann Ihnen ein Proxydienst aushelfen. Zum Beispiel könnten Sie einen auf Ihrer Domäne gehosteten WCF-Webdienst erstellen, der die twitter.com-Webdienste aufruft. Die von Ihren WCF-Diensten ausgehenden Aufrufe übertragen die Informationen zwischen der Silverlight-Anwendung und Twitter. Der auf dem Server ausgeführte WCF-Dienst ist von den domänenübergreifenden Richtlinieneinschränkungen, denen ein Silverlight-Client unterliegt, nicht betroffen und kann deshalb die Aufrufe ausführen. Eine andere Option besteht darin, die Aufrufe durch einen Dienst wie Popfly oder die Clouddienste von Yahoo zu übertragen.

Senden Sie Fragen und Kommentare für John Papa in englischer Sprache an mmdata@microsoft.com.

John Papa (johnpapa.net) ist leitender Berater bei ASPSOFT (aspsoft.com). Als leidenschaftlicher Baseballfan feuert er an den Sommerabenden zusammen mit seiner Familie die Yankees an. Er ist C#-MVP und INETA-Referent, hat mehrere Bücher verfasst und arbeitet derzeit an seinem aktuellen Buch Data Access with Silverlight 2. John Papa hält häufig Vorträge auf Konferenzen wie DevConnections und VSLive.

Page view tracker