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)); }
3 thoughts on “Force .NET and ASP.NET Core to Load Available Assemblies”