95

My projects are set up like this:

  • Project "Definition"
  • Project "Implementation"
  • Project "Consumer"

Project "Consumer" references both "Definition" and "Implementation", but does not statically reference any types in "Implementation".

When the application starts, Project "Consumer" calls a static method in "Definition", which needs to find types in "Implementation"

Is there a way I can force any referenced assembly to be loaded into the App Domain without knowing the path or name, and preferably without having to use a full-fledged IOC framework?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Daniel Schaffer
  • 56,753
  • 31
  • 116
  • 165

10 Answers10

102

This seemed to do the trick:

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))));

As Jon noted, the ideal solution would need to recurse into the dependencies for each of the loaded assemblies, but in my specific scenario I don't have to worry about it.


Update: The Managed Extensibility Framework (System.ComponentModel) included in .NET 4 has much better facilities for accomplishing things like this.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Daniel Schaffer
  • 56,753
  • 31
  • 116
  • 165
  • 6
    This doesnt work for me, my referenced assemblies, that isnt loaded, does not show up in AppDomain.CurrentDomain.GetAssemblies().. Hmm... – Ted Nov 11 '13 at 14:39
  • 11
    What facilities? I haven't found anything via searching. – marknuzz Feb 13 '14 at 01:26
  • 10
    Using MEF, the above code can be shortened to: `new DirectoryCatalog(".");` (requires referencing `System.ComponentModel.Composition`). – Allon Guralnek Aug 09 '15 at 07:02
  • 2
    This answer solved my problem and worked for me.I had a MS unit test project referencing another one of my assemblies and AppDomain.CurrentDomain.GetAssemblies() was not returning that assembly when running a test. I suspect that even though my unit tests were using code from that library the assembly may not have been showing up in "GetAssemblies" because of the way vs.net loads up a MS unit test project (class library) as compared to running a regular .exe application. Something to keep in mind if your code uses reflection and is failing your unit tests. – Dean Lunz Jun 22 '16 at 19:22
  • 8
    Just wanted to add, be careful of dynamically loaded assemblies The invoked member is not supported in a dynamic assembly. Either filter out assemblies where IsDynamic = false, or if you can be fault tolerant of loads, try/catch your call to CurrentDomain.Load. And `assembly.Location`. That one also needs to be checked. Doesn't work for `IsDynamic` assemblies. – Eli Gassert Mar 24 '17 at 09:49
  • It is so ugly, that I tried anything to avoid using it. Yet, it works, and after hours of trial-error have I found out why. Could have saved those hours... – Kobor42 Feb 18 '22 at 04:17
68

You can use Assembly.GetReferencedAssemblies to get an AssemblyName[], and then call Assembly.Load(AssemblyName) on each of them. You'll need to recurse, of course - but preferably keeping track of assemblies you've already loaded :)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I found that, but the problem is that I have to do whatever I'm doing from the referenced assembly... and at least in the context of a unit test, GetCallingAssembly, GetExecutingAssembly of course return the referenced assembly, and GetEntryAssembly returns null :\ – Daniel Schaffer Mar 05 '10 at 04:45
  • 4
    If you are after loading reference assemblies then the above will solve your problem. You can also ask a specific type typeof(T).Assembly if that helps. I have a feeling that what you need is to dynamically load the assemblies that contain the implementation (not referenced). If this is the case, you will have to either keep a static list of name and load them manually or going through your entire directory, load and then find the type with the right interfaces. – Fadrian Sudaman Mar 05 '10 at 04:58
  • It seems strange that this kind of functionality isn't built into the BCL - this post is rather old, anyone aware of something like this being added to the framework since then? – mfeineis Jan 14 '15 at 12:01
  • 1
    @vanhelgen: It's rarely something you need to explicitly, in my experience. Normally the CLR's "load on demand" works fine. – Jon Skeet Jan 14 '15 at 12:05
  • 3
    Under normal circumstances that may be true but when using a DI container to discover available services (via `System.Reflection`) it naturally doesn't find the services contained within assemblies that have not been loaded yet. My default approach ever since has been to create a dummy sub class from a random type of every referenced assembly in the CompositionRoot of my app to make sure all dependencies are in place. I hope I can skip this nonesense by loading everything upfront, even at the cost of further increasing startup time. @JonSkeet is there another way to do this? thx – mfeineis Jan 14 '15 at 12:26
  • @vanhelgen: I'd expect that if the DI container needs to find types, *it* would look at the referenced assemblies - or assemblies in a particular directory. But otherwise, the technique in my answer should be okay. – Jon Skeet Jan 14 '15 at 12:43
  • @JonSkeet: I forgot to mention, that I supply the assemblies to the [SimpleInjector](http://simpleinjector.codeplex.com) by myself - the container doesn't include this functionality by design. So it isn't really the DI container's responsibility but my own. But still, everyone that wants to handle these cases needs to implement this assembly discovery and loading by themselves. Although I concur that isn't exactly a common use case in day to day business :-) – mfeineis Jan 14 '15 at 12:50
  • @vanhelgen: Right, so if you just know *a* type in each assembly, you can just use `typeof(Foo).Assembly` etc. There's no need for a "dummy" class anywhere. – Jon Skeet Jan 14 '15 at 12:56
  • @JonSkeet: that is another possibility but it doesn't save me the work to choose a type per assembly and maintain that list manually *g* - thx anyways, pleasure chatting with you! – mfeineis Jan 14 '15 at 13:02
  • 16
    Where this falls down is that GetReferencedAssemblies apparently returns an "optimised" list - so if you don't explicitly call code in a referenced assembly, it won't be included. (Per [this discussion](https://social.msdn.microsoft.com/Forums/vstudio/en-US/17f89058-5780-48c5-a43a-dbb4edab43ed/getreferencedassemblies-not-returning-complete-list?forum=netfxbcl)) – FTWinston Jul 12 '18 at 09:27
24

just wanted to share a recursive example. I'm calling the LoadReferencedAssembly method in my startup routine like this:

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    this.LoadReferencedAssembly(assembly);
}

This is the recursive method:

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));
        }
    }
}
jmelhus
  • 1,130
  • 2
  • 12
  • 28
  • 5
    I wonder if circular assembly references could cause stack overflow exceptions to be thrown. – Ronnie Overby Sep 19 '16 at 16:07
  • 1
    Ronnie, I believe not, the code only runs the recursion in case `name` is not in already `AppDomain.CurrentDomain.GetAssemblies()`, meaning that it will only recur if the `foreach` picked `AssemblyName` is not yet loaded. – Felype Mar 29 '17 at 17:19
  • 1
    I'm not happy about the `O(n^2)` runtime of this algorithm (`GetAssemblies().Any(...)` inside a `foreach`)). I'd use a `HashSet` to bring this down to something on the order of `O(n)`. – Dai Nov 26 '18 at 15:23
16

If you use Fody.Costura, or any other assembly merging solution, the accepted answer will not work.

The following loads the Referenced Assemblies of any currently loaded Assembly. Recursion is left to you.

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)));
Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • Care to advise where this snippet should go? – Telemat Jun 12 '14 at 18:29
  • 1
    in your boot loader / start-up I imagine. – Meirion Hughes Jun 12 '14 at 19:40
  • 1
    I may be wrong, but I think you can just check for `!y.IsDynamic` in your `.Where` – Felype Apr 04 '17 at 13:27
  • 1
    This code snippet does not work. The initial `var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();` does not return the non-used assemblies and thus will not propagate them at all, in my tests. – Ted Feb 18 '21 at 11:01
  • yeah that's the recursive comment I made- needing to load dependencies of dependencies, etc. - its been many years since I've looked into this stuff, perhaps there is a better way now. – Meirion Hughes Feb 18 '21 at 11:40
2

Seeing as I had to load an assembly + dependencies from a specific path today I wrote this class to do it.

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);
    }
}
John M. Wright
  • 4,477
  • 1
  • 43
  • 61
Peter Morris
  • 20,174
  • 9
  • 81
  • 146
  • 1
    good code, except there's no need for the Dictionary, in this case a simple list would suffice. I'm assuming your original code had a need to know which assemblies were loaded and which ones were not, which is why you have the Dictionary. – Reinis Jun 03 '19 at 05:47
1

For getting referenced assembly by name you can use following method:

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;
}
Petr Voborník
  • 1,249
  • 1
  • 14
  • 11
0

Yet another version (based on Daniel Schaffer answer) is the case when you might not need to load all Assemblies, but a predefined number of them:

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!");
}
Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
0

If you have assemblies where no code is referenced at compile time, those assemblies will not be included as a reference to your other assembly, even if you have added the project or nuget package as a reference. This is regardless of Debug or Release build settings, code optimization, etc. In these cases, you must explicitly call Assembly.LoadFrom(dllFileName) to get the assembly loaded.

jjxtra
  • 20,415
  • 16
  • 100
  • 140
0

In my winforms application I give JavaScript (in a WebView2 control) the possibility to call various .NET things, for example methods of Microsoft.VisualBasic.Interaction in the assembly Microsoft.VisualBasic.dll (such as InputBox() etc).

But my application as such does not use that assembly, so the assembly is never loaded.

So to force the assembly to load, I ended up simply adding this in my 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
}

The compiler thinks that the assembly might be needed, but in reality this never happens of course.

Not a very sophisticated solution, but quick and dirty.

Magnus
  • 1,584
  • 19
  • 14
-1

I created my own based on @Jon Skeet answer with name prefix filtering to avoid loading unnecessary assemblies:

public static IEnumerable<Assembly> GetProjectAssemblies(string prefixName)
{
    var assemblies = new HashSet<Assembly>
    {
        Assembly.GetEntryAssembly()
    };

    for (int i = 0; i < assemblies.Count; i++)
    {
        var assembly = assemblies.ElementAt(i);

        var referencedProjectAssemblies = assembly.GetReferencedAssemblies()
            .Where(assemblyName => assemblyName.FullName.StartsWith(prefixName))
            .Select(assemblyName => Assembly.Load(assemblyName));

        assemblies.UnionWith(referencedProjectAssemblies);
    }

    return assemblies;
}
Francisco
  • 431
  • 9
  • 16
  • In this case it probably doesn't matter, but I just wanted to point out that calling `ElementAt(i)` on a set will iterate it until it reaches the `i`th element, thefore causing the code above to be _O(N^2)_ overall. I would suggest using `foreach(var assembly in assemblies)`. – vyrp Apr 05 '21 at 06:37
  • @vyrp nop: System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' – Francisco Jun 15 '21 at 23:59
  • Ah, I didn't realize that `assemblies` was being modified. Then yeah, an exception would be thrown with my proposal. However, doesn't the code rely on undocumented behavior? The elements of a `HashSet` are not guaranteed to be in a particular order. After adding new elements with `UnionWith`, can't the elements move around and `ElementAt(i)` not return what you are expecting? You are using the `HashSet` almost like a `Queue` without duplicates. – vyrp Jun 16 '21 at 09:41
  • I always use hashset as that and it never failed haha. Do you have suggestions? – Francisco Jun 17 '21 at 00:00
  • This wont work if the first references in the chain of references isnt used. So: MainApplications references: ReferenceA --> ReferenceB --> ReferenceC If a class in ReferenceA is _not_ used in the MainApplication, any calls GetReferencedAssemblies() will not return ReferenceA, and this not ReferenceB or ReferenceC either. – Ted Aug 18 '21 at 14:58