2009-09-03 8 views
108

Meine DLLs werden von einer Anwendung von Drittanbietern geladen, die wir nicht anpassen können. Meine Assemblies müssen sich in einem eigenen Ordner befinden. Ich kann sie nicht in GAC einfügen (meine Anwendung muss mit XCOPY bereitgestellt werden). Wenn die Root-DLL versucht, eine Ressource oder einen Typ von einer anderen DLL (im selben Ordner) zu laden, schlägt das Laden fehl (FileNotFound). Ist es möglich, den Ordner, in dem sich meine DLLs befinden, dem Assembly-Suchpfad programmgesteuert hinzuzufügen (aus der Stamm-DLL)? Ich darf die Konfigurationsdateien der Anwendung nicht ändern.Wie fügt man zur Laufzeit in .NET den Ordner zum Assembly-Suchpfad hinzu?

Antwort

45

Sie können der Datei CONFIG Ihrer Anwendung eine probing path hinzufügen. Dies funktioniert jedoch nur, wenn der Prüfpfad im Basisverzeichnis Ihrer Anwendung enthalten ist.

+3

Danke für das Hinzufügen. Ich habe die 'AssemblyResolve' Lösung so oft gesehen, gut um eine andere (und einfachere) Option zu haben. –

130

Klingt, als könnten Sie das AppDomain.AssemblyResolve-Ereignis verwenden und die Abhängigkeiten manuell von Ihrem DLL-Verzeichnis laden.

Edit (aus dem Kommentar):

AppDomain currentDomain = AppDomain.CurrentDomain; 
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder); 

static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args) 
{ 
    string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 
    string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); 
    if (!File.Exists(assemblyPath)) return null; 
    Assembly assembly = Assembly.LoadFrom(assemblyPath); 
    return assembly; 
} 
+2

Danke, Mattias! Dies funktioniert: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = neuer ResolveEventHandler (LoadFromSameFolderResolveEventHandler); statische Assembly LoadFromSameFolderResolveEventHandler (Objekt Absender, ResolveEventArgs Args) { Zeichenfolge Ordnerpfad = Path.GetDirectoryName (Assembly.GetExecutingAssembly(). Ort); Zeichenfolge assemblyPath = Path.Combine (folderPath, args.Name + ".dll"); Baugruppe assembly = Assembly.LoadFrom (assemblyPath); Rückholeinheit; } – isobretatel

+0

Große Lösung! Hier ist ein Artikel beschreibt einige andere Lösungen einschließlich dieser im Detail: http://kbalertz.com/897297/consume-assemblies-located-folder-different-application-folder-Visual-Basic.aspx (Warnung: es ist für VB , nicht C# und ein wenig alt) – Domenic

+0

Was würden Sie tun, wenn Sie auf den grundlegenden Resolver "Fallback" wollten. z.B. 'if (! File.Exists (asmPath)) return searchInGAC (...);' –

3

Blick in AppDomain.AppendPrivatePath (veraltet) oder AppDomainSetup.PrivateBinPath

+10

Von [MSDN] (http://msdn.microsoft.com/en-us/library/system.appdomainsetup.aspx): Ändern Die Eigenschaften einer AppDomainSetup-Instanz wirken sich nicht auf vorhandene AppDomains aus. Es kann nur die Erstellung einer neuen Anwendungsdomäne beeinflussen, wenn die CreateDomain-Methode mit der AppDomainSetup-Instanz als Parameter aufgerufen wird. – Nathan

+1

['AppDomain.AppendPrivatePath'] (https://msdn.microsoft.com/en-us/library/system.appdomain.appendprivatepath%28v=vs.110%29.aspx) scheint die Dokumentation darauf hindeuten, dass es sollte unterstützt die dynamische Erweiterung des Suchpfads 'AppDomain', nur dass das Feature veraltet ist. Wenn es funktioniert, ist es eine viel sauberere Lösung als "AssemblyResolve" zu überladen. – binki

8

Die beste Erklärung from MS itself:

AppDomain currentDomain = AppDomain.CurrentDomain; 
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler); 

private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args) 
{ 
    //This handler is called only when the common language runtime tries to bind to the assembly and fails. 

    //Retrieve the list of referenced assemblies in an array of AssemblyName. 
    Assembly MyAssembly, objExecutingAssembly; 
    string strTempAssmbPath = ""; 

    objExecutingAssembly = Assembly.GetExecutingAssembly(); 
    AssemblyName[] arrReferencedAssmbNames = objExecutingAssembly.GetReferencedAssemblies(); 

    //Loop through the array of referenced assembly names. 
    foreach(AssemblyName strAssmbName in arrReferencedAssmbNames) 
    { 
     //Check for the assembly names that have raised the "AssemblyResolve" event. 
     if(strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(","))) 
     { 
      //Build the path of the assembly from where it has to be loaded.     
      strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll"; 
      break; 
     } 

    } 

    //Load the assembly from the specified path.      
    MyAssembly = Assembly.LoadFrom(strTempAssmbPath);     

    //Return the loaded assembly. 
    return MyAssembly;   
} 
+0

'AssemblyResolve' ist für CurrentDomain, nicht gültig für eine andere Domäne' AppDomain.CreateDomain' – Kiquenet

11

Update für Framework 4

Seit Framework 4 die Assembl erhöhen yResolve-Ereignis auch für Ressourcen tatsächlich dieser Handler besser funktioniert. Es basiert auf dem Konzept, dass sich Lokalisationen in App-Unterverzeichnissen befinden (eines für die Lokalisierung mit dem Namen der Kultur, d. H. C: \ MyApp \ it für Italienisch). Darin befinden sich Ressourcen-Dateien. Der Handler funktioniert auch, wenn die Lokalisierung eine Länderregion ist, d.h. it-IT oder pt-BR. In diesem Fall könnte der Handler "mehrmals aufgerufen werden: einmal für jede Kultur in der Fallback-Kette" [von MSDN]. Dies bedeutet, dass, wenn wir null für die Ressourcendatei "it-IT" zurückgeben, das Framework das Ereignis auslöst und nach "it" fragt.

Ereignis Haken

 AppDomain currentDomain = AppDomain.CurrentDomain; 
     currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve); 

Eventhandler

Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 
    { 
     //This handler is called only when the common language runtime tries to bind to the assembly and fails. 

     Assembly executingAssembly = Assembly.GetExecutingAssembly(); 

     string applicationDirectory = Path.GetDirectoryName(executingAssembly.Location); 

     string[] fields = args.Name.Split(','); 
     string assemblyName = fields[0]; 
     string assemblyCulture; 
     if (fields.Length < 2) 
      assemblyCulture = null; 
     else 
      assemblyCulture = fields[2].Substring(fields[2].IndexOf('=') + 1); 


     string assemblyFileName = assemblyName + ".dll"; 
     string assemblyPath; 

     if (assemblyName.EndsWith(".resources")) 
     { 
      // Specific resources are located in app subdirectories 
      string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture); 

      assemblyPath = Path.Combine(resourceDirectory, assemblyFileName); 
     } 
     else 
     { 
      assemblyPath = Path.Combine(applicationDirectory, assemblyFileName); 
     } 



     if (File.Exists(assemblyPath)) 
     { 
      //Load the assembly from the specified path.      
      Assembly loadingAssembly = Assembly.LoadFrom(assemblyPath); 

      //Return the loaded assembly. 
      return loadingAssembly; 
     } 
     else 
     { 
      return null; 
     } 

    } 
6

Für C++/CLI-Benutzer, hier ist die Antwort ‚@Mattias S (was für mich funktioniert):

using namespace System; 
using namespace System::IO; 
using namespace System::Reflection; 

static Assembly ^LoadFromSameFolder(Object ^sender, ResolveEventArgs ^args) 
{ 
    String ^folderPath = Path::GetDirectoryName(Assembly::GetExecutingAssembly()->Location); 
    String ^assemblyPath = Path::Combine(folderPath, (gcnew AssemblyName(args->Name))->Name + ".dll"); 
    if (File::Exists(assemblyPath) == false) return nullptr; 
    Assembly ^assembly = Assembly::LoadFrom(assemblyPath); 
    return assembly; 
} 

// put this somewhere you know it will run (early, when the DLL gets loaded) 
System::AppDomain ^currentDomain = AppDomain::CurrentDomain; 
currentDomain->AssemblyResolve += gcnew ResolveEventHandler(LoadFromSameFolder); 
0

I habe @ Mattias S 'Lösung verwendet. Wenn Sie Abhängigkeiten aus dem gleichen Ordner tatsächlich auflösen möchten, sollten Sie versuchen, Assembly Speicherort zu verwenden, wie unten gezeigt. args.RequestAssembly sollte auf Nichtigkeit überprüft werden.

Verwandte Themen