1

Given a .Net 5 app I have multiple classes implementing an interface and I have to load one of them by its name using dependency injection.

First I created a custom attribute to mark all classes with that flag

[AttributeUsage(AttributeTargets.Class)]
public class HandlerAttribute : Attribute
{
    public string Name { get; }

    public HandlerAttribute(string name)
    {
        Name = name;
    }
}

to be sure all of them implement the functionality I created an interface

public interface IHandler
{
    public string GetString();
}

so one handler might look like

[HandlerAttribute("HelloWorldHandler")]
public class MyHandler : IHandler
{
    public string GetString() => "Hello World";
}

All handlers should be transient services. Given the following web api controller

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    [HttpGet("{handler}")]
    public string Get(string handler)
    {
        IHandler theInjectedHandler = new MyHandler(); // get this one from the DI container by the handler name parameter

        return theInjectedHandler.GetString();
    }
}

how can I retrieve the correct handler as an interface by its attribute name? A large switch-case statement would be an obvious solution but I'm looking for a method similiar to the services.AddControllers() method which searches for the handlers by the attribute and registers all of them as transient services. My current search implementation for this is

IEnumerable<Type> handlerTypes = AppDomain
    .CurrentDomain
    .GetAssemblies()
    .SelectMany(assembly => assembly
        .GetTypes()
        .Where(currentType => currentType.GetCustomAttribute<HandlerAttribute>() != null));

And inside the MyController.Get method, how can I retrieve the correct handler?

  • 1
    There's nothing in MS.DI that would help you in achieving this. This probably means you should register all handlers by their concrete type and have a dictionary that maps their name to the implementation type. This dictionary (or an abstraction over it) can be injected into your controller. – Steven Aug 03 '21 at 14:30

2 Answers2

2

First you need to register the handlers as transient services with the following extension method. Call it in the Startup.cs

public static class ServiceCollectionExtension
{
    public static IServiceCollection AddHandlers(this IServiceCollection services, Assembly assemblyToScan)
    {
        foreach (Type handler in assemblyToScan.GetTypes().Where(t => t.IsDefined(typeof(HandlerAttribute), false)))
        {
            services.AddTransient(typeof(IHandler), handler);
        }
        return services;
    }
}

You need one more extension method to get the right handler from the collection:

public static IHandler GetByName(this IEnumerable<IHandler> handlers, string name)
    {
       return handlers.SingleOrDefault(h => h.GetType().GetCustomAttribute<HandlerAttribute>()?.Name == name);
    }

Now you can inject this services into the controller's constructor like this:

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IEnumerable<IHandler> _handlers;

    public MyController(IEnumerable<IHandler> handlers)
    {
        _handlers = handlers;
    }
    
    [HttpGet("{handler}")]
    public string Get(string handler)
    {
        IHandler theInjectedHandler = _handlers.GetByName(handler); 

        return theInjectedHandler.GetString();
    }
}
kroatti
  • 374
  • 2
  • 4
0

Check this answer.

With StructureMap you can register named instances in your container.

Ygalbel
  • 5,214
  • 1
  • 24
  • 32
  • https://jasperfx.github.io/lamar/documentation/ioc/ - "Part of Lamar's mission is to be a much more performant replacement for the venerable StructureMap IoC container library." – Christoph Lütjen Aug 03 '21 at 12:54