2009-09-03 15 views
108

Mis archivos DLL son cargados por una aplicación de terceros, que no podemos personalizar. Mis ensamblajes deben estar ubicados en su propia carpeta. No puedo ponerlos en GAC (mi aplicación tiene un requisito para implementarse usando XCOPY). Cuando la DLL raíz intenta cargar el recurso o escribir desde otra DLL (en la misma carpeta), la carga falla (FileNotFound). ¿Es posible agregar la carpeta donde se encuentran mis archivos DLL a la ruta de búsqueda de ensamblados mediante programación (desde la DLL de raíz)? No tengo permiso para cambiar los archivos de configuración de la aplicación.¿Cómo agregar carpeta a la ruta de búsqueda del ensamblado en el tiempo de ejecución en .NET?

Respuesta

45

Puede agregar probing path al archivo .config de su aplicación, pero solo funcionará si la ruta de exploración está contenida en el directorio base de su aplicación.

+3

Gracias por agregar esto. He visto la solución 'AssemblyResolve' muchas veces, es bueno tener otra (y más fácil) opción. –

130

Parece que podría utilizar el evento AppDomain.AssemblyResolve y cargar manualmente las dependencias de su directorio DLL.

Editar (desde el comentario):

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

¡Gracias, Mattias! Esto funciona: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = new ResolveEventHandler (LoadFromSameFolderResolveEventHandler); Asamblea estática LoadFromSameFolderResolveEventHandler (remitente del objeto, ResolveEventArgs args) { string folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly(). Location); string assemblyPath = Path.Combine (folderPath, args.Name + ".dll"); Ensamblaje de ensamblaje = Assembly.LoadFrom (assemblyPath); conjunto de retorno; } – isobretatel

+0

¡Gran solución! Aquí hay un artículo que describe algunas otras soluciones, incluida esta en detalle: http://kbalertz.com/897297/consume-assemblies-located-folder-different-application-folder-Visual-Basic.aspx (warning: its for VB , no C# y un poco viejo) – Domenic

+0

¿Qué harías si quisieras "retroceder" al Resolver básico? p.ej. 'if (! File.Exists (asmPath)) return searchInGAC (...);' –

3

mirada en AppDomain.AppendPrivatePath (en desuso) o AppDomainSetup.PrivateBinPath

+10

Desde [MSDN] (http://msdn.microsoft.com/en-us/library/system.appdomainsetup.aspx): Cambiando las propiedades de una instancia de AppDomainSetup no afectan a ningún dominio de aplicación existente. Solo puede afectar la creación de un nuevo dominio de aplicación cuando se llama al método CreateDomain con la instancia de AppDomainSetup como parámetro. – Nathan

+1

['AppDomain.AppendPrivatePath'] (https://msdn.microsoft.com/en-us/library/system.appdomain.appendprivatepath%28v=vs.110%29.aspx) la documentación parece sugerir que debería soporte para expandir dinámicamente la ruta de búsqueda de 'AppDomain', solo que la característica está en desuso. Si funciona, es una solución mucho más limpia que sobrecargar 'AssemblyResolve'. – binki

8

La mejor explicación 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' es para CurrentDomain, no es válido para otro dominio' AppDomain.CreateDomain' – Kiquenet

11

Actualización para Framework 4

Dado que Framework 4 levanta el ensamblaje Evento yResolve también para recursos, en realidad este manejador funciona mejor. Se basa en el concepto de que las localizaciones se encuentran en los subdirectorios de la aplicación (uno para la localización con el nombre de la cultura, es decir, C: \ MyApp \ it para italiano) Dentro hay un archivo de recursos. El controlador también funciona si la localización es país-región, es decir it-IT o pt-BR. En este caso, el controlador "podría llamarse varias veces: una vez para cada cultura en la cadena alternativa" [de MSDN]. Esto significa que si devolvemos el valor nulo para el archivo de recursos "IT-IT", el marco plantea el evento que solicita el "it".

gancho Evento

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

Controlador de eventos

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

Para los usuarios de C++/CLI, aquí es la respuesta @Mattias S'(que funciona para mí):

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 usé la solución @Mattias S '. Si realmente desea resolver dependencias de la misma carpeta, intente utilizar Solicitud de ensamblaje ubicación, como se muestra a continuación. args.RequestingAssembly se debe comprobar la nulidad.

System.AppDomain.CurrentDomain.AssemblyResolve += (s, args) => 
{ 
    var loadedAssembly = System.AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName == args.Name).FirstOrDefault(); 
    if(loadedAssembly != null) 
    { 
     return loadedAssembly; 
    } 

    if (args.RequestingAssembly == null) return null; 

    string folderPath = Path.GetDirectoryName(args.RequestingAssembly.Location); 
    string rawAssemblyPath = Path.Combine(folderPath, new System.Reflection.AssemblyName(args.Name).Name); 

    string assemblyPath = rawAssemblyPath + ".dll"; 

    if (!File.Exists(assemblyPath)) 
    { 
     assemblyPath = rawAssemblyPath + ".exe"; 
     if (!File.Exists(assemblyPath)) return null; 
    } 

    var assembly = System.Reflection.Assembly.LoadFrom(assemblyPath); 
    return assembly; 
}; 
Cuestiones relacionadas