Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Registering All Classes That Implement Interface At Application Start (Web API)

UPDATE:

Basically, this boils down to "how can I force class libraries to load at Application Start for an Web API site so I can reflect through them once and be sure I get all implementations of a certain class. Alternately, if there's no good way to do this, what's the best way to allow classes in that library to register themselves?

Original Question:

I'm trying to register all classes that implement a certain interface on application start in my Web API and put them in a list, so I can find them later without reflecting through the assembly on each call.

It seems fairly simple, although I've never done it before. So after a bit of googling and reading some other Stack Overflow questions, I created a container class and put a method to register all the concrete implementations. A simplified solution looks something like this:

public static void RegisterAllBots()
        {
            var type = typeof(IRobot);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(type.IsAssignableFrom);
            foreach (var t in types)
            {
                TypeRepo.Add(t.Name.ToLower(), t);
            }
        }

And then I put my RegisterAllBots() in Application_Start() of the Global.asax.

The problem is, sometimes (but not always) if I start debugging the solution "cold," it doesn't find the implementations, only the Interface itself. If I go to Build -> Rebuild Solution before running it, it finds them. So I'm assuming this is an issue with Visual Studio starting up the WebHost Project without rebuilding the other class library projects.

So I have a few questions here.

  1. Am I right about the cause here?
  2. How can I stop this?
  3. As of right now, can this happen in production? I've deployed it to a test site and it seemed to work, but that could just be luck since sometimes it does find the concrete implementations anyway.
like image 893
Pharylon Avatar asked Mar 18 '23 20:03

Pharylon


2 Answers

AppDomain.CurrentDomain.GetAssemblies() will only return assemblies which have been loaded into the current app domain, an assembly is loaded when a type from that assembly is used.

Whenever I have needed to do this in the past, I have simply maintained a list of assemblies, i.e.

var assemblies = new [] 
{
    typeof(TypeFromAssemblyA).Assembly,
    typeof(TypeFromAssemblyB).Assembly
};

This only needs to contain one type from each assembly to ensure that assembly gets loaded into the current app domain.

Another option is to force all referenced assemblies to be loaded using Assembly.GetReferencedAssemblies() and Assembly.Load(AssemblyName). See this question for more details about this approach. You may then use your existing code after this point as all of the assemblies will have been loaded into the app domain.

A third option would be to make use of something like the Managed Extensibility Framework (MEF) to Export and subsequently Import types from assemblies in a directory.

Which option you choose will depend upon your solution structure and how you want implementations to be discovered. I like the first approach as it is explicit about which assemblies will be scanned, but the last approach allows for much more extensibility without recompilation.

like image 88
Lukazoid Avatar answered Mar 20 '23 09:03

Lukazoid


Here's a method I usually have in a utility class to do just that.

    public static List<Type> GetTypes<T>()
    {
        var results = new List<Type>();
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        foreach (var assembly in assemblies)
        {
            var types = assembly.GetTypes()
                .Where(t => t.IsAbstract == false
                    && (typeof(T).IsInterface == false || t.GetInterfaces().Contains(typeof(T)))
                    && (typeof(T).IsClass == false || t.BaseType == typeof(T)))
                .ToList();
            results.AddRange(types);
        }
        return results;
    }
like image 33
Jason W Avatar answered Mar 20 '23 09:03

Jason W