Moje projekty są ustawione w następujący sposób:
- Definicja projektu"
- Wdrożenie projektu"
- Projekt „Konsument”
Projekt „Konsument” odwołuje się zarówno do „Definicji”, jak i „Wdrażania”, ale nie zawiera statycznego odniesienia do żadnego typu w polu „Wdrożenie”.
Po uruchomieniu aplikacji Projekt „Konsument” wywołuje metodę statyczną w „Definicji”, która musi znaleźć typy w polu „Implementacja”
Czy istnieje sposób, w jaki mogę wymusić załadowanie dowolnego zestawu, do którego się odwołujemy, do domeny aplikacji bez znajomości ścieżki lub nazwy, a najlepiej bez konieczności korzystania z pełnoprawnej struktury IOC?
c#
assemblies
appdomain
Daniel Schaffer
źródło
źródło
Odpowiedzi:
Wydawało się, że to działa:
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))));
Jak zauważył Jon, idealne rozwiązanie wymagałoby ponownego użycia zależności dla każdego z załadowanych zestawów, ale w moim scenariuszu nie muszę się tym martwić.
Aktualizacja: Managed Extensibility Framework (System.ComponentModel) zawarty w .NET 4 ma znacznie lepsze możliwości wykonywania takich rzeczy.
źródło
new DirectoryCatalog(".");
(wymaga odwołaniaSystem.ComponentModel.Composition
).assembly.Location
. To też trzeba sprawdzić. Nie działa w przypadkuIsDynamic
zespołów.Możesz użyć,
Assembly.GetReferencedAssemblies
aby uzyskaćAssemblyName[]
, a następnie zadzwonićAssembly.Load(AssemblyName)
do każdego z nich. Oczywiście będziesz musiał powtórzyć - ale najlepiej śledzić zestawy, które już załadowałeś :)źródło
System.Reflection
) naturalnie nie znajduje usług zawartych w zestawach, które nie zostały jeszcze załadowane. Od tamtej pory moim domyślnym podejściem jest utworzenie fałszywej podklasy z losowego typu każdego zestawu, do którego się odwołujemy w CompositionRoot mojej aplikacji, aby upewnić się, że wszystkie zależności są na miejscu. Mam nadzieję, że uda mi się pominąć ten nonsens, ładując wszystko z góry, nawet kosztem dalszego wydłużania czasu uruchamiania. @JonSkeet czy jest inny sposób na zrobienie tego? thxchciałem tylko udostępnić przykład rekurencyjny. Wywołuję metodę LoadReferencedAssembly w mojej procedurze uruchamiania w następujący sposób:
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { this.LoadReferencedAssembly(assembly); }
Oto metoda rekurencyjna:
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)); } } }
źródło
name
nie maAppDomain.CurrentDomain.GetAssemblies()
, co oznacza, że powtórzy się tylko wtedy, gdyforeach
wybranyAssemblyName
nie jest jeszcze załadowany.O(n^2)
czas działania tego algorytmu (GetAssemblies().Any(...)
wewnątrz aforeach
)). Użyłbym,HashSet
żeby sprowadzić to do czegoś w kolejnościO(n)
.Jeśli korzystasz z Fody.Costura lub innego rozwiązania scalającego montaż, zaakceptowana odpowiedź nie zadziała.
Poniższe czynności ładują zestawy, do których istnieją odniesienia, dowolnego aktualnie załadowanego zestawu. Rekurencja jest pozostawiona Tobie.
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)));
źródło
!y.IsDynamic
w swoim.Where
Widząc, że musiałem dziś załadować zestaw + zależności z określonej ścieżki, napisałem tę klasę, aby to zrobić.
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); } }
źródło
Jeszcze inna wersja (oparta na odpowiedzi Daniela Schaffera ) to przypadek, w którym może nie być konieczne ładowanie wszystkich zestawów, ale wstępnie zdefiniowana ich liczba:
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!"); }
źródło
Jeśli masz zestawy, do których nie odwołuje się żaden kod w czasie kompilacji, te zestawy nie zostaną uwzględnione jako odwołanie do innego zestawu, nawet jeśli dodano projekt lub pakiet NuGet jako odwołanie. Jest to niezależnie od tego,
Debug
czyRelease
budować ustawień, optymalizacja kodu, itp w tych przypadkach, trzeba jawnie wywołaćAssembly.LoadFrom(dllFileName)
dostać zespół załadowany.źródło
Aby uzyskać odwołanie do zestawu według nazwy, możesz użyć następującej metody:
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; }
źródło
W mojej aplikacji winforms daję JavaScript (w kontrolce WebView2) możliwość wywołania różnych rzeczy .NET, na przykład metod
Microsoft.VisualBasic.Interaction
w zestawie Microsoft.VisualBasic.dll (takich jakInputBox()
itp.).Ale moja aplikacja jako taka nie używa tego zestawu, więc zestaw nigdy nie jest ładowany.
Aby wymusić załadowanie zestawu, po prostu dodałem to w moim 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 }
Kompilator uważa, że montaż może być potrzebny, ale w rzeczywistości tak się nigdy nie dzieje.
Niezbyt wyszukane rozwiązanie, ale szybkie i brudne.
źródło