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Ü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:
gacutil.exe /ldl
C:\>gacutil /ldlMicrosoft (R) .NET Global Assembly Cache Utility. Version 1.1.4322.573Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.The cache of downloaded files contains the following entries:NDoc.Documenter.Xml, Version=1.2.1303.41469, Culture=neutral, PublicKeyToken=b9896512f28c0f09, Custom=nullInterop.MSHelpCompiler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b9896512f28c0f09, Custom=nullNDoc.Documenter.Msdn, Version=1.2.1303.41460, Culture=neutral, PublicKeyToken=b9896512f28c0f09, Custom=nullNDoc.Documenter.JavaDoc, Version=1.2.1303.41464, Culture=neutral, PublicKeyToken=b9896512f28c0f09, Custom=nullRssCommentator, Version=1.0.1389.25555, Culture=neutral, PublicKeyToken=Number of items = 5
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].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
private static string[] GetCacheDirectories(string startDirectory){ArrayList results = new ArrayList();string[] directories = Directory.GetDirectories(startDirectory);foreach (string directory in directories){if (File.Exists(Path.Combine(directory, "__AssemblyInfo__.ini"))){results.Add(directory);}// Rekursiver Aufrufresults.AddRange(GetCacheDirectories(directory));}return (string[])results.ToArray(typeof(string));}
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
public struct CachedAssemblyInfo{public string DisplayName;public string Version;public string Culture;public string CodeBase;public string PublicKeyToken;public string LocalCacheDirectory;public CachedAssemblyInfo(string filePath, string localCacheDirectory){const string INI_SECTION = "AssemblyInfo";AdcUtil.IO.IniFile ini = new AdcUtil.IO.IniFile(filePath);string buffer = ini.Read(INI_SECTION, "DisplayName", "");this.DisplayName = buffer.Substring(0, buffer.IndexOf(','));this.Version = ExtractFromString(buffer, "Version");this.Culture = ExtractFromString(buffer, "Culture");this.PublicKeyToken = ExtractFromString(buffer, "PublicKeyToken");this.CodeBase = ini.Read(INI_SECTION, "URL", "");this.LocalCacheDirectory = localCacheDirectory;}private static string ExtractFromString(string buffer, string key){string[] parts = buffer.Split(',');foreach (string part in parts){int index = part.IndexOf('=');if (index != -1) // Erster Eintrag hat kein "="!{if (part.Substring(1, index - 1) == key) // Key ist case-sensitive!{return part.Substring(index + 1);}}}return String.Empty;}}
Listing 3
public static CachedAssemblyInfo[] GetCachedAssemblies(){if (Directory.Exists(UserCacheFolder)){string[] directories = GetCacheDirectories(UserCacheFolder);CachedAssemblyInfo[] results = new CachedAssemblyInfo[directories.Length];for (int i = 0; i < directories.Length; i++){results[i] = new CachedAssemblyInfo(Path.Combine(directories[i], "__AssemblyInfo__.ini"), "" + directories[i]);}return results;}else{return new CachedAssemblyInfo[0];}}
Der ADC-Pfad lässt sich folgendermaßen ermitteln:
public string UserCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"assembly\dl2");
CachedAssemblyInfo[] assemblies = AssemblyDownloadCache.GetCachedAssemblies();foreach (CachedAssemblyInfo assembly in assemblies){ListViewItem item = new ListViewItem();item.Text = assembly.DisplayName;item.SubItems.Add(assembly.Version);item.SubItems.Add(assembly.PublicKeyToken);item.SubItems.Add(assembly.Culture);item.SubItems.Add(assembly.CodeBase);item.SubItems.Add(assembly.LocalCacheDirectory);this.lvListView.Items.Add(item);}
public static void DeleteCachedAssembly(CachedAssemblyInfo asm){System.IO.Directory.Delete(asm.LocalCacheDirectory, true);}
public static void DeleteCache(){System.IO.Directory.Delete(UserCacheFolder, true);}
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
- [1] No-Touch Deployment in the .NET Framework (Juli 2002): msdn.microsoft.com/library/default.asp?url=/library/en-us/
dv_vstechart/html/vbtchno-touchdeploymentinnetframework.asp - [2] Global Assembly Cache (GAC): msdn.microsoft.com/library/default.asp?url=/library/
en-us/cpguide/html/cpconGlobalAssemblyCache.asp - [3] URL Monikers: msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/monikers_3m9f.asp
- [4] About WinINet: msdn.microsoft.com/library/default.asp?url=/library/en-us/wininet/wininet/about_wininet.asp
- [5] Assembly Cache Viewer (Shfusion.dll): msdn.microsoft.com/library/default.asp?url=/library/en-us/
cptools/html/cpgrfassemblycacheviewershfusiondll.asp - [6] Global Assembly Cache Tool (Gacutil.exe): msdn.microsoft.com/library/default.asp?url=/library/
en-us/cptools/html/cpgrfglobalassemblycacheutilitygacutilexe.asp - [7] John Renaud, Undocumented Fusion: www.codeproject.com/dotnet/undocumentedfusion.asp?target=Fusion
- [8] Assembly Download Cache Utility (Adcutil.exe): www.dotnet-online.de/go/?id=adcutil


