Artikel

Mai 2004 | Artikel

Zwischenlager

(Link zum Artikel: http://www.it-republik.de/dotnet/artikel/0550)

Der Assembly Download Cache (ADC)

Text: von Neno Loje
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Wenn eine .NET-Anwendung direkt von einem Webserver gestartet oder ein .NET-Steuerelement in einer Internetseite ausgeführt wird, so landen die betroffenen Assemblies im .NET Assembly Download Cache (kurz ADC). In diesem Artikel wollen wir Ihnen zeigen, wozu dieser dient und wie Sie ihn verwalten können.

Ein Feature von .NET ist das sichere Ausführen von .NET-Programmen, die auf einem Webserver gehostet werden - das so genannte No-Touch Deployment [1]. So genügt es, eine .NET-EXE in einen Ordner auf dem Webserver zu kopieren und schon lässt sich das Programm über den URL (z.B. http://MeinServer/MeineAnwendung/Test.exe) starten. Wird die Anwendung ein zweites Mal ausgeführt, wird diese nur erneut heruntergeladen, wenn eine neuere Version auf dem Server bereitgestellt wurde, ansonsten wird die Assembly direkt aus dem ADC geladen.

Verwechseln Sie den ADC nicht mit dem Global Assembly Cache (GAC [2]), der als zentrale Aufbewahrungsstelle für gemeinsam genutzte Assemblies dient, oder mit dem Internet Explorer-Cache. Allerdings nutzt die derzeitige Implementierung der CLR intern URLMon [3], um Assemblies über HTTP zu empfangen. Als Nebeneffekt landet alles, was die CLR herunterlädt, zusätzlich auch im WinINet-Cache, das heißt dem Cache, den auch der Internet Explorer verwendet. Von Haus aus bietet das .NET Framework drei Varianten zur Administration des ADC, die nun näher vorgestellt werden.

Mit dem Windows Explorer
Mit dem .NET Framework wird auch der Assembly Cache Viewer [5] installiert, welcher sich in der shfusion.dll befindet und eine Erweiterung des Windows Explorers ist, die es Ihnen ermöglicht, durch Öffnen des virtuellen Ordners \assembly den GAC bzw. durch assembly\Download den ADC einzusehen (siehe Abb. 1). So erhalten Sie eine Übersicht der im Cache befindlichen Assemblies und können erkennen, von wo die Assemblies ursprünglich heruntergeladen wurden. Leider fehlt hier die Möglichkeit, Assemblies gezielt aus dem Cache zu entfernen oder den Cache zu leeren.

Über Extras | Cacheeinstellungen können Sie eine Obergrenze dafür festlegen, wie viel Platz der Cache auf Ihrer Festplatte einnehmen darf (siehe Abb. 2). Beachten Sie dabei, dass jeder Benutzer einen eigenen Cache hat und sich die Einstellung nur auf den aktuellen Benutzer bezieht. Nach der Installation ist eine Cachegröße von 50 MB pro Benutzer voreingestellt.
Über die Kommandozeile
Die Freunde der Kommandozeile können sich mit dem Global Assembly Cache Utility (gacutil.exe [6]) den Inhalt des Download Caches anzeigen lassen oder den gesamten Inhalt löschen.
So lassen Sie sich die Assemblies im Download Cache anzeigen:
  1. gacutil.exe /ldl
Das Ergebnis sieht wie folgt aus:
  1. C:\>gacutil /ldl
  2. Microsoft (R) .NET Global Assembly Cache Utility. Version 1.1.4322.573
  3. Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
  4. The cache of downloaded files contains the following entries:
  5. NDoc.Documenter.Xml, Version=1.2.1303.41469, Culture=neutral, PublicKeyT
  6. oken=b9896512f28c0f09, Custom=null
  7. Interop.MSHelpCompiler, Version=1.0.0.0, Culture=neutral, PublicKeyToken
  8. =b9896512f28c0f09, Custom=null
  9. NDoc.Documenter.Msdn, Version=1.2.1303.41460, Culture=neutral, PublicKey
  10. Token=b9896512f28c0f09, Custom=null
  11. NDoc.Documenter.JavaDoc, Version=1.2.1303.41464, Culture=neutral, Public
  12. KeyToken=b9896512f28c0f09, Custom=null
  13. RssCommentator, Version=1.0.1389.25555, Culture=neutral, PublicKeyToken=
  14. Number of items = 5
Und so leeren Sie den Download Cache:
  1. gacutil.exe /cdl
Programmatisch mit Microsoft.Win32.Fusion
Im Herzen des .NET Frameworks - der mscorlib.dll - im Namensraum Microsoft.Win32 befindet sich die Klasse Fusion, die es ermöglicht, programmatisch nicht nur auf den ADC, sondern auch auf den GAC zuzugreifen. Leider waren die Microsoft-Entwickler hier nicht gnädig mit uns und haben die Klassen als internal deklariert (siehe Abb. 3), sodass sie von außerhalb der Assembly nicht nutzbar sind. Dennoch kann man die Klasse über Reflection verwenden. Da dies aber mehr einen schmutzigen Trick als eine gelungene Technik darstellt, verweise ich auf einen entsprechenden Artikel im Web zu diesem Thema [7].
Physikalisch befindet sich der Cache-Ordner unter Dokumente und Einstellungen\\Lokale Einstellungen\Anwendungsdaten\assembly\dl2\. Dort legt das .NET Framework aus Gründen der Sicherheit Unterordner mit kryptischen Namen an. Jede heruntergeladene Assembly erhält dabei einen eigenen Ordner, in dem die Assembly sowie eine INI-Datei mit dem Namen __AssemblyInfo__.ini, in der die wichtigsten Metadaten und die Herkunft gespeichert werden, abgelegt werden. Für weitere Assemblies, die referenziert werden, werden eigene Ordner erstellt. Sie sollten sich allerdings dessen bewusst sein, dass die interne Struktur und der physikalische Ort des ADC undokumentiert sind und sich beim nächsten Update bzw. bei der nächsten .NET Framework-Version möglicherweise verändern.
Um programmatisch eine Liste aller Ordner mit Assemblies zu bekommen, suchen wir den Cache-Ordner sowie alle seine Unterordner nach der vorhin genannten INI-Datei ab. Ist diese vorhanden, haben wir einen gültigen Ordner gefunden (siehe Listing 1).

Listing 1
  1. private static string[] GetCacheDirectories(string startDirectory)
  2. {
  3. ArrayList results = new ArrayList();
  4. string[] directories = Directory.GetDirectories(startDirectory);
  5. foreach (string directory in directories)
  6. {
  7. if (File.Exists(Path.Combine(directory, "__AssemblyInfo__.ini")))
  8. {
  9. results.Add(directory);
  10. }
  11. // Rekursiver Aufruf
  12. results.AddRange(GetCacheDirectories(directory));
  13. }
  14. return (string[])results.ToArray(typeof(string));
  15. }

Um die Informationen zu der Assembly im Speicher zu halten, benötigen wir noch eine Struktur CachedAssemblyInfo. Die öffentlichen Felder DisplayName, Version, Culture, CodeBase und PublicKeyToken werden im Konstruktor aus der INI-Datei ermittelt und der aktuelle Cacheordner in LocalCacheDirectory abgelegt (Listing 2). Nun können wir alle Cache-Ordner durchgehen und die CachedAssemblyInfo-Objekte erstellen (Listing 3).

Listing 2
  1. public struct CachedAssemblyInfo
  2. {
  3. public string DisplayName;
  4. public string Version;
  5. public string Culture;
  6. public string CodeBase;
  7. public string PublicKeyToken;
  8. public string LocalCacheDirectory;
  9. public CachedAssemblyInfo(string filePath, string localCacheDirectory)
  10. {
  11. const string INI_SECTION = "AssemblyInfo";
  12. AdcUtil.IO.IniFile ini = new AdcUtil.IO.IniFile(filePath);
  13. string buffer = ini.Read(INI_SECTION, "DisplayName", "");
  14. this.DisplayName = buffer.Substring(0, buffer.IndexOf(','));
  15. this.Version = ExtractFromString(buffer, "Version");
  16. this.Culture = ExtractFromString(buffer, "Culture");
  17. this.PublicKeyToken = ExtractFromString(buffer, "PublicKeyToken");
  18. this.CodeBase = ini.Read(INI_SECTION, "URL", "");
  19. this.LocalCacheDirectory = localCacheDirectory;
  20. }
  21. private static string ExtractFromString(string buffer, string key)
  22. {
  23. string[] parts = buffer.Split(',');
  24. foreach (string part in parts)
  25. {
  26. int index = part.IndexOf('=');
  27. if (index != -1) // Erster Eintrag hat kein "="!
  28. {
  29. if (part.Substring(1, index - 1) == key) // Key ist case-sensitive!
  30. {
  31. return part.Substring(index + 1);
  32. }
  33. }
  34. }
  35. return String.Empty;
  36. }
  37. }

Listing 3
  1. public static CachedAssemblyInfo[] GetCachedAssemblies()
  2. {
  3. if (Directory.Exists(UserCacheFolder))
  4. {
  5. string[] directories = GetCacheDirectories(UserCacheFolder);
  6. CachedAssemblyInfo[] results = new CachedAssemblyInfo[directories.Length];
  7. for (int i = 0; i < directories.Length; i++)
  8. {
  9. results[i] = new CachedAssemblyInfo(Path.Combine(directories[i], "__AssemblyInfo__.ini"), "" + directories[i]);
  10. }
  11. return results;
  12. }
  13. else
  14. {
  15. return new CachedAssemblyInfo[0];
  16. }
  17. }

Der ADC-Pfad lässt sich folgendermaßen ermitteln:
  1. public string UserCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"assembly\dl2");
Jetzt können wir die Liste aller Assemblies zum Beispiel in einer ListView anzeigen:
  1. CachedAssemblyInfo[] assemblies = AssemblyDownloadCache.GetCachedAssemblies();
  2. foreach (CachedAssemblyInfo assembly in assemblies)
  3. {
  4. ListViewItem item = new ListViewItem();
  5. item.Text = assembly.DisplayName;
  6. item.SubItems.Add(assembly.Version);
  7. item.SubItems.Add(assembly.PublicKeyToken);
  8. item.SubItems.Add(assembly.Culture);
  9. item.SubItems.Add(assembly.CodeBase);
  10. item.SubItems.Add(assembly.LocalCacheDirectory);
  11. this.lvListView.Items.Add(item);
  12. }
Um eine Assembly programmatisch aus dem ADC zu entfernen, reicht es aus, den entsprechenden Unterordner zu löschen:
  1. public static void DeleteCachedAssembly(CachedAssemblyInfo asm)
  2. {
  3. System.IO.Directory.Delete(asm.LocalCacheDirectory, true);
  4. }
Analog dazu brauchen Sie, um den Cache zu leeren, nur den ADC-Ordner komplett zu löschen (analog zu dem was gacutil /cdl bewirkt):
  1. public static void DeleteCache()
  2. {
  3. System.IO.Directory.Delete(UserCacheFolder, true);
  4. }
Das Beispielprogramm .NET Assembly Download Cache Utility (AdcUtil.exe) steht mit Quelltext zum Download unter [8] bereit und stellt einen voll funktionsfähigen Download Cache Viewer da (siehe Abb. 4), der im Vergleich zu der Explorer-Erweiterung mehr Funktionalität bietet (Assemblies löschen, Cache leeren, Im Browser öffnen).

Neno Loje ist Microsoft Student Partner (MSP) an der Universität Hamburg und Programmierer bei der Keep it simple GmbH in Hamburg. Für Fragen erreichen Sie ihn über seine Webseite www.dotnet-online.de.

Links und Literatur


Anzeige

Kommentare

zurück zum Seitenanfang