Share via


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

Cutting Edge

Verwalten der dynamischen Inhaltsbereitstellung in Silverlight, Teil 2

Dino Esposito

Inhalt

Gründe für einen permanenten Cache
Grundlagen für isolierten Speicher
API für isolierten Speicher
Erstellen eines permanenten Paketcache
Ablaufrichtlinien
Letzte Schritte
Schlussbemerkungen

Im Artikel des letzten Monats wurde erläutert, wie in einer Silverlight-Anwendung beim Start und sogar in einem bedarfsgesteuerten Szenario dynamisch generierter Inhalt bereitgestellt werden kann. Es gibt viele Beispiele für die Verwendung der WebClient-Klasse und ihres asynchronen Aufrufmodells zum Herunterladen einer URL-basierten Ressource. Ich habe mich insbesondere darauf konzentriert, wie sich ein XAP-Paket herunterladen lässt, dass XAML und verwalteten Code enthält (siehe Artikel der Rubrik „Cutting Edge“ vom Januar 2009).

Der Grundgedanke besteht darin, dass Sie einen gezippten Datenstrom herunterladen und dann eine beliebige Assembly extrahieren. Als Nächstes instanziieren Sie alle gewünschten Klassen, die in der Assembly enthalten sind. Diese Klassen sind XAML-Benutzersteuerelemente – Gesamtstücke einer visuellen XAML-Struktur –, die Sie dann an beliebige Platzhalter im aktuellen XAML-Dokumentobjektmodell (Document Objekt Model, DOM) anhängen können.

Der Browser kann heruntergeladene XAP-Ressourcen nicht von anderen Ressourcen unterscheiden. Daher wird ein XAP-Paket genau wie jede andere heruntergeladene Ressource gespeichert. Dieser integrierte Mechanismus bietet eine erste Ebene der Optimierung, durch die sich mehrfache Roundtrips zum wiederholten Abrufen desselben Pakets vermeiden lassen. Die WebClient-Klasse, also der Kern der im letzten Monat erörterten Downloadkomponente, basiert auf dem Konnektivitätsmodul des Browsers und lädt keine lokal verfügbaren bzw. noch nicht abgelaufenen Ressourcen herunter.

Damit ergibt sich für Sie eine Downloadkomponente, mit der das Herunterladen beliebiger externer Pakete auf einen späteren Zeitpunkt verschoben werden kann. Außerdem erhalten Sie Zwischenspeicherungsfunktionen für beliebige dynamisch heruntergeladene Ressourcen. Vielleicht erwägen Sie, einen permanenten lokalen Paketcache hinzuzufügen, mit dem auch dynamische Änderungen an der visuellen Struktur behandelt werden können. Im Folgenden wird dies näher erläutert.

Gründe für einen permanenten Cache

Im Hinblick auf dynamische Silverlight-Inhalte gibt es zwei Hauptaspekte, die Sie möglicherweise besser steuern möchten.

Der erste ist die Ablaufrichtlinie des heruntergeladenen Inhalts. Sie möchten vielleicht genau steuern können, wann ein bestimmtes Paket abläuft und erneut heruntergeladen werden muss. Außerdem möchten Sie den Ablaufzeitpunkt möglicherweise mit einem externen Ereignis wie einer bestimmten Benutzeraktion oder mit Änderungen an anderen zwischengespeicherten Ressourcen verbinden. Wenn Sie wissen, wie der ASP.NET-Cache funktioniert, wissen Sie, was ich meine.

Der ASP.NET-Cache ermöglicht Ihnen, Daten zwischenzuspeichern und jedem zwischengespeicherten Element seine eigene Ablaufrichtlinie zuzuweisen, und zwar auf Grundlage von Dateiänderungen, Datum/Zeit oder sogar Änderungen an anderen zwischengespeicherten Elementen. Ein derartiges Modul existiert in Silverlight 2 nicht. Bei großen, dynamischen und weitgehend anpassbaren Anwendungen bietet dies allerdings erhebliche Vorteile.

Der zweite Aspekt der Standardzwischenspeicherung von Silverlight-Ressourcen, den Sie vielleicht ändern möchten, betrifft das Gefahrenpotenzial, wenn die Pakete für Benutzeraktivitäten verfügbar sind. Anders ausgedrückt: Der Benutzer hat Zugriff auf alle im Browsercache gespeicherten XAP-Pakete. Wenn der Benutzer durch Aktionen auf der Browseroberfläche den Cache leert, gehen alle XAP-Pakete unweigerlich verloren.

Ein über eine Anwendung verwalteter permanenter Cache löst beide Probleme. In diesem permanenten Cache gespeicherte XAP-Pakete wären nicht betroffen, wenn der Benutzer den Browsercache leert. Für ein permanentes Speichern von Silverlight XAP-Paketen ist Zugriff auf das lokale Dateisystem erforderlich. Aus Sicherheitsgründen lässt Silverlight nicht zu, dass Anwendungen auf das gesamte lokale Dateisystem zugreifen können. Dieses Problem lässt sich aber mit der API für den isolierten Speicher lösen. Weitere Informationen zur Sicherheit in Silverlight finden Sie unter Tiefe Einblicke in CLR: Sicherheit in Silverlight 2.

Grundlagen für isolierten Speicher

Isolierter Speicher wurde nicht speziell für Silverlight geschaffen. Das Konzept des isolierten Speichers ist bereits seit Version 1.0 Teil von Microsoft .NET Framework. Mithilfe von isoliertem Speicher können teilweise vertrauenswürdige Anwendungen Daten auf dem lokalen Computer speichern, wobei alle vorhandenen Sicherheitsrichtlinien beachtet werden. Beim Speichern der Daten einer traditionellen, voll vertrauenswürdigen .NET-Anwendung ist es wahrscheinlich nie erforderlich, die isolierte Speicherschicht zu verwenden. Bei einer teilweise vertrauenswürdigen Anwendung dagegen ist der isolierte Speicher die einzige Option zum Speichern von Daten auf dem Client.

Aus der Silverlight-Perspektive ist der isolierte Speicher ein leistungsfähiges Tool und stellt die einzige Möglichkeit dar, relativ große Datenmengen browserübergreifend und ohne die Einschränkungen zu speichern, die beispielsweise bei HTTP-Cookies vorliegen. Der folgende Punkt ist wichtig: Bei Verwendung von Silverlight ist der isolierte Speicher die einzige Möglichkeit, Daten auf dem lokalen Computer zwischenzuspeichern. Wenn eine Silverlight-Anwendung Daten lokal speichern muss, ist dies nur mithilfe des isolierten Speichers möglich. Beim Einsatz von isoliertem Speicher können außerdem die Daten jeder Anwendung getrennt von anderen Anwendungen oder von Anwendungen außerhalb der Website gespeichert werden.

Eine allgemeine Einführung zu isoliertem Speicher auf Grundlage von .NET sowie die gängigsten Verwendungsszenarios finden Sie im .NET Framework-Entwicklerhandbuch zu isoliertem Speicher. In diesem Artikel sind einige Szenarios aufgeführt, die sich nicht für den Einsatz von isoliertem Speicher eignen. Insbesondere wird erläutert, dass isolierter Speicher nicht zum Speichern von vertraulichen Daten, Code oder Konfigurationseinstellungen (außer Benutzereinstellungen) verwendet werden sollte. Hierbei handelt es sich um allgemeine Sicherheitsrichtlinien, die nicht bedeuteten, dass der Einsatz von isoliertem Speicher generell gefährlich ist.

Ist es also möglich, heruntergeladene XAP-Pakete sicher in einem isolierten Silverlight-Speicher aufzubewahren? Im Unterschied zur Desktop-CLR wird in Silverlight jeder ausführbare Codeabschnitt standardmäßig als nicht vertrauenswürdig angesehen und kann nicht zum Aufrufen kritischer Methoden oder zum Erhöhen von Berechtigungen des Aufrufstapels verwendet werden. Jeglicher für die spätere Ausführung in Silverlight gespeicherter Code ist nicht in der Lage, gefährliche Aktivitäten auszuführen. Dies birgt kein größeres Risiko als das Ausführen von beliebigem anderem Silverlight-Code. Durch Erstellen eines permanenten Cache von Silverlight-Paketen speichern Sie im Endeffekt ein Segment der ausgeführten Silverlight-Anwendung lokal.

Die Rolle des isolierten Speichers in Silverlight entspricht, soweit es um Persistenz geht, der Rolle von HTTP-Cookies in klassischen Webanwendungen. Der isolierte Speicher in Silverlight sollte als ein Satz größerer Cookies angesehen werden, die beliebige Daten, einschließlich ausführbaren Codes, enthalten können. In diesem Fall werden allerdings Schutzfunktionen durch die zentrale CLR in Silverlight bereitgestellt. Entsprechend dem Silverlight-Sicherheitsmodell löst die zentrale CLR nämlich jedes Mal eine Ausnahme aus, wenn der Anwendungscode kritische Methoden ausführen will. Im Unterschied zu HTTP-Cookies ist der isolierte Speicher in Silverlight nicht mit der Netzwerk-E/A verknüpft, und Inhalt wird nicht ohne eine Anforderung übermittelt.

Die Daten im isolierten Speicher werden von der Anwendung isoliert, und keine andere Silverlight-Anwendung kann auf den Speicher zugreifen. Die Daten werden aber auf dem lokalen Dateisystem gespeichert, sodass der Administrator des Computers darauf zugreifen kann.

Das gesamte Modell unterscheidet sich nicht wesentlich von den Vorgängen bei HTTP-Cookies. Ein Administrator ist in der Lage, die Cookies aufzufinden und sogar ihren Inhalt zu ändern. Soweit dies für Ihren Kontext passend ist, können Sie mithilfe von Verschlüsselung eine weitere Datenschutzschicht hinzufügen.

Wenn Sie immer noch besorgt sind, dass sich heruntergeladener ausführbarer Code auf Ihrem Computer befindet, sollten Sie sich erneut genauer mit dem Silverlight-Sicherheitsmodell beschäftigen. Die zentrale Silverlight-CLR löst jedes Mal eine Ausnahme aus, wenn Anwendungscode versucht, eine kritische Methode auszuführen. In der Silverlight-Basisklassenbibliothek (Base Class Library, BCL) sind die Methoden und Klassen, die Vorgänge mit hohen Berechtigungen erfordern, mit einem besonderen SecurityCritical-Attribut markiert. Dies ist bei den meisten Inhalten des System.IO-Namespace der Fall.

Im Silverlight-Sicherheitsmodell wird berücksichtigt, dass einige Plattformklassen sichere Aufrufe an kritische Methoden vornehmen müssen. Diese Klassen und Methoden werden dann mit dem SecuritySafeCritical-Attribut markiert. Dies ist der Fall bei Klassen in der System.IO.IsolatedStorage-API (siehe Abbildung 1). Der wichtige Punkt beim Silverlight-Sicherheitsmodell besteht darin, dass kein Anwendungscodeabschnitt jemals mit dem SecurityCritical- oder dem SecuritySafeCritical-Attribut markiert werden kann. Dieses Attribut ist für Klassen in Assemblys reserviert, die digital von Microsoft signiert wurden und vom Silverlight-Installationsverzeichnis in den Speicher geladen wurden.

Abbildung 1 Durchsuchen der API für den isolierten Speicher

Wie Sie sehen, beschränkt sich ein möglicher Schaden auf normale Vorgänge, die im transparenten Modus ausgeführt werden, selbst wenn es einem Angreifer tatsächlich gelingen sollte, in Ihren Computer einzudringen und heruntergeladenen Silverlight-Inhalt zu ersetzen.

API für isolierten Speicher

Die Silverlight-BCL enthält eine eigene Implementierung des isolierten Speichers, die ganz auf das Webszenario ausgerichtet ist. Isolierter Speicher bietet Zugriff auf eine Unterstruktur des gesamten lokalen Dateisystems, wobei es in keiner Methode oder Eigenschaft die Möglichkeit gibt, dass der ausgeführte Code den physikalischen Speicherplatz des Dateispeichers auf dem Benutzercomputer erkennen kann. In einer Silverlight-Anwendung können keine absoluten Dateisystempfade für den isolierten Speicher verwendet werden. Ebenso sind keine Laufwerksinformationen verfügbar bzw. werden unterstützt. Dasselbe gilt für relative Pfade, die Auslassungspunkte enthalten, wie beispielsweise den folgenden:

\..\..\myfile.txt 

Der Unterstrukturstamm des isolierten Speichers befindet sich in einem Ordner unter dem aktuellen Benutzerpfad. Unter Windows Vista befindet sich der Ordnerstamm des isolierten Speichers z. B. unterhalb des Benutzerverzeichnisses.

Eine Silverlight-Anwendung erhält durch einen Methodenaufruf Zugriff auf den anwendungsspezifischen Einstiegspunkt für den isolierten Speicher:

using (IsolatedStorageFile iso = 
       IsolatedStorageFile.GetUserStoreForApplication()) 
{
  ...
}

Die statische Methode „GetUserStoreForApplication“ gibt ein Token zurück, das für den weiteren Zugriff auf den isolierten Speicher verwendet wird. Wenn der erste Aufruf an GetUserStoreForApplication erfolgt ist, wird die anwendungsspezifische Unterstruktur erstellt, wenn sie nicht bereits vorhanden ist.

Die API für den isolierten Silverlight-Speicher stellt Klassen für die Arbeit mit Dateien und Verzeichnissen innerhalb der geschützten Dateisystemunterstruktur bereit. Glücklicherweise ist die Liste der Klassen, die Sie kennen müssen, ziemlich kurz (siehe Abbildung 2).

fig02.gif

In der IsolatedStorageFile-Klasse gibt es eine Reihe von Methoden zum Erstellen und Löschen von Dateien und Verzeichnissen, zum Überprüfen, ob bestimmte Dateien und Verzeichnisse vorhanden sind, sowie zum Lesen und Schreiben neuer Dateien. Zum Arbeiten mit Dateien setzen Sie Datenströme ein. Optional können Sie Datenströme mit StreamReaders umschließen, um Objekte zu erhalten, mit denen sich einfacher arbeiten lässt. Abbildung 3 zeigt ein kurzes Beispiel dafür, wie sich mithilfe eines StreamReaders eine isolierte Speicherdatei erstellen lässt.

Abbildung 3 Erstellen einer isolierten Speicherdatei

using (IsolatedStorageFile iso = 
      IsolatedStorageFile.GetUserStoreForApplication())
{
    // Open or create the low level stream
    IsolatedStorageFileStream fileStream;
    fileStream = new IsolatedStorageFileStream(fileName, 
        FileMode.OpenOrCreate, iso);

    // Encapsulate the raw stream in a more convenient writer
    StreamWriter writer = new StreamWriter(stream);

    // Write some data
    writer.Write(DateTime.Now.ToString());

    // Clean up
    writer.Close();
    stream.Close();
}

Wenn Sie einen untergeordneten Datenstrom in einem bequemer zu verwendenden Datenstromgenerator oder StreamReader umschließen, ist der Code zum Schreiben bzw. Lesen von Daten fast identisch mit dem Code in einer klassischen .NET-Anwendung. Als Nächstes soll untersucht werden, wie Sie die API für den isolierten Speicher nutzen können, um beliebige heruntergeladene XAP-Pakete lokal zu speichern und später erneut zu laden.

Erstellen eines permanenten Paketcache

Im Artikel des letzten Monats wurde eine Downloadwrapperklasse verwendet, um einen Teil des Standardcodes zu verbergen, der für das Herunterladen eines XAP-Pakets und zum Extrahieren von Assemblys und anderen Ressourcen erforderlich ist. Die Downloader-Klasse ist aber nicht nur eine Hilfsklasse. Vom Konzept her repräsentiert sie einen wichtigen Teil der Logik, den Sie aus verschiedenen Gründen vom übrigen Anwendungscode trennen sollten.

Der erste Grund ist die Testbarkeit. Durch Bereitstellen der Funktionalität einer Downloadkomponente über eine Schnittstelle ergibt sich die Möglichkeit, das Downloadprogramm für Testzwecke schnell und effizient zu simulieren. Außerdem können Sie eine Schnittstelle als Tool nutzen, um ein einfacheres Downloadprogramm durch ein anspruchsvolleres zu ersetzen, das tatsächlich die Paketzwischenspeicherung unterstützt. Abbildung 4 zeigt die Architektur des Entwurfs, den Sie anstreben sollten.

fig04.gif

Abbildung 4 Die Downloadkomponente und die übrige Anwendung

fig05.gif

Abbildung 5 Extrahieren einer Schnittstelle

Im Quellcode des letzten Monats war die Downloader-Klasse ein monolithischer Codeabschnitt. Für einen flexibleren Entwurf soll nun daraus eine Schnittstelle extrahiert werden. Wie in Abbildung 5 dargestellt, verfügt Visual Studio über ein Kontextmenü, das zwar nicht so viele Funktionen wie kommerzielle Umgestaltungstools bietet, aber beim Extrahieren einer Schnittstelle aus einer Klasse hilfreich ist.

Da der Kern Ihrer Silverlight-Anwendungen jetzt mit der IDownloader-Schnittstelle kommuniziert, muss die gesamte Logik für den Paketcache nur in der eigentlichen Downloader-Klasse gespeichert werden:

interface IDownloader
{
    void LoadPackage(string xapUrl, string asm, string cls);
    event EventHandler<Samples.XapEventArgs> XapDownloaded;
}

Insbesondere wird die LoadPackage-Methode neu geschrieben und enthält jetzt die Logik, die das Vorhandensein des angegebenen XAP-Pakets innerhalb des isolierten Speichers überprüft und es gegebenenfalls aus dem Internet herunterlädt. Abbildung 6 zeigt einen großen Codeabschnitt für die Downloader-Klasse. Die Methode versucht zunächst, den Datenstrom für das XAP-Paket aus dem internen Cache abzurufen. Wenn dieser Versuch fehlschlägt, lädt die Methode das Paket vom Hostserver herunter. (Genau dies wurde im Artikel des letzten Monats ausführlich besprochen.)

Abbildung 6 Cacheunterstützung für die Downloadkomponente

public void LoadPackage(string xap, string asm, string cls)
{
    // Cache data within the class
    Initialize(xap, asm, cls, PackageContent.ClassFromAssembly);

    // Have a look in the cache
    Stream xapStream = LookupCacheForPackage();
    if (xapStream == null)
        StartDownload();
    else
    {
        // Process and extract resources
        FindClassFromAssembly(xapStream);
    }
}

protected Stream LookupCacheForPackage()
{
    // Look up the XAP package for the assembly.
    // Assuming the XAP URL is a file name with no HTTP information
    string xapFile = m_data.XapName;

    return DownloadCache.Load(xapFile);
}

protected void StartDownload()
{
    Uri address = new Uri(m_data.XapName, UriKind.RelativeOrAbsolute);
    WebClient client = new WebClient();

    switch (m_data.ActionRequired)
    {
        case PackageContent.ClassFromAssembly:
            client.OpenReadCompleted += 
                new OpenReadCompletedEventHandler(OnCompleted);
            break;
        default:
            return;
    }
    client.OpenReadAsync(address);
}

private void OnCompleted(object sender, OpenReadCompletedEventArgs e)
{
    // Handler registered at the application level?
    if (XapDownloaded == null)
        return;

    if (e.Error != null)
        return;

    // Save to the cache
    DownloadCache.Add(m_data.XapName, e.Result);

    // Process and extract resources
    FindClassFromAssembly(e.Result);
}

private void FindClassFromAssembly(Stream content)
{
    // Load a particular assembly from XAP
    Assembly a = GetAssemblyFromPackage(m_data.AssemblyName, content);

    // Get an instance of the specified user control class
    object page = a.CreateInstance(m_data.ClassName);

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

In Silverlight ist Herunterladen ein asynchroner Prozess. Daher löst die interne StartDownload-Methode ein „completed“-Ereignis aus, wenn das Paket vollständig auf dem Client verfügbar ist. Der Ereignishandler speichert zuerst den Inhalt des XAP-Paketdatenstroms in einer lokalen Datei und extrahiert anschließend Ressourcen daraus. Beachten Sie, dass im Beispielcode nur Assemblys extrahiert werden. Bei einer allgemeineren Komponente können Sie die Cachefunktionen möglicherweise auf andere Arten von Ressourcen erweitern, beispielsweise XAML für Animationen, Bilder oder andere Zusatzdateien.

Die LoadPackage-Methode in der Downloader-Klasse wird zum Herunterladen von Silverlight­Benutzersteuerelementen verwendet, die in die aktuelle XAML-Struktur eingefügt werden. Weil das XAP-Paket ein Mehrfachdateicontainer ist, müssen Sie angeben, welche Assembly das Benutzersteuerelement und den Klassennamen enthält. Mit dem Code in Abbildung 6 wird einfach die angegebene Assembly aus dem Paket extrahiert und in die aktuelle Anwendungsdomäne geladen, und anschließend wird eine Instanz der angegebenen enthaltenen Klasse erstellt.

Was passiert, wenn die Assembly Abhängigkeiten aufweist? Der Code in Abbildung 6 deckt nicht nur dieses Szenario ab. Wenn daher die Assembly, die als Argument an LoadPackage übergeben wird, eine Abhängigkeit in Bezug auf eine andere Assembly (oder sogar im selben XAP-Paket) aufweist, wird eine Ausnahme ausgelöst, sobald der Ausführungsfluss eine Klasse in der Abhängigkeitsassembly erreicht. Der springende Punkt ist, dass alle Assemblys im Paket in den Speicher geladen sein müssen. Dazu müssen Sie auf die Manifestdatei zugreifen und Informationen zu bereitgestellten Assemblys lesen und verarbeiten. In Abbildung 7 sehen Sie, wie sich alle in der Manifestdatei aufgeführten Assemblys in den Speicher laden lassen.

Abbildung 7 Laden aller Assemblys in der Manifestdatei

private Assembly GetAssemblyFromPackage(
     string assemblyName, Stream xapStream)
{
    // Local variables
    StreamResourceInfo resPackage = null;
    StreamResourceInfo resAssembly = null;

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

    // Extract the primary assembly and load into the AppDomain 
    AssemblyPart part = new AssemblyPart();
    Assembly a = part.Load(resAssembly.Stream);

    // Load other assemblies (dependencies) from manifest
    Uri manifestUri = new Uri("AppManifest.xaml", UriKind.Relative);
    Stream manifestStream = Application.GetResourceStream(
        resPackage, manifestUri).Stream; 
    string manifest = new StreamReader(manifestStream).ReadToEnd();

    // Parse the manifest to get the list of referenced assemblies
    List<AssemblyPart> parts = ManifestHelper.GetDeploymentParts(manifest);

    foreach (AssemblyPart ap in parts)  
    {
        // Skip over primary assembly (already processed) 
        if (!ap.Source.ToLower().Equals(assemblyName))
        {
            StreamResourceInfo sri = null;
            sri = Application.GetResourceStream(
                resPackage, new Uri(ap.Source, UriKind.Relative));
            ap.Load(sri.Stream);
        }
    }

    // Close stream and returns
    xapStream.Close();
    return a;
}

Die Manifestdatei ist eine XML-Datei, wie hier gezeigt:

<Deployment EntryPointAssembly="More" EntryPointType="More.App" 
            RuntimeVersion="2.0.31005.0">
  <Deployment.Parts>
    <AssemblyPart x:Name="More" Source="More.dll" />
    <AssemblyPart x:Name="TestLib" Source="TestLib.dll" />
  </Deployment.Parts>
</Deployment> 

Zur Analyse der Datei können Sie LINQ-to-XML verwenden. Der Quellcode enthält als Beispiel eine ManifestHelper-Klasse mit einer Methode, die eine Liste von AssemblyPart-Objekten zurückgibt (siehe Abbildung 8). Beachten Sie, dass in Betaversionen von Silverlight 2 die XamlReader-Klasse zur Analyse der Manifestdatei in einem Bereitstellungsobjekt verwendet werden kann:

// This code throws in Silverlight 2 RTM
Deployment deploy = XamlReader.Load(manifest) as Deployment;

Abbildung 8 Analyse der Manifestdatei mithilfe von LINQ-to-XML

public class ManifestHelper
{
   public static List<AssemblyPart> GetDeploymentParts(string manifest)
   {
      XElement deploymentRoot = XDocument.Parse(manifest).Root;
      List<AssemblyPart> parts = 
          (from n in deploymentRoot.Descendants().Elements() 
           select new AssemblyPart() { 
                Source = n.Attribute("Source").Value }
          ).ToList();

          return parts;
   }
}

In der endgültigen Produktversion wurde das Bereitstellungsobjekt in ein Singleton umgewandelt und kann daher nicht mehr zum dynamischen Laden von Assemblys verwendet werden. Das XML im Manifest muss deshalb manuell analysiert werden.

Ablaufrichtlinien

Bei der bisherigen Implementierung ist der Assemblycache permanent, und es gibt für den Benutzer keine Möglichkeit, aktualisierte Pakete abzurufen. Die einzige mögliche Problemumgehung besteht darin, dass ein Computeradministrator die isolierten Speicherdateien für die Anwendung sucht und sie manuell über Windows Explorer löscht.

Im Folgenden soll eine einfache Ablaufrichtlinie hinzugefügt werden, die alle zwischengespeicherten XAP-Dateien zu einem bestimmten Zeitpunkt nach dem Herunterladen ungültig macht. Eine Ablaufrichtlinie wird in der DownloadCache-Klasse eingerichtet (siehe Abbildung 6). Wenn Sie dem Cache eine XAP-Datei hinzufügen, müssen Sie Informationen zur Downloadzeit speichern. Wenn Sie auf den Cache zugreifen, um ein Paket abzurufen, sollten Sie überprüfen, ob das Paket abgelaufen ist (siehe Abbildung 9).

Abbildung 9 Überprüfen des Ablaufdatums

public bool IsExpired(string xapFile)
{
    bool expired = true;
    if (m_ItemsIndex.ContainsKey(xapFile))
    {
        DateTime dt = (DateTime)m_ItemsIndex[xapFile];

        // Expires after 1 hour
        expired = dt.AddSeconds(3600) < DateTime.Now;    
        if (expired)
            Remove(xapFile);
    }

    return expired;
}

Die Implementierung dieses anscheinend einfachen Algorithmus wird durch eine wichtige Tatsache behindert. In Silverlight haben Sie keine Möglichkeit, auf Dateiattribute wie die letzte Aktualisierung oder die Erstellungszeit zuzugreifen. Folglich sind Sie für das Verwalten der Zeitinformationen verantwortlich. Wenn Sie dem Cache also ein XAP-Paket hinzufügen, müssen Sie auch einen Eintrag in einem benutzerdefinierten, persistenten Verzeichnis vornehmen, um den Downloadzeitpunkt des Pakets verfolgen zu können. Diese Informationen müssen natürlich auf irgendeine Weise permanent im isolierten Speicher aufbewahrt werden. Abbildung 10 veranschaulicht dieses Konzept.

Abbildung 10 Aufbewahren von Downloadinformationen im isolierten Speicher

public static Stream Load(string file)
{
    IsolatedStorageFile iso;
    iso = IsolatedStorageFile.GetUserStoreForApplication();

    if (!iso.FileExists(file))
    {
        iso.Dispose();
        return null;
    }

    // Check some expiration policy
    CacheIndex m_Index = new CacheIndex();
    if (!m_Index.IsExpired(file))
        return iso.OpenFile(file, FileMode.Open);

    // Force reload
    iso.Dispose();
    return null;
}

CacheIndex ist eine Hilfsklasse, die die systemeigene API der Silverlight-Anwendungseinstellungen verwendet, um ein Wörterbuch von XAP-Namen und -Downloadzeiten im isolierten Speicher aufzubewahren. Der m_ItemIndex-Member ist ein einfaches Wörterbuchobjekt, das im CacheIndex-Konstruktor instanziiert wurde (siehe Abbildung 11).

Abbildung 11 Die CacheIndex-Klasse

public class CacheIndex
{
    private const string XAPCACHENAME = "XapCache";
    private Dictionary<string, object> m_ItemsIndex; 
    public CacheIndex()
    {
      IsolatedStorageSettings iss;
      iss = IsolatedStorageSettings.ApplicationSettings;
      if (iss.Contains(XAPCACHENAME))
         m_ItemsIndex = iss[XAPCACHENAME] as Dictionary<string, object>;
      else
      {
         m_ItemsIndex = new Dictionary<string, object>();
         iss[XAPCACHENAME] = m_ItemsIndex;
         iss.Save();
      }
   }
  ...
}

ApplicationSettings ist ein nettes Feature in Silverlight 2. Es besteht im Grunde aus einem Zeichenfolgen-/Objektwörterbuch und wird beim Laden der Anwendung automatisch aus dem Speicher gelesen und beim Schließen wieder im Speicher abgelegt. Jedes (serialisierbare) Objekt, das Sie dem Speicher hinzufügen, wird automatisch permanent aufbewahrt.

Durch Erstellen eines XAPCACHENAME-Eintrags im Wörterbuch wird der Inhalt des XAP-Wörterbuchs permanent gespeichert. Das XAP-Wörterbuch enthält einen Eintrag für jedes heruntergeladene Paket, zusammen mit der Downloadzeit, wie in Abbildung 12 illustriert. Beachten Sie, dass die ApplicationSettings-API ebenfalls eine Save-Methode bietet, um vor dem Herunterfahren der Anwendung Persistenz zu erzwingen.

Abbildung 12 Hinzufügen von Downloadinformationen

public void Add(string xapFile)
{
    m_ItemsIndex[xapFile] = DateTime.Now;
    IsolatedStorageSettings iss;
    iss = IsolatedStorageSettings.ApplicationSettings;
    iss.Save();                
}

public void Remove(string xapFile)
{
    m_ItemsIndex.Remove(xapFile);
    IsolatedStorageSettings iss;
    iss = IsolatedStorageSettings.ApplicationSettings;
    iss.Save();
}

Letzte Schritte

Alle bisher erörterten Änderungen und Details finden verborgen in einer öffentlichen Klasse statt, nämlich der Downloader-Klasse. Durch Integrieren dieser Klasse in Ihre Silverlight-Anwendung erhalten Sie mehrere Zwischenspeicherebenen auf einmal. Durch Einsatz des richtigen Codes können Sie heruntergeladene Benutzersteuerelemente für die Dauer der Anwendungssitzung zwischenspeichern. Wenn Sie beispielsweise Inhalt für ein Registerkartenelement heruntergeladen haben, können Sie das Registerkartenelement wiederholt verbergen und anzeigen, ohne das Paket erneut herunterladen zu müssen.

Wenn Sie den Download über die WebClient-Klasse vorgenommen haben (wie im Artikel des letzten Monats), werden die Informationen durch das Browsermodul geleitet und auf Browserebene zwischengespeichert. Alle heruntergeladenen XAP-Pakete bleiben auf dem Computer des Benutzers gespeichert, bis dieser den Browsercache leert. Schließlich unterstützt die mit diesem Artikel bereitgestellte Downloader-Klasse einen permanenten Cache durch isolierten Speicher. Jedes Mal, wenn Sie einen Download über die WebClient-Klasse vornehmen, wird das XAP-Paket daher auch im lokalen Speicher abgelegt.

Die Downloader-Klasse bietet zudem die Möglichkeit, die XAP-Datei aus dem Speicher abzurufen, wenn ein gültiges Paket gefunden wird. Beachten Sie, dass dies über mehrere Anwendungssitzungen hinweg funktioniert. Sie laden das Paket einmal herunter, arbeiten daran und beenden dann die Anwendung. Wenn Sie die Anwendung wieder öffnen, wird das Paket erneut aus dem Speicher geladen, wenn es nicht abgelaufen ist. Was passiert, wenn das Paket abläuft? In diesem Fall durchläuft der Downloader die WebClient-Klasse. An diesem Punkt gibt die WebClient-Klasse möglicherweise nur eine zuvor browsergespeicherte Kopie desselben Pakets zurück.

Dieses Verhalten ist beabsichtigt. Um das Speichern auf Browserebene zu umgehen, muss es in der ursprünglichen HTTP-Anforderung deaktiviert werden, wie bereits im letzten Monat angemerkt. Eigenschaften müssen auf Seitenebene zwischengespeichert werden, oder das Paket muss über einen HTTP-Handler abgerufen werden, bei dem die Ablaufrichtlinien genauer festgelegt werden können, ohne dass sich dies auf die anderen Ressourcen auf der Seite auswirkt.

Schlussbemerkungen

Ein Zwischenspeichern des XAP-Pakets bedeutet nicht, dass einzelne Ressourcen wie DLLs, XAML-Animationen oder Multimediainhalte zwischengespeichert werden. In der aktuellen Implementierung werden die Ressourcen bei jeder Verwendung aus dem XAP-Paket extrahiert. Dieser Aspekt lässt sich jedoch mit der Downloader-Klasse noch weiter verbessern. Ebenso bietet das Paket ein Benutzersteuerelement, verfolgt aber nicht die Änderungen, die der Benutzer auf seiner Benutzeroberfläche erzwingen kann. Das Nachverfolgen dynamischer Änderungen an der XAML-Struktur ist ein separates Thema, das in einem eigenen Artikel diskutiert werden sollte.

Es gibt zwei Möglichkeiten, auf ein privates Silverlight-Dateisystem auf dem Computer des lokalen Benutzers zuzugreifen. In allen Codeausschnitten für diesen Artikel wurde die GetUserStoreForApplication-Methode der IsolatedStorageFile-Klasse verwendet. Diese Methode gibt ein Token zurück, um auf einen Abschnitt des Dateisystems zuzugreifen, der nach Anwendung isoliert ist. Dies bedeutet, dass alle und nur die Assemblys, die mit einer Anwendung verbunden sind, denselben Speicher verwenden. Sie können auch einen Speicher auswählen und für alle Anwendungen freigeben, die auf derselben Website gehostet werden. In diesem Fall rufen Sie das Token über die GetUserStoreForSite-Methode ab.

Beachten Sie außerdem, dass der lokale Speicher über das Silverlight-Konfigurationsdialogfeld verwaltet und sogar ganz deaktiviert werden kann. (Klicken Sie mit der rechten Maustaste auf eine Silverlight-Anwendung.) In diesem Fall wird beim Abrufen des Tokens eine Ausnahme ausgelöst. Außerdem gilt für den lokalen Speicher ein Datenträgerkontingent auf Domänenbasis. Der Standardwert lautet 1 MB. Dies sollten Sie beim Planen eines permanenten Silverlight-Cache berücksichtigen.

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

Dino Esposito ist Architekt bei IDesign und Mitautor 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.