Home > front end >  Framework Service Registration | Registration Class Not Found When Using Reflection | AppDomain Ques
Framework Service Registration | Registration Class Not Found When Using Reflection | AppDomain Ques

Time:11-09

I'm implementing a framework for C# on ASP.NET using Dotnet 6. I want part of the framework to be extensible by outside parties; they just need to implement a few classes and we can integrate their work via Nuget or direct assembly reference.

Part of what they need to complete is a Registration Class so they can define how their engine should be registered with the dependency injection container. This is an example of what I'd expect 3rd parties to supply:

public class EchoServiceRegistration : IRegisterDI
{
  public IServiceCollection Register(IServiceCollection serviceCollection)
  {
    serviceCollection.TryAddSingleton<EchoEngine>();
    return serviceCollection;
  }
}

In the consuming application, I'm looking for all of the classes that implement the IRegisterDI interface using the AppDomain class, which is a riff off this SO answer https://stackoverflow.com/a/26750/2573109. (Note: I switched to using name-based matching just for troubleshooting and will change it back to a better implementation once properly solved):

List<Type> allRegistrationClasses = AppDomain
  .CurrentDomain
  .GetAssemblies()
  .SelectMany(it => it.GetTypes())
  .Where(tp => tp.GetInterfaces()
    .Any(inter => inter.Name == nameof(IRegisterDI)))
  .ToList();

As written, this returns 0 types.

On the next troubleshooting iteration, I proved that the registration class is available to the caller. So, I manually created an instance of the EchoServiceRegistration class, as seen below. This time, the AppDomain contains a single entry for EchoServiceRegistration (as expected).

var allRegistrationClasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()).Where(tp => tp.GetInterfaces().Any(inter => inter.Name == nameof(IRegisterDI))).ToList();
var echoServiceRegistration = new EchoServiceRegistration();
echoServiceRegistration.Register(builder.Services);

if (allRegistrationClasses.Count is not 1) throw new InvalidOperationException(); // Does not throw an exception

To prove I didn't accidentally fix something, I commented out the two lines related to Echo Service Registration, and allRegistrationClasses again contains 0 records.

var allRegistrationClasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()).Where(tp => tp.GetInterfaces().Any(inter => inter.Name == nameof(IRegisterDI))).ToList();
// var echoServiceRegistration = new EchoServiceRegistration();
// echoServiceRegistration.Register(builder.Services);

if (allRegistrationClasses.Count is not 1) throw new InvalidOperationException(); // Throws an exception

My gut reaction is I don't understand how the AppDomain determines what assemblies to load. I started reading the documentation Microsoft provides about it, but it seems like a deep topic and I won't have a more clear understanding without quite a bit of dedicated reading.

How do I guarantee the full set of classes are available when calling this during the DI Container build? Please let me know if I can provide any additional detail or clarity.

CodePudding user response:

It looks to me like on your first test, the assembly is not loaded into the app domain because it's not used.

On your second test, you are manually creating an instance so you are forcing the loading of the assembly. Note that even if the assembly is added as a reference, it won't be loaded unless it's actually needed.

You need to explicitly load the assemblies for them to be scanned. One way of doing this would be to have all extension libraries live in a particular folder from which you can then load them before registration:

var files = Directory.GetFiles(@"C:\my-framework\extensions");
foreach (var file in files)
{
    var extension = Path.GetExtension(file);
    if(extension == ".dll")
    {
        var asm = Assembly.LoadFile(file);
        Console.WriteLine($"Loaded extension [{asm.FullName}]");
    }
}

Once the assemblies are loaded, you should get the desired results.

This is an overly-simplified example but it should be enough to get you going.

  • Related