Freigeben über


Cutting Edge

Verwalten der dynamischen Inhaltsbereitstellung in Silverlight, Teil 1

Dino Esposito

Codedownload verfügbar in der MSDN-Codegalerie
Code online durchsuchen

Inhalt

Die Größe von Silverlight-Anwendungen
Dynamisch generiertes XAML
Dynamisch generiertes XAP
On-Demand-Inhalt
Zwischenspeichern heruntergeladenen Inhalts
Ein Tool für Downloads
Herunterladen von Nur-XAML-Daten
Arbeiten mit XAP-Paketen
Verarbeiten von XAP-Inhalt
Zusammenfassung

Benutzer von Rich Internet Applications (RIAs) haben durchaus Grund zur Sorge wegen der Sicherheit und der Downloadgröße. Silverlight-Anwendungen werden von einer Teilmenge von Microsoft .NET Framework unterstützt und haben als solche das Potenzial, gefährliche Aktionen, die gegen den lokalen Benutzercomputer gerichtet sind, auszuführen. Aus diesem Grund hat das Silverlight-Team ein neues Sicherheitsmodell entwickelt, das es den Anwendungen unmöglich macht, eine sicherheitskritische Klasse in der zentralen CLR (die Silverlight-Edition von .NET Framework) aufzurufen. Weitere Informationen zum Silverlight-Sicherheitsmodell und zum Erstellen von Silverlight-Anwendungen finden Sie im Artikel Programmieren von Silverlight mit der CoreCLR bzw. in Einführung in das Erstellen tieferer Weberfahrungen.

Das Herunterladen des Silverlight-Plug-In ist kein Problem, da es nur einige Sekunden in Anspruch nimmt und weil es ein Vorgang ist, den Sie nur einmal ausführen. Wie sieht es aber mit der Größe der heruntergeladenen Anwendungen aus?

Im Artikel dieses Monats werde ich der Frage nach Downloads für Silverlight-Anwendungen auf den Grund gehen. Zuerst zeige ich, wie Sie XAML dynamisch generieren können. Danach erkläre ich, wie Anwendungsfeatures auf einer streng benutzerspezifischen Anforderungsbasis dynamisch aktiviert werden können. Einige anwendungsspezifische Features, die schwer in den Hauptdatenstrom des Codes zu implementieren sind, können danach separat implementiert werden, und können, was noch wichtiger ist, separat heruntergeladen und dennoch problemlos in die primäre Benutzeroberfläche integriert werden.

Die Größe von Silverlight-Anwendungen

Sobald ein Benutzer das Silverlight-Plug-In installiert hat, sind alle seine Systemassemblys, die eine Silverlight-Anwendung möglicherweise erfordern wird, vollständig. Dies bedeutet, dass der Download auf die Assembly, die die Anwendung enthält und auf alle anderen benutzerdefinierten Assemblys, auf die verwiesen wird, begrenzt ist. Schließlich befindet sich die Größe des Anwendungsdownloads oft im zweistelligen Kilobyte-Bereich. Beachten Sie, dass diese Schätzung für die RTM-Version von Silverlight 2 sowie für den im Veröffentlichungsmodus kompilierten Code gilt.

Natürlich kann die Anwendung einen viel größeren Speicherplatz beanspruchen, insbesondere wenn sie lange Algorithmen, grafische Inhalte und Medieninhalte oder Animationen enthält. Um mit großen Anwendungen arbeiten zu können, bei denen die Downloadzeit ein Problem darstellt, stehen Ihnen zwei Hauptoptionen zur Verfügung. Eine Möglichkeit besteht darin, den Silverlight-Inhalt entsprechend der Erläuterung auf silverlight.live.com zu streamen. Die andere Möglichkeit besteht darin, die Anwendung in unabhängige Stücke aufzuteilen, die separat und bei Bedarf heruntergeladen werden können.

Dynamisch generiertes XAML

Im Wesentlichen wurde das Silverlight-Plug-In zum Anzeigen des XAML-Inhalts entworfen. Wenn das XAML CodeBehind enthält, verarbeitet das Plug-In den Code, um die Benutzeroberfläche herzustellen und die codierten Verhaltensweisen oder Auswirkungen zu unterstützen. Auf das XAML kann direkt über eine URL verwiesen werden, wenn das alles ist, was Sie herunterladen möchten. Andernfalls können Sie über die XAP-Erweiterung auf ein Silverlight-Paket verweisen.

Ein XAP-Paket enthält ein Manifest und eine oder mehrere Assemblys. Eine der Assemblys enthält den Einstiegspunkt der Anwendung, andere Assemblys sind einfach Assemblys, auf die verwiesen wird. Das XAML für die Benutzeroberfläche ist in den Ressourcen der Einstiegspunktassembly gespeichert. Ein XAP-Paket wird von der Visual Studio 2008-Erweiterung für Silverlight 2 erstellt, wenn Sie das Projekt entwerfen und erstellen.

XAML- und XAP-Datenströme sind alles, womit das Silverlight-Plug-In umzugehen weiß. Um solche Inhalte herunterzuladen, müssen Sie jedoch nicht unbedingt das Plug-In auf eine physische XAML- oder XAP-Ressource auf dem Server verweisen. Sie können das Plug-In beispielsweise auf eine URL verweisen, die dynamisch generierte XAML- oder XAP-Inhalte zurückgeben kann.

In Abbildung 1 ist ein Beispiel für einen ASP.NET-HTTP-Handler dargestellt, der dynamisch erstelltes XAML zurückgibt. Die ProcessRequest-Methode legt den Inhaltstyp für das Antwortobjekt fest und schreibt danach den XAML-Inhalt aus, wie z. B. dynamisch verfasstes XAML, basierend auf Konfigurationsdaten, Parametern oder Laufzeitbedingungen. Durch Festlegen der Expires-Eigenschaft für das Response-Objekt können Sie auch das Zwischenspeichern der Ressource auf dem Client verhindern. Dies kann hilfreich sein, wenn der Inhalt, den Sie bedienen, sich regelmäßig ändert und aktualisiert werden muss.

Abbildung 1 HTTP-Handler, der XAML zurückgibt

<%@ WebHandler Language="C#" Class="XamlGenHandler" %>

using System;
using System.Web;

public class XamlGenHandler : IHttpHandler 
{
    public void ProcessRequest (HttpContext context) 
    {
        // Prevent caching of the response
        context.Response.Expires = -1;

        // Set the type of data we're returning
        context.Response.ContentType = "text/xaml";

        // Create some XAML and return it down the wire
        context.Response.Write("<Canvas xmlns=
            'https://schemas.microsoft.com/client/2007' " +
            "xmlns:x='https://schemas.microsoft.com/winfx/2006/xaml'>" +
            "<TextBlock Foreground='black' Padding='10' FontSize='20'>
             <Run>XAML content</Run><LineBreak/>" + 
            "<Run>[generated " + DateTime.Now.ToLongTimeString() + "]</Run>" +
            "</TextBlock></Canvas>");
    }

    public bool IsReusable 
    {
        get {return true;}
    }

}

Dynamisch generiertes XAP

Das Zurückgeben eines dynamisch generierten XAP-Pakets unterscheidet sich nicht viel vom Zurückgeben von reinem XAML-Text, außer dass ein XAP-Paket keine Nur-Text-Datei ist. Ein XAP-Paket ist eine ZIP-Datei, die ein XML-Manifest und eine oder mehrere Assemblys enthält. Mithilfe eines Paketformats hat das Team die Anzahl der Roundtrips minimiert, die notwendig sind, um den gesamten für eine Silverlight-Anwendung erforderlichen Inhalt herunterzuladen. In Abbildung 2 ist ein ASP.NET-HTTP-Handler dargestellt, der den Inhalt einer XAP-Datei in den HTTP-Antwortdatenstrom schreibt.

Abbildung 2 HTTP-Handler, der ein XAP-Paket zurückgibt

<%@ WebHandler Language="C#" Class="XapGenHandler" %>

using System;
using System.Web;

public class XapGenHandler : IHttpHandler 
{
    public void ProcessRequest (HttpContext context) 
    {
        // XAP file to return 
        string xapFile = "...";

        // Set the type of data we're returning
        context.Response.ContentType = "application/octet-stream";

        // Create some XAML and return it down the wire
        content.Response.WriteFile(xapFile);
    }


    public bool IsReusable 
    {
        get {return true;}
    }

}

Der Beispielcode liest die XAP-Daten aus einer vorhandenen Datei. Es versteht sich von selbst, dass, wenn Sie eine ZIP-Bibliothek in Ihr Projekt einbetten, Sie das Paket problemlos dynamisch assemblieren können, indem Sie verschiedene DLLs kombinieren und anschließend eine richtige XML-Manifestdatei erstellen.

Wenn Sie XAP-Inhalt zurückgeben, setzen Sie den Inhaltstyp der Antwort auf „application/octet-stream“ – den MIME-Typ, der normalerweise generischem binären Inhalt entspricht.

Zum Verknüpfen des Plug-In mit einem HTTP-Handler oder einem anderen von Ihnen ausgewählten Endpunkt, verwenden Sie die üblichen Silverlight-Programmierverfahren. Sie können beispielsweise das Silverlight-Serversteuerelement in einer ASP.NET-Seite verwenden:

<asp:Silverlight ID="Xaml1" runat="server" 
    Source="~/xap.ashx" 
    MinimumVersion="2.0.30523" 
    Width="100%" 
    Height="100%" />

In beiden Beispielen ist die Factory für die Silverlight-Anwendungen auf dem Webserver gespeichert. Dies ist ein hervorragender Ansatz, wenn die Hostseite dynamisch herausfinden muss, welcher Inhalt herunterzuladen ist.

Dies ist jedoch nur ein mögliches Szenario. Es gibt ein anderes Szenario, das wahrscheinlich üblicher ist – die Notwendigkeit, optionale Komponenten für die aktuelle Silverlight-Anwendung herunterzuladen. In diesem Fall wird die gesamte Logik zum Auswählen und Herunterladen externer Inhalte auf dem Client im Silverlight-Plug-In ausgeführt.

On-Demand-Inhalt

Silverlight 2 bietet eine umfassende und leistungsfähige API zum Herunterladen von Code und/oder XAML bei Bedarf, die Sie verwenden können, um den Inhalt herunterzuladen und in das vorhandene XAML-Dokumentobjektmodell einzufügen.

Alle visuellen Elemente einer XAML-Struktur besitzen eine Eigenschaft namens „Children“, die Sie verwenden können, um untergeordnete Elemente jeglicher Größe programmgesteuert hinzuzufügen oder zu entfernen. Zum Beispiel können Sie ein gesamtes Benutzersteuerelement anfügen, das vom gleichen Server oder sogar von einem vertrauenswürdigen, ausgewählten Remoteserver heruntergeladen wurde. In der folgenden Zeile ist ein Beispiel dargestellt:

StackPanel1.Children.Add(downloadedContent);

Das vom Argument repräsentierte Benutzersteuerelement wird der Children-Sammlung eines StackPanel-XAML-Elements hinzugefügt. Das Rendering wird unmittelbar ausgeführt, und die Benutzeroberfläche wird in Echtzeit aktualisiert.

Für Sie sind viel mehr Möglichkeiten vorhanden, als nur den Inhalt herunterzuladen und an das vorhandene Dokumentobjektmodell anzufügen. Beispielsweise können Sie den Inhalt lokal im lokalen Speicher der Anwendung zwischenspeichern und Ihren eigenen Cache für On-Demand-Inhalt überprüfen, bevor Sie eine neue Anforderung an den Server senden.

Dieser Ansatz gibt Ihnen permanenten Speicher für den heruntergeladenen Inhalt. In einigen Fällen kann dies jedoch übertrieben sein. Es gibt eine weitere, einfachere Möglichkeit, die keine zusätzliche Arbeit erfordert: Sie können den Browser dazu veranlassen, die XAP-Ressource für Sie zwischenzuspeichern.

Zwischenspeichern heruntergeladenen Inhalts

Das XAP-Paket, das Sie vom Webserver abrufen, hat keine besondere Bedeutung für den Browser. Der Browser speichert es deshalb so zwischen, wie er alles andere zwischenspeichert, was er vom Webserver erhält, wobei er sich an die Anforderungs-Cacherichtlinien hält, die durch das Cachesteuerelement und durch den „expires“-HTTP-Header in der Anforderung oder ähnliche Metatags in der Host-HTML-Seite festgelegt sind.

Beachten Sie, dass, wenn eine XAP-Ressource vorliegt, die im Browser heruntergeladen werden soll, Sie das Zwischenspeichern über die Einstellungen in der Seite steuern können, die Sie normalerweise verwenden, indem Sie Metatags oder ASP.NET-Direktivenattribute einfügen. Wenn die XAP-Ressource über einen HTTP-Handler wie im vorherigen Beispiel herunterzuladen ist, können Sie das Zwischenspeichern für die spezifische Anforderung steuern.

Ein weiterer Punkt, der beachtet werden sollte, ist, dass es sich um den ursprünglichen XAP-Inhalt handelt, einschließlich der Assemblys und des zwischengespeicherten XAML. Deshalb kann die ausgeführte Anwendung das ursprüngliche XAML programmgesteuert ändern. Solche Änderungen werden jedoch nicht automatisch zwischengespeichert, und ebenso werden sämtliche Ressourcen, die Sie möglicherweise aus einem XAP-Paket extrahieren (Medien, Bilder usw.) nicht separat zwischengespeichert. Folglich wird das XAP-Paket, wenn der Benutzer die Seite besucht, wieder als nicht heruntergeladen angezeigt (es sei denn, es ist abgelaufen), aber sämtliche Ressourcen werden wieder extrahiert. Außerdem gehen sämtliche Änderungen, die Sie an diesen Ressourcen in früheren Sitzungen vorgenommen haben, verloren. Um Änderungen am XAML-Dokumentobjektmodell beizubehalten, müssen Sie Ihren eigenen maßgeschneiderten Cache einrichten. (Dies ist ein praktisches Verfahren, das ich in Teil 2 dieses Artikels erläutern werde.)

Beachten Sie, dass das XAP-Paket, das im Cache des Browsers gespeichert wird, vom Benutzer abhängig ist. Sollte der Benutzer entscheiden, den Cache irgendwann zu leeren, geht alles, was dort gespeichert ist, einschließlich Ihrer XAP-Pakete, verloren. Um die Silverlight-XAP-Pakete permanent zu speichern, müssen Sie zu einem isolierten Speicher greifen (dieses Thema ist ebenfalls für Teil 2 geplant).

Ein Tool für Downloads

Wenn Sie Silverlight-Anwendungen schreiben, vergessen Sie nicht, dass jede notwendige Ressource, die nicht bereits im XAP der Anwendung verpackt ist, explizit vom Server heruntergeladen werden muss. Die WebClient-Klasse ist das primäre Silverlight-Tool, das Sie zum Einrichten der Downloads zusätzlicher Ressourcen verwenden sollten. Die Klasse bietet einige asynchrone Methoden zum Senden und Empfangen von Daten an eine bzw. von einer Webressource. Dies geschieht folgendermaßen:

WebClient wc = new WebClient();
wc.DownloadStringCompleted += 
     new DownloadStringCompletedEventHandler(callback);
wc.DownloadStringAsync(address);

Die DownloadStringAsync-Methode verarbeitet HTTP GET und erfasst die URL-Antwort als Zeichenfolge. Die Zeichenfolge wird vom zugeordneten Rückruf empfangen, wie nachfolgend veranschaulicht wird:

void callback(object sender, DownloadStringCompletedEventArgs e)
{
  if (e.Error != null)
     return;

  string response = e.Result;
...
}

Wie Sie in Kürze sehen werden, ist diese Methode perfekt zum Herunterladen von einfachem XAML ohne angefügtes CodeBehind. Um binäre Daten, wie z. B. ein XAP-Paket, programmgesteuert herunterzuladen, benötigen Sie einen Datenstrom und einen etwas anderen Ansatz. Die WebClient-Klasse ist immer noch hilfreich, da sie eine geeignete Methode in OpenReadAsync bietet:

WebClient wc = new WebClient();
wc.OpenReadCompleted += 
     new OpenReadCompletedEventHandler(callback);
wc.OpenReadAsync(address);

Die Struktur des zugeordneten Rückrufs ist dieselbe wie im vorherigen Fall. Letztendlich verwenden Sie die DownloadStringAsync-Methode, um eine einfache Zeichenfolge abzurufen. Verwenden Sie die OpenReadAsync-Methode, um einen Datenstrom abzurufen. Ob Sie eine Zeichenfolge oder einen Datenstrom herunterladen, ist hauptsächlich eine Frage der Einstellung und hängt im Grunde davon ab, wie Sie die Daten, die Sie erhalten, zu verwenden planen.

Beachten Sie auch, dass WebClient ein paar Methoden zum Schreiben an eine Remote-URL bietet: UploadStringAsync zum Bereitstellen einer Zeichenfolge und OpenWriteAsync zum Hochladen sämtlicher Daten zu einer URL mithilfe eines Datenstroms.

Sie können die Header-Eigenschaft verwenden, um zusätzliche Header anzugeben, da die Klasse standardmäßig keine angibt. Beachten Sie jedoch, dass einige Header, die Sie möglicherweise festlegen, vom Framework getrennt und intern verwaltet werden. Dies betrifft Verweiser, Verbindung und Benutzer-Agent. Der Content-Type-Header wird bewahrt, sofern er festgelegt ist.

Wenn es zum Herunterladen einer Ressource kommt, verwendet die WebClient-Klasse transparent den Cache des Browsers, bevor sie versucht, eine Verbindung aufzubauen. Wenn die XAML- oder XAP-Ressource nicht im Cache vorhanden ist, fährt die Klasse mit dem Herunterladen fort. Das Herunterladen von Inhalten aus einer Silverlight-Anwendung resultiert aus kombinierten Bestrebungen der Silverlight-Laufzeitumgebung und der internen API, die der Hostbrowser für ein Plug-In verfügbar macht. Dies bedeutet, dass innerhalb von WebClient die Silverlight-Laufzeit mit der Browsersandbox kommuniziert, um zu prüfen, ob die angeforderte Ressource bereits im Cache vorhanden ist. Wenn nicht, fährt Silverlight mit den eigenen Sicherheitsrichtlinien fort, um die Anforderung zu autorisieren. Wenn schließlich Daten vom Endpunkt zurückgegeben werden, speichert die Silverlight-Laufzeitumgebung sie lokal über die Dienste des Browsers und unter voller Einhaltung der Richtlinien des laufenden Zwischenspeicherns.

Einige Sicherheitseinschränkungen gelten für die WebClient-Klasse und andere HTTP-Klassen im Silverlight-System.Net-Namespace. Insbesondere unterstützt die WebClient-Klasse nur HTTP- und HTTPS-Schemas beim Herunterladen per Datenstrom sowie das FILE-Schema beim Herunterladen von reinem XAML. Schemaübergreifender Zugriff ist immer verboten. Deshalb können Sie WebClient z. B. nicht an eine HTTPS-Ressource verweisen, wenn die Hostseite über HTTP heruntergeladen wurde (und umgekehrt). Eine WebClient-Anforderung kann normalerweise eine URL in der Zone eines anderen Browsers erreichen, aber dies funktioniert nicht, wenn sie versucht, von einer Internetzone in eine stärker eingeschränkte Zone zu wechseln. Derzeit unterstützt Silverlight nur Zonen unter einem Windows-Betriebssystem.

Schließlich wird domänenübergreifender Zugriff nur unterstützt, wenn die Remotewebsite durch Hosten einer richtigen XML-Datei im Stammverzeichnis ausgewählt wird. Beachten Sie auch, dass domänenübergreifender Zugriff nicht in einem HTTPS-zu-HTTPS-Szenario funktioniert.

Das gleiche WebClient-Objekt kann nicht mehrere Anforderungen gleichzeitig bearbeiten. Sie sollten die IsBusy-Eigenschaft – einen booleschen Wert – prüfen, um zu bestimmen, ob es für Ihren Code sicher ist, eine neue Anforderung über die gleiche WebClient-Instanz durchzuführen. Wenn Sie mehrere WebClient-Objekte verwenden – vielleicht auf verschiedenen Threads –, können Sie mehrere Downloads gleichzeitig starten.

Herunterladen von Nur-XAML-Daten

Im Folgenden wird gezeigt, wie WebClient zu verwenden ist, um XAML-Daten herunterzuladen und in die visuelle Struktur zu integrieren. In Silverlight 2-Anwendungen bietet Ihnen das dynamische Herunterladen der einfachen XAML-Daten nicht unbedingt die Programmiermöglichkeiten, die Sie benötigen. Die XAML-Zeichenfolge muss einfaches XAML sein, und zwar ohne Verweise, die zur Laufzeit aufgelöst werden müssen, wie z. B. Bindungen oder Verweise auf Stile, Ressourcen und Ereignisse.

Nachdem die XAML-Zeichenfolge heruntergeladen ist, verwenden Sie die XamlReader-Klasse, um sie in ein Benutzeroberflächenelement zu verwandeln, das dem bereits vorhandenen Dokumentobjektmodell hinzugefügt werden kann. Der folgende Code zeigt, wie eine XAML-Zeichenfolge programmgesteuert von einer URL heruntergeladen werden kann. Beachten Sie, dass Sie die URL als Uri-Objekt zur Verfügung stellen müssen:

WebClient client = new WebClient();
client.DownloadStringCompleted += 
   new DownloadStringCompletedEventHandler(OnDownloadCompleted);
Uri uri = new Uri("xaml.ashx", UriKind.Relative);
client.DownloadStringAsync(uri);

Die URL kann auf eine einfache XAML-Ressource oder auf einen Endpunkt zeigen, der eine text/xaml-Antwort zurückgibt. Dies ist der Code zum Verarbeiten des heruntergeladenen XAML und zum Anhängen des XAML an einen Platzhalter in der visuellen Struktur:

void OnDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    // Parse XAML to a UI element 
    string xaml = e.Result;
    UIElement dom = XamlReader.Load(xaml) as UIElement;

    // Append to the DOM
    Placeholder.Children.Clear();
    Placeholder.Children.Add(dom);
}

Wie bereits erwähnt, kann jedes Element im Dokumentobjektmodell als Platzhalter dienen, das kürzlich im Plug-In gerendert wurde. Beachten Sie, dass die untergeordneten Elemente eines Benutzeroberflächenelements eine Sammlung bilden und als eine Sequenz gerendert werden. Dies bedeutet, dass Sie zum Vermeiden unerwünschter Überlappung der Elemente im Falle von Aktualisierungen die Elemente zunächst entfernen und anschließend hinzufügen sollten.

XAML-Serialisierung, die über die XamlReader- und XamlWriter-Klassen durchgeführt wird, basiert auf dem Prinzip, dass diese Erweiterungsverweise dereferenziert und Laufzeitwerte über Entwurfszeiteinstellungen gespeichert sind. Was passiert, wenn Sie XAML-Inhalte herunterladen und vor dem Anzeigen beispielsweise über dynamische Datenbindung anpassen möchten? Sie können keine Bindungen in die XAML-Quelle einbetten, aber Sie können Platzhalter in der heruntergeladenen XAML definieren, diese über Analyse abrufen und programmgesteuert auf jeden beliebigen Wert setzen. In Silverlight 2 ist das Herunterladen eines XAP-Pakets definitiv die bessere Lösung.

Arbeiten mit XAP-Paketen

Ein XAP-Paket enthält eine vollständige Silverlight-Anwendung, deren Benutzeroberfläche im Grunde aus einem Benutzersteuerelement besteht, das wirklich nur ein Container für XAML-Markup und Code ist.

Wie bereits erwähnt, enthält ein XAP-Paket eine oder mehrere Assemblys, die von einer Manifestdatei nachverfolgt werden. Das Verarbeiten eines XAP-Pakets erfordert zwei zusätzliche Schritte über das Herunterladen hinaus. Sie müssen die Hauptassembly extrahieren und anschließend die Einstiegspunktklasse instanziieren, die die heruntergeladene Anwendung startet. Selbstverständlich können Sie in diesem Fall Bindung, Stile, Ereignisse und was Sie sonst noch wünschen, im XAML verwenden. Wenn Sie mit einem XAP-Paket arbeiten, ist es die Silverlight-Laufzeitumgebung – nicht die Serialisierungs-API – die das XAML verarbeitet und anschließend die Verweise auflöst. Dies stellt einen großen Unterschied in Bezug auf Programmiermöglichkeiten dar.

Das Herunterladen und Verarbeiten eines XAP-Pakets erfordert etwas mehr Arbeit als nur das Erstellen eines Objektmodells aus einer Zeichenfolge. Ein Teil dieser Arbeit – in der Regel der Inhaltsdownload und die Assemblyextraktion – kann in eine wiederverwendbare Downloadklasse verschoben werden (siehe Abbildung 3).

Abbildung 3 Dynamisches Herunterladen eines XAP-Pakets

public partial class Page : UserControl
{
    private UIElement content = null;
    private TabItem item = null;

    public Page()
    {
        InitializeComponent();
    }

    private void chkNewContent_Click(object sender, RoutedEventArgs e)
    {
        bool shouldDisplay = (sender as CheckBox).IsChecked.Value;   

        if (shouldDisplay)
        {
            if (!IsContentAvailable())
                DownloadContent();
            else
                ShowContent();
        }
        else
        {
            HideContent();
        }
    }

    private bool IsContentAvailable()
    {
        return (content != null);
    }

    private void DownloadContent()
    {
        Downloader dl = new Downloader();
        dl.XapDownloaded += 
            new EventHandler<XapEventArgs>(OnPackageDownload);
        dl.LoadPackage("more.xap", "more.dll", "More.ExtraTab");
    }

    void OnPackageDownload(object sender, XapEventArgs e)
    {
        content = e.DownloadedContent;
        ShowContent();
    }

    private void HideContent()
    {
        this.TabList.Items.Remove(item);
    }

    private void ShowContent()
    {
        item = new TabItem();
        item.Header = "Extra tab";
        item.Content = content;
        this.TabList.Items.Add(item);
    }
}

Der Code in der Abbildung 3 zeigt ein Beispiel für eine registerkartenbasierte Anwendung, die beim erstmaligen Klicken des Benutzers auf ein Kontrollkästchen eine neue Registerkarte lädt. In diesem Fall wird ein neues XAP-Paket heruntergeladen, und das Benutzersteuerelement, das in der entsprechenden Benutzeroberfläche enthalten ist, wird in ein neu erstelltes TabItem eingefügt. Das Ausblenden des Inhalts des neu heruntergeladenen Pakets ist ein einfacher Clientvorgang. Das erneute Anzeigen erfordert aus zwei guten Gründen keinen zweiten Roundtrip: Der Inhalt ist im Speicher zwischengespeichert, und das Paket, aus dem es erstellt wurde, ist im Cache des Browsers zwischengespeichert.

In Abbildung 4 ist die Schnittstelle dieser Beispielanwendung dargestellt. Der in der zusätzlichen Registerkarte eingefügte Inhalt kommt aus einem neuen Silverlight-Anwendungsprojekt. Alles, was Sie bezüglich der Bereitstellung tun müssen, ist, das XAP-Paket im ClientBin-Ordner auf der Hostwebsite zu speichern (dem Standardspeicherort, an dem WebClient nach verwandten Inhalten sucht), bzw. an einem anderen Speicherort, der für WebClient sicher zu erreichen ist.

fig04.gif

Abbildung 4 Eine Anwendung, die Teile von Benutzeroberflächen dynamisch herunterladen kann

Erforschen wir jetzt den Code der Downloader-Hilfsklasse. Beachten Sie, dass die Downloader-Klasse, die in diesem Beispiel verwendet wird, eine interne Klasse darstellt und nichts mit dem Silverlight-Downloader-Objekt zu tun hat, das für JavaScript-Aufrufer verfügbar ist. Das JavaScript-Downloadobjekt ist im Grunde ein durch Skripts aufrufbarer Wrapper für WebClient.

Verarbeiten von XAP-Inhalt

Die Downloader-Klasse verwendet WebClient, um ein XAP-Paket herunterzuladen und anschließend die Assembly zu extrahieren, die die Anwendung aus dem gezippten Datenstrom enthält und die angegebene Basisklasse instanziiert. Diese gesamte Logik ist hinter einer relativ einfachen Programmierfassade versteckt, die aus einer LoadPackage-Methode und einem XapDownloaded-Ereignis besteht. Die Methode hat die folgende Signatur:

public void LoadPackage(
    string xapURL, string assemblyName, string className);

Sie erhält die URL für das Paket, den Namen der Assembly in dem zu extrahierenden Paket sowie den Namen der zu instanziierenden Klasse. Wie bereits erwähnt, ist diese Klasse das CodeBehind der XAML-Schnittstellendatei.

Das XapDownloaded-Ereignis wird ausgelöst, wenn die Downloadklasse die XAP-Verarbeitung abgeschlossen und das Benutzersteuerelement für die Clientanwendung fertiggestellt hat. Dies ist die Definition des Ereignisses:

public event 
    EventHandler<XapEventArgs> XapDownloaded;

Die Ereignisargumentklasse gibt die folgenden Informationen zurück:

public class XapEventArgs : EventArgs
{
  public UserControl DownloadedContent;
}

Untersuchen wir die Logik der LoadPackage-Methode. Die Methode setzt die binäre Schnittstelle von WebClient ein, um auf das XAP-Paket zu verweisen:

void LoadPackage(string package, string asm, string cls)
{
    assemblyName = asm;
    className = cls;

    Uri address = new Uri(package, UriKind.RelativeOrAbsolute); 
    WebClient client = new WebClient();
    client.OpenReadCompleted += 
       new OpenReadCompletedEventHandler(OnCompleted);
    client.OpenReadAsync(address);
}

Der Download fährt asynchron fort und löst nach Beendigung das OpenReadCompleted-Ereignis aus. In Abbildung 5 ist die Implementierung für den Ereignishandler dargestellt.

Abbildung 5 Download abgeschlossen

void OnCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (PackageDownloaded == null)
        return;

    if (e.Error != null)
        return;

    // Load a particular assembly from the XAP package
    Assembly a = GetAssemblyFromPackage(assemblyName, e.Result);

    // Get an instance of the XAML object
    object page = a.CreateInstance(className);  

    // Fire the event
    XapEventArgs args = new XapEventArgs();
    args.DownloadedContent = page as UserControl;
    XapDownloaded(this, args);
}

Wenn kein Fehler aufgetreten ist, extrahiert eine Hilfsfunktion die angegebene Assembly aus dem Paket und erhält eine Instanz der Benutzersteuerelementklasse im CodeBehind des XAML-Blocks zum Hinzufügen. Als Nächstes wird ein benutzerdefiniertes Ereignis an den Aufrufer ausgelöst, damit die XAML-Unterstruktur verarbeitet werden kann. An dieser Stelle bleibt eine Sache, die ausführlicher zu betrachten ist: die Details dazu, wie eine Assembly aus dem gezippten Datenstrom extrahiert wird, der von der URL heruntergeladen wurde. Dieser Code ist in Abbildung 6 dargestellt. Dieser Code wird häufig zum Extrahieren einer Assembly aus einem XAP-Paket verwendet. StreamResourceInfo und Application.GetResourceStream sind ebenfalls häufige Tools zum Extrahieren von Ressourcenpaketen wie z. B. Bildern oder Medieninhalten aus dem aktuellen XAP.

Abbildung 6 Extrahieren einer Assembly aus einem XAP-Paket

Assembly GetAssemblyFromPackage(string assemblyName, Stream xap)
{
    // Local variables
    Uri assemblyUri = null;
    StreamResourceInfo resPackage = null;
    StreamResourceInfo resAssembly = null;
    AssemblyPart part = null;

    // Initialize
    assemblyUri = new Uri(assemblyName, UriKind.Relative);
    resPackage = new StreamResourceInfo(xap, null);
    resAssembly = Application.GetResourceStream(resPackage, assemblyUri);

    // Extract an assembly 
    part = new AssemblyPart();
    Assembly a = part.Load(assemblySri.Stream);
    return a; 
}

Zusammenfassung

Von Silverlight-Anwendungen wird erwartet, dass sie schnell heruntergeladen und installiert sowie umgehend auf dem Benutzercomputer ausgeführt werden können. Verwalteter Code übertrifft das interpretierte JavaScript. Deshalb wird die Leistung tatsächlich sehr gut sein. Da das Herunterladen von großen Silverlight-Anwendungen oder von Anwendungen, die große externe Grafik- und Medieninhalte erfordern, unerwünschte Wartezeiten verursachen kann, hilft das Zerlegen des Downloads in mehrere Schritte. Es ist sogar besser, wenn Sie diese Schritte so programmieren, dass sie bei Bedarf stattfinden.

Die WebClient-Klasse bietet eine effektive und asynchrone API zum Herunterladen jeglicher Ressourcen, auf die über das Internet zugegriffen werden kann. Das erweiterbare Modell für das Silverlight-Objektmodell ermöglicht Ihnen, sämtliche Änderungen schnell in der vorhandenen Struktur vorzunehmen. Schließlich vereinfacht die datenstrombasierte API, die um die StreamResourceInfo-Klasse herum konstruiert ist, das Extrahieren von Inhalt aus den Ressourcen von Assemblys und XAP-Paketen.

Sämtliche heruntergeladenen Ressourcen werden durch den Browser zwischengespeichert. Diese Art des Zwischenspeicherns ist jedoch nicht permanent. In Teil 2 dieses Artikels verwende ich einen isolierten Speicher, um alle heruntergeladenen Inhalte länger auf dem Benutzercomputer zu behalten, wodurch viele potenzielle Roundtrips gespart werden. Weitere nützliche Silverlight-Funktionen finden Sie im Wicked Code-Artikel Tipps, Tricks und bewährte Methoden für Silverlight von Jeff Prosise.

Senden Sie Fragen und Kommentare für Dino Esposito in englischer Sprache an cutting@microsoft.com.

Dino Esposito ist Architekt bei IDesign und Koautor von Microsoft .NET: Architecting Applications for the Enterprise (Microsoft Press, 2008). Er lebt in Italien und ist ein weltweit gefragter Referent bei Branchenveranstaltungen. Sie finden seinen Blog unter weblogs.asp.net/despos.