I'm trying to solve an architecture problem involving a gigantic legacy file with several massive switch statements that need to be broken up into discrete classes. I want to solve this by having a single (and minimal) shared interface for many handlers to implement, using an enum value in order to identify each handler and register them all for dependency injection in a dictionary.
that is probably not worded well, so here's an arbitrary example:
// Example enum
public enum Color
{
Green = 1,
Blue = 2,
Red = 3,
// Over a hundred more options
}
// Example interface for all handlers to implement using a generic parameter
public interface IColorHandler<Color>
{
Task<string> GetContent(GeneralParamsObject info);
}
// Or, using a public property
public interface IColorHandler
{
Color HandledColor;
Task<string> GetContent(GeneralParamsObject info);
}
// Example implementation specifically for the color green
public class GreenHandler : IColorHandler<Color.Green>
{
public const Color HandledColor = Color.Green;
public Task<string> GetContent(GeneralParamsObject info)
{
// Build content for green
}
}
Then, in a "master" service, I'd like to inject something like a dictionary where the keys are members of the enum
, allowing the code to dynamically retrieve the correct concrete implementation.
public class ColorContentService : IColorContentService
{
private readonly IDictionary<Color, IColorHandler> _handlers;
public ColorContentService(IDictionary<Color, IColorHandler> handlers)
{
_handlers = handlers;
}
public async Task<string> GetColorsContent(List<Color> colors)
{
var info = BuildInfoObject();
var content = new StringBuilder();
foreach (var color in colors)
{
if (_handler.TryGetValue(color, out var handler))
{
content.AppendLine(await handler.GetContent(info));
}
}
return content.ToString();
}
}
To do this, I would need to be able to build and register this dictionary in the IServiceCollection
container, but I'm starting to wonder if that's even possible. I know how to register a list of classes based on an interface into an IEnumerable
, but I'd rather use a dictionary since there will be so many options and I would rather not have to iterate through the entire collection over and over to find the correct implementation.
For example, here's what we're currently doing for certain classes with a shared interface:
serviceCollection.Scan(scan => scan.FromAssemblies(AppDomain.CurrentDomain.GetAssemblies())
.AddClasses(classes => classes.AssignableTo<ISomeInterface>())
.As<ISomeInterface>()
.WithScopedLifetime());
This results in being able to inject IEnumerable<ISomeInterface>
and iterate through to call a method and retrieve the one we need. But, in the cases where we're currently using this, there are maybe 10 implementations max and we only ever need to retrieve one of them per any request. This new solution will involve over a hundred and an arbitrary number of them might be needed per request. There might be a way to do this without using a dictionary, but this is kind of where my knowledge stops.
So, is there any way to do what I'm hoping? If Google has answers, I have no idea what to search for to find this, and reading through Microsoft's docs hasn't yielded anything so far.
Oh, and we're on .NET 6, so some newer features might not be available for us at the moment.
I haven't tried implementing anything yet - still in the planning stages, but I've been digging through docs and haven't had a lightbulb come on yet.
Your approach should already work. Maybe you have to change your constructor a little bit, to get an IEnumerable<IColorHandler>
. Like this:
public class ColorContentService : IColorContentService
{
private readonly IDictionary<Color, IColorHandler> _handlers;
public ColorContentService(IEnumerable<IColorHandler> handlers)
{
_handlers = handlers.ToDictionary(k => k.HandledColor, v => v);
}
public async Task<string> GetColorsContent(List<Color> colors)
{
var info = BuildInfoObject();
var content = new StringBuilder();
foreach (var color in colors)
{
if (_handler.TryGetValue(color, out var handler))
{
content.AppendLine(await handler.GetContent(info));
}
}
return content.ToString();
}
}
So together with your DI-registrations, it should inject all the registered services as an IEnumerable<IColorHandler> handlers
and your ColorContentService
should work now.
Of course you could use Mediator, but then you have to dynamically create your Request objects, which leads to the same problem, which you have already solved in your code above (the creation of specific services by a Color property).