Force .NET and ASP.NET Core to Load Available Assemblies

This blog post explains a technique that causes .NET and ASP.NET applications to load all available assemblies into memory. You may want or need to use a technique like this if you use reflection to determine types to instantiate, specifically if the types do not seem to be available when you reflect.

Where it feels appropriate, rather than registering every type for dependency injection, I use reflection and attribution to determine types that implement services. For example, if want my system to apply certain JsonConverters automatically, then I implement an attribute to identify those JsonConverters.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;

public abstract class AttributeBase : Attribute
{
    // map the type of the attribute to a list of types that have that attribute.
    // static and cached to optimize performance.
    private static ConcurrentDictionary<Type, List<Type>> _types =
        new ConcurrentDictionary<Type, List<Type>>();

    public static Type[] GetTypesWithAttribute(Type attribute)
    {
        // Have assemblies already been scanned for this attribute.
        if (!_types.ContainsKey(attribute))
        {
            // no, create a new entry in the dictionary mapping
            // the attribute to the list of types that have that attribute.
            List<Type> result = new List<Type>();

            // for each assembly available
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    // for each type in the assembly
                    foreach (Type type in assembly.GetTypes())
                    {
                        // if the type has the attribute
                        if (type.GetCustomAttributes(attribute, true).Length > 0)
                        {
                            // then add it to the list of types for the attribute
                            result.Add(type);
                        }
                    }
                }
                catch (Exception)
                {
                    // ignore; unable to load; assembly may be obfuscated, etc. 
                }
            }

            // store a record mapping the attribute to the list of types with that attribute
            _types[attribute] = result;
        }

        // return the list of types with the attribute
        return _types[attribute].ToArray();
    }
}

...

using System;
using System.Diagnostics;
using System.Reflection;

/// <summary>
/// Attribute to decorate JsonConverters, mainly to manage modular blocks,
/// so that SerializationConfigurator can add them to the JsonSerializerSettings
/// exposed by ContentstackClient and used to instantiate objects from Contentstack JSON.
/// [AutoLoadJsonConverter(true)]
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AutoLoadJsonConverter : AttributeBase
{
    public bool Enabled { get; }

    public AutoLoadJsonConverter(bool enabled = true)
    {
        Enabled = enabled;
    }
    
    public static bool IsEnabledForType(Type t)
    {
        Trace.Assert(t != null, "t is null");

        foreach (var attr in t.GetCustomAttributes(typeof(AutoLoadJsonConverter)))
        {
            AutoLoadJsonConverter ctdAttr = attr as AutoLoadJsonConverter;
            Trace.Assert(ctdAttr != null, "cast is null");

            if (!ctdAttr.Enabled)
            {
                return ctdAttr.Enabled;
            }
        }

        return true;
    }
}

Here is an example signature for a JsonConverter decorated with the attribute.

[AutoLoadJsonConverter(true)]
public class MarkupFieldJsonConverter : JsonConverter<MarkupField>
{
    public override MarkupField Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options)
{
    return new MarkupField(reader.GetString());
}

public override void Write(
    Utf8JsonWriter writer, 
    MarkupField value, 
    JsonSerializerOptions options)
{
    throw new NotImplementedException();
}

When I need these JsonConverters, I iterate the types that have that attribute.

foreach (Type type in 
    AutoLoadJsonConverter.GetTypesWithAttribute(typeof(AutoLoadJsonConverter)))
{
    if (AutoLoadJsonConverter.IsEnabledForType(type))
    {
        options.Converters.Add(Activator.CreateInstance(type) as JsonConverter);
    }
}

Lately (possibly since .NET 5, but more likely since moving the code that interrogates the attributes from the project that contains the types to interrogate to a separate project), .NET does not seem to find some of the attributed types in my assemblies. Apparently this is some sort of (pre-)optimization, as loading an assembly takes a cycle or two. Maybe there is a setting in the project or elsewhere to control this, but I could not find it.

You can use code such as the following to force .NET to load an assembly, replacing HomePage with a type in the assembly that you want to load.

Type ignoreMe = typeof(HomePage);

This approach is too explicit – I might not know the types in the assemblies or the names of the assemblies. Therefore, I added the following logic to the Configure() method in my Startup class to load all available assemblies. Note that it is probably best to avoid loading the same assembly twice. This code likely is not perfect.

foreach (string name in Directory.GetFiles(
    AppDomain.CurrentDomain.BaseDirectory, "*.dll").Where(r => 
        !AppDomain.CurrentDomain.GetAssemblies().ToList().Select(a => 
            a.Location).ToArray().Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList())
{
    Assembly.Load(Path.GetFileNameWithoutExtension(new FileInfo(name).Name));
}

One thought on “Force .NET and ASP.NET Core to Load Available Assemblies

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: