How to Force All Referenced Assemblies to Be Loaded into the App Domain

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

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.

How to force loading of an referenced assembly that is not explicitly used by the project

Answer based on the comment from TnTinMn. Thanks for the suggestion!

  • Change the static Program class (Entry point of the host app) to be a partial class
  • Create a TextTemplate/T4 file named Program.AssemblyLoader.tt
  • Add the following content:

Program.AssemblyLoader.tt:

<#@ template language="C#" hostSpecific="true" debug="True" #>
<#@ output extension="cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDte" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System" #>
<#

var visualStudio = (EnvDTE.DTE)(Host as IServiceProvider).GetService(typeof(EnvDTE.DTE));
var solution = (EnvDTE.Solution)visualStudio.Solution;
var projects = solution.Projects;

// Assume that the first item is the main project, the host app.
// Index is **NOT** zero-based!
var main = (EnvDTE.Project)projects.Item(1);
var p = main.Properties.Item("DefaultNamespace");
var ns = p.Value.ToString();

#>
using System;
using System.Linq;
using System.IO;
using System.Reflection;

namespace <# WriteLine(ns); #>
{
static partial class Program
{
static Program()
{
//! Generated file. Do not edit. Check the .tt file instead!
<#
foreach(EnvDTE.Project proj in projects)
{
if(proj.Properties == null || proj.Properties.Item("OutputFileName") == null)
continue;

EnvDTE.Property p2 = proj.Properties.Item("OutputFileName") as EnvDTE.Property;
WriteLine(" LoadAssemblyIfNecessary(\"" + p2.Value + "\");");
}
#>
}

private static readonly AssemblyName[] REFS = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
private static readonly string APP = Path.GetFileName(System.Windows.Forms.Application.ExecutablePath);

private static void LoadAssemblyIfNecessary(string name)
{
if (string.Equals(name, APP, StringComparison.InvariantCultureIgnoreCase)) return;

if (!REFS.Any(x => x.Name.StartsWith(Path.GetFileNameWithoutExtension(name), StringComparison.InvariantCultureIgnoreCase)))
{
AppDomain.CurrentDomain.Load(Path.GetFileNameWithoutExtension(name));
}
}
}
}

Example for the generated Program.AssemblyLoader.cs:

using System;
using System.Linq;
using System.IO;
using System.Reflection;

namespace Your.Name.Space
{
static partial class Program
{
static Program()
{
//! Generated file. Do not edit. Check the .tt file instead!
LoadAssemblyIfNecessary("HostApp.dll");
LoadAssemblyIfNecessary("Dependency1.dll");
LoadAssemblyIfNecessary("Dependency2.dll");
LoadAssemblyIfNecessary("Modul1.exe");
}

private static readonly AssemblyName[] REFS = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
private static readonly string APP = Path.GetFileName(System.Windows.Forms.Application.ExecutablePath);

private static void LoadAssemblyIfNecessary(string name)
{
if (string.Equals(name, APP, StringComparison.InvariantCultureIgnoreCase)) return;

if (!REFS.Any(x => x.Name.StartsWith(Path.GetFileNameWithoutExtension(name), StringComparison.InvariantCultureIgnoreCase)))
{
AppDomain.CurrentDomain.Load(Path.GetFileNameWithoutExtension(name));
}
}
}
}

Ensure early loading of (some) referenced assemblies

In my opinion, assembly analysis with Mono.Cecil is useful here. For example you can enumerate references (of course without ones deleted from project by compiler) without loading given assembly and any of its references by:

var assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly("protobuf-net.dll");
foreach(var module in assembly.Modules)
{
foreach(var reference in module.AssemblyReferences)
{
Console.WriteLine(reference.FullName);
}
}

Such code prints:

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Runtime.Serialization, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

However IMO better idea is to scan through directory, possibly containing plugins and check them for your attribute using Cecil and therefore without loading (because you do not know at that point whether you want to load them at all):

var assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly("protobuf-net.dll");
foreach(var attribute in assembly.CustomAttributes)
{
Console.WriteLine(attribute.AttributeType.FullName);
}

There are couple options for comparing your attribute (which is a class from loaded assembly) to Cecil's attribute description, full type name is one of them.

How are assemblies loaded into the AppDomain?

  1. no, you are guaranteed the opposite: only assemblies directly needed at start are loaded.
  2. yes, there will be attempt to load assembly as soon as some class will require type information from that assembly or code that needs that assembly will be JITed.
  3. no, there is no way at compile time to force assembly load sequence short of referencing something from each assembly in your Main (note that usually goal is opposite - to delay loading of as many assemblies as possible to speed up loading of an app).
  4. no, you can't control automatic loading (as Simon Edström points out there Assembly Resolve event fired when CLR decides it needs an assembly). You can always pre-load assemblies yourself if you know dependencies.

Note: assemblies don't "depend" on each other directly, just classes/methods in each depend on each other.

a correct way of forcing assembly load into current domain

Your existing code is the best way to do that (AFAIK).

To get rid of the warning, change it to

typeof(AssemblyB.AssemblyB_Type).ToString();

Force C# to load an assembly that is referenced only in a cshtml file

You are right, the compiler deletes the project reference during compilation/optimization, because it detects that it's not really being used by the code.

The official way to tell the runtime to load the assembly anyway by configuration, is to add a reference in your web.config file inside the <compilation> section

<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="Newtonsoft.Json" />
</assemblies>
</compilation>

That being said, sometimes the web.config is not available, or I want to avoid bloating it with more and more lines, so I personally prefer the hack of making a one line reference in the code: (yes, a dirty bad nasty & silly hack, but I can't find much harm being done).

class LoadNewtonSoftHack
{
private void DoNothing()
{
var o = Newtonsoft.Json.Linq.JValue.Parse("{}");
}
}

A third alternative would be to force the assembly load when the web app starts: (but ensure that your build copies the .dll in your bin\ folder).

Assembly assembly = Assembly.LoadFrom("Newtonsoft.Json.dll");

Load different version of assembly into separate AppDomain

I think your problem is the following:

Assembly asm = pluginDomain.GetAssemblies().First(a => a.GetName().Name == "Plugin");
Type p = asm.GetTypes().First(t => t.GetInterfaces().Contains(typeof(IPlugin)));
var pluginInstance = (IPlugin)pluginDomain.CreateInstanceAndUnwrap(asm.FullName, p.FullName);

With this you're loading the updated/outdated type references into the main AppDomain (which has the assembly already loaded in a different version).

I recommend you use the following approach:

  1. Create a separate assembly that contains the contracts/Interfaces. This Assembly is fixed and remains at a specific version all the time and it can never be outdated and each version of your plugin can reference it.
  2. Write an assembly loader that is part of your host application, but in a seperate assembly as well. This assembly loader assembly must not have any reference to the rest of your application so you can set it up in the new appdomain.
  3. After setting up the new appdomain, load an instance of your AssemblyLoader. This one loads the plugin assemblies and interacts with it.
  4. Your application doesn't interact with the assembly itself. Your application calls your AssemblyLoader via the appdomain boundary and the AssemblyLoader calls the Plugin

I can't say if this is the best version possible, but this one works pretty well for me in an environment where each plugin can load any version of any assembly. With this setup you're pretty much resistant to changing versions or updates and each plugin can use the version it wants.

Let me know if it works for you.



Related Topics



Leave a reply



Submit