Mis proyectos están configurados así:
- Definición del proyecto"
- Implementacion de proyecto"
- Proyecto "Consumidor"
El proyecto "Consumidor" hace referencia tanto a la "Definición" como a la "Implementación", pero no hace referencia estática a ningún tipo en "Implementación".
Cuando se inicia la aplicación, el proyecto "Consumidor" llama a un método estático en "Definición", que necesita encontrar tipos en "Implementación".
¿Hay alguna manera de forzar que cualquier ensamblado referenciado se cargue en el dominio de la aplicación sin conocer la ruta o el nombre, y preferiblemente sin tener que usar un marco IOC completo?
c#
assemblies
appdomain
Daniel Schaffer
fuente
fuente
Respuestas:
Esto parecía hacer el truco:
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray(); var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"); var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList(); toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
Como señaló Jon, la solución ideal tendría que recurrir a las dependencias para cada uno de los ensamblados cargados, pero en mi escenario específico no tengo que preocuparme por eso.
Actualización: El marco de extensibilidad administrado (System.ComponentModel) incluido en .NET 4 tiene instalaciones mucho mejores para lograr cosas como esta.
fuente
new DirectoryCatalog(".");
(requiere referenciaSystem.ComponentModel.Composition
).assembly.Location
. Ese también necesita ser verificado. No funciona paraIsDynamic
ensamblajes.Puede usar
Assembly.GetReferencedAssemblies
para obtener unAssemblyName[]
, y luego llamarAssembly.Load(AssemblyName)
a cada uno de ellos. Tendrá que recurrir, por supuesto, pero preferiblemente realizar un seguimiento de los ensamblados que ya ha cargado :)fuente
System.Reflection
), naturalmente no encuentra los servicios contenidos en ensamblados que aún no se han cargado. Mi enfoque predeterminado desde entonces ha sido crear una subclase ficticia a partir de un tipo aleatorio de cada ensamblado referenciado en CompositionRoot de mi aplicación para asegurarme de que todas las dependencias estén en su lugar. Espero poder omitir esta falta de sentido cargando todo por adelantado, incluso a costa de aumentar aún más el tiempo de inicio. @JonSkeet, ¿hay otra forma de hacer esto? thxsolo quería compartir un ejemplo recursivo. Estoy llamando al método LoadReferencedAssembly en mi rutina de inicio de esta manera:
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { this.LoadReferencedAssembly(assembly); }
Este es el método recursivo:
private void LoadReferencedAssembly(Assembly assembly) { foreach (AssemblyName name in assembly.GetReferencedAssemblies()) { if (!AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == name.FullName)) { this.LoadReferencedAssembly(Assembly.Load(name)); } } }
fuente
name
que no esté yaAppDomain.CurrentDomain.GetAssemblies()
, lo que significa que solo se repetirá si elforeach
elegidoAssemblyName
aún no está cargado.O(n^2)
tiempo de ejecución de este algoritmo (GetAssemblies().Any(...)
dentro de aforeach
)). Usaría unHashSet
para reducir esto a algo del orden deO(n)
.Si usa Fody.Costura o cualquier otra solución de fusión de ensamblajes, la respuesta aceptada no funcionará.
Lo siguiente carga los ensamblajes referenciados de cualquier ensamblaje cargado actualmente. La recursividad se te deja a ti.
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); loadedAssemblies .SelectMany(x => x.GetReferencedAssemblies()) .Distinct() .Where(y => loadedAssemblies.Any((a) => a.FullName == y.FullName) == false) .ToList() .ForEach(x => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(x)));
fuente
!y.IsDynamic
en su.Where
Viendo que tenía que cargar un ensamblado + dependencias de una ruta específica hoy, escribí esta clase para hacerlo.
public static class AssemblyLoader { private static readonly ConcurrentDictionary<string, bool> AssemblyDirectories = new ConcurrentDictionary<string, bool>(); static AssemblyLoader() { AssemblyDirectories[GetExecutingAssemblyDirectory()] = true; AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly; } public static Assembly LoadWithDependencies(string assemblyPath) { AssemblyDirectories[Path.GetDirectoryName(assemblyPath)] = true; return Assembly.LoadFile(assemblyPath); } private static Assembly ResolveAssembly(object sender, ResolveEventArgs args) { string dependentAssemblyName = args.Name.Split(',')[0] + ".dll"; List<string> directoriesToScan = AssemblyDirectories.Keys.ToList(); foreach (string directoryToScan in directoriesToScan) { string dependentAssemblyPath = Path.Combine(directoryToScan, dependentAssemblyName); if (File.Exists(dependentAssemblyPath)) return LoadWithDependencies(dependentAssemblyPath); } return null; } private static string GetExecutingAssemblyDirectory() { string codeBase = Assembly.GetExecutingAssembly().CodeBase; var uri = new UriBuilder(codeBase); string path = Uri.UnescapeDataString(uri.Path); return Path.GetDirectoryName(path); } }
fuente
Sin embargo, otra versión (basada en la respuesta de Daniel Schaffer ) es el caso en el que es posible que no necesite cargar todos los ensamblados, sino un número predefinido de ellos:
var assembliesToLoad = { "MY_SLN.PROJECT_1", "MY_SLN.PROJECT_2" }; // First trying to get all in above list, however this might not // load all of them, because CLR will exclude the ones // which are not used in the code List<Assembly> dataAssembliesNames = AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => AssembliesToLoad.Any(a => assembly.GetName().Name == a)) .ToList(); var loadedPaths = dataAssembliesNames.Select(a => a.Location).ToArray(); var compareConfig = StringComparison.InvariantCultureIgnoreCase; var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Where(f => { // filtering the ones which are in above list var lastIndexOf = f.LastIndexOf("\\", compareConfig); var dllIndex = f.LastIndexOf(".dll", compareConfig); if (-1 == lastIndexOf || -1 == dllIndex) { return false; } return AssembliesToLoad.Any(aName => aName == f.Substring(lastIndexOf + 1, dllIndex - lastIndexOf - 1)); }); var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList(); toLoad.ForEach(path => dataAssembliesNames.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path)))); if (dataAssembliesNames.Count() != AssembliesToLoad.Length) { throw new Exception("Not all assemblies were loaded into the project!"); }
fuente
Si tiene ensamblados en los que no se hace referencia a ningún código en tiempo de compilación, esos ensamblados no se incluirán como referencia a su otro ensamblado, incluso si ha agregado el proyecto o el paquete nuget como referencia. Esto es independientemente de
Debug
oRelease
configuración, optimización de código, etc. En estos casos la acumulación, debe llamar explícitamenteAssembly.LoadFrom(dllFileName)
para obtener el ensamblado cargado.fuente
Para obtener el ensamblaje referenciado por nombre, puede usar el siguiente método:
public static Assembly GetAssemblyByName(string name) { var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == name); if (asm == null) asm = AppDomain.CurrentDomain.Load(name); return asm; }
fuente
En mi aplicación winforms le doy a JavaScript (en un control WebView2) la posibilidad de llamar a varias cosas .NET, por ejemplo, métodos
Microsoft.VisualBasic.Interaction
en el ensamblado Microsoft.VisualBasic.dll (comoInputBox()
etc.).Pero mi aplicación como tal no usa ese ensamblado, por lo que el ensamblado nunca se carga.
Entonces, para forzar la carga del ensamblaje, terminé simplemente agregando esto en mi Form1_Load:
if (DateTime.Now < new DateTime(1000, 1, 1, 0, 0, 0)) { // never happens Microsoft.VisualBasic.Interaction.Beep(); // you can add more things here }
El compilador piensa que el ensamblaje podría ser necesario, pero en realidad esto nunca sucede, por supuesto.
No es una solución muy sofisticada, pero rápida y sucia.
fuente