Getting all registered types inheriting from a generic interface from the container

60 Views Asked by At

I am using Simple Injector 5.3.3 and I would like to access at run time to a list of the types inheriting from a generic interface (say IHandleMessage<T>) that I registered in the container. How can I achieve this?

I read the documentation here: https://docs.simpleinjector.org/en/latest/advanced.html And therefore I tried something like this:

container.Collection.Register(typeof(IHandleMessages<>), new[]
    {
        typeof(Handler1),
        typeof(Handler2),
        typeof(Handler3)
    });

But then, when using the method container.GetAllInstances<IHandleMessages<>>, I do need to specify something in the generic. Furthermore, I would like to get only the list of types, and not the instances (I will instantiate them at a later time).

I hope my question is clear, and I did not really find my answer beforehand.

Edit : I will elaborate on my problem.

We have a listener endpoint that we are building when starting the program. In this endpoint, we need to register all the command handlers that we already added in the SI container. The code looks roughly like this now :

    public virtual IEndpoint GetReceiverEndpoint()
    {
        return GiveMe.AnEndpointBuilder("myEndPoint")
                     .UseUddiFromFramework()
                     .UseFactory(GetHandler<Handler1>)
                     .UseFactory(GetHandler<Handler2>)
                     .UseFactory(GetHandler<Handler3>)
                     .Start();
    }
    
    public T GetHandler<T>() where T : class
    {
        // Container is where we already registered our handlers
        var scope = AsyncScopedLifestyle.BeginScope(Container);

        return scope.GetInstance<T>();
    }

So we have to manually register the Handler in the container, and then at the endpoint. Regularly, when it comes to adding an handler, a developer forgets to add it to the Endpoint, and this is something that we can see only at runtime (usually on the test environment). This is a mistake that I would like to avoid by doing something like

    public virtual IEndpoint GetReceiverEndpoint()
    {
        var handlersTypes = // get all the implementation of IHandlerMessage<T> 
        
        var endpoint = GiveMe.AnEndpointBuilder("myEndPoint")
                             .UseUddiFromFramework();

        foreach (var type in handlersTypes)
        {
            endpoint = endpoint.UseFactory(GetHandler<type>());
        }

        return endpoint;
    }
    
    public T GetHandler<T>() where T : class
    {
        var scope = AsyncScopedLifestyle.BeginScope(Container);

        return scope.GetInstance<T>();
    }

So that we do not need to register the type twice. Now I see that I could store all the types in a static list and use that list both in the DI container (to register the classes in the container) and then in the endpointBuilder (to ass those handlers to the endpoint). Is there something which is more elegant?

1

There are 1 best solutions below

1
Steven On

Simple Injector contains several methods methods that allow getting a list of registered types back. In your case I'd suggest using Container.GetCurrentRegistrations(). For instance:

var handlerImplementationTypes = (
    from reg in container.GetCurrentRegistrations()
    where reg.ServiceType.IsClosedTypeOf(typeof(IHandleMessages<>))
    select reg.Registration.ImplementationType)
    .Distinct()
    .ToArray();

This query goes through the registrations gets all registrations for a closed version of IHandleMessages<T> (IsClosedTypeOf is an extension method provided by Simple Injector). The distinct is used to prevent duplicate implementation types to be returned in case a single class implements IHandleMessages<T> multiple times.

From this point on it gets ugly, but that's because -as you explained in the comments- the framework you are using doesn't support a non-generic overload of UseFactory.

public virtual IEndpoint GetReceiverEndpoint()
{
    var builder = GiveMe.AnEndpointBuilder("myEndPoint")
        .UseUddiFromFramework();

    foreach (Type handlerType in handlerImplementationTypes)
    {
        Delegate factory = Expression.Lambda(
            Expression.Call(
                Expression.Constant(this),
                this.GetType()
                    .GetMethod(nameof(this.GetHandler))
                    .MakeGenericMethod(handlerType)))
            .Compile();

        useFactory.MakeGenericMethod(handlerType).Invoke(builder, new[] { factory });
    }

    return builder.Start();
}

I would, however, like to warn you about a few things:

  1. Your GetHandler<T> method is resolving a concrete handler type (e.g. Handler1), while you are registering those handlers by their interface (i.e. IHandleMessages<T>). Simple Injector will not allow resolving those concrete types unless a. you registered them explicitly or b. configured Simple Injector to resolve unregistered types. Unregistered type resolution, however, has been turned off by default in recent versions because of the problems it caused with keeping your configuration verifiable. Your messaging framework might require you to supply a concrete type, but if it doesn't, I'd suggest changing the code that you do the following: .UseFactory<IHandleMessages<MyFirstMessage>>(this.GetHandlerForMessage<MyFirstMessage>).
  2. As I mentioned in the comments, while you are resolving a handler from a Simple Injector scope, you never seem to be disposing off those scopes. This can cause memory leaks, open database transactions, or other nasty behavior, depending on what happens during the execution of a handler. Disposing off the scope, however, must be done after the handler is executed, which might cause issues, depending on what the messaging framework supports. Ideally, you would wrap the handler in a decorator that manages the lifetime of the handler and ensures creation and disposable off a scope, as is documented here (look at the ScopedCommandHandlerProxy<T>). This method, however, requires resolving the handler by its interface and requires support from your messaging framework for handling with the interface rather than the implementation (as stated in the previous point). If this is not possible, you need to look for an integration point in the messaging framework that allows you to wrap the resolve and execution of a handler with a Simple Injector scope. If you already have a place where you dispose off the scope, you can ignore this warning.
  3. I have no idea which messaging framework you are using and whether this is something that is built internally or publically available. So it could very well be that the messaging framework provides certain extension points that would reduce the complexity of integration with the framework. Because of that, it would consider it to be a good idea to let developers of the messaging framework take a look at your question and this answer to get some feedback on the proposed solution.