Daniel Schaffer
Daniel Schaffer

Reputation: 57871

Is there a way to force all referenced assemblies to be loaded into the app domain?

My projects are set up like this:

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?

Upvotes: 100

Views: 94021

Answers (12)

Denis
Denis

Reputation: 3759

There is LoadAssembliesOnStartup package in NuGet manager. This may be what you need.

Upvotes: 0

CajunCoding
CajunCoding

Reputation: 969

If you'd like a simplified version of the prior examples packaged neatly as an Extension method for the AppDomain.CurrentDomain here's one that makes it easy to specify any/all (via params array) referenced assemblies you want to force the loading of (if not already loaded)....

This extension method is netstandard2.0 compatible...

Implemented as:

//Ensure that the assembly is loaded (since it is dynamically accessed and may not yet be initialized)...
AppDomain.CurrentDomain.ForceLoadAssemblies("MyDynamicAssemblies.AssemblyName1", "MyDynamicAssemblies.AssemblyName2");

/// <summary>
/// Force load all or specified Assemblies via the filter params.
/// Adapted from original Stack Overflow answer here: https://stackoverflow.com/a/2384679/7293142
/// </summary>
/// <param name="appDomain"></param>
/// <param name="assemblyNames"></param>
public static void ForceLoadAssemblies(this AppDomain appDomain, params string[] assemblyNames)
{
    var loadedAssemblyPaths = appDomain.GetAssemblies()
        .Where(a => !a.IsDynamic)
        .Select(a => a.Location)
        .ToArray();
    
    var assemblyNamesHashSet = new HashSet<string>(assemblyNames, StringComparer.InvariantCultureIgnoreCase);
    var referencedPaths = Directory.GetFiles(appDomain.BaseDirectory, "*.dll");
    
    var pathsToLoad = referencedPaths
        .Where(p => 
            assemblyNamesHashSet.Count == 0 //<== Load ALL Assemblies
            || assemblyNamesHashSet.Contains(Path.GetFileName(p)) //<== Support Full File Name & Extension matches... 
            || assemblyNamesHashSet.Contains(Path.GetFileNameWithoutExtension(p))) //<== Support File Name only (no Extension) matches... 
        .Except(loadedAssemblyPaths, StringComparer.InvariantCultureIgnoreCase)
        .ToList();

    pathsToLoad.ForEach(p => appDomain.Load(AssemblyName.GetAssemblyName(p)));
}

Upvotes: 0

Daniel Schaffer
Daniel Schaffer

Reputation: 57871

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.

Upvotes: 105

Francisco
Francisco

Reputation: 450

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

Upvotes: -2

Magnus
Magnus

Reputation: 1764

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.

Upvotes: 0

Petr Voborn&#237;k
Petr Voborn&#237;k

Reputation: 1265

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

Upvotes: 0

jjxtra
jjxtra

Reputation: 21180

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.

Upvotes: 0

Arsen Khachaturyan
Arsen Khachaturyan

Reputation: 8360

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!");
}

Upvotes: 0

Peter Morris
Peter Morris

Reputation: 23284

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

Upvotes: 2

jmelhus
jmelhus

Reputation: 1140

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

Upvotes: 25

Meirion Hughes
Meirion Hughes

Reputation: 26448

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

Upvotes: 16

Jon Skeet
Jon Skeet

Reputation: 1504142

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

Upvotes: 70

Related Questions