We have some shared library code where we have an interface with a method Handle
and an abstract class implementing the interface like below.
public interface ISampleHandler
{
Task Handle(string input);
}
public abstract class SampleBaseHandler : ISampleHandler
{
public Task Handle(string input)
{
// Some common operations
return OnHandle(input);
}
public abstract Task OnHandle(string input);
}
We have many different types of handlers implementing the abstract class, each does different thing, similarly to the following sample code
public class HandlerA : SampleBaseHandler
{
public override Task OnHandle(string input)
{
Console.WriteLine($"HandlerA says: {input}");
return Task.CompletedTask;
}
}
class HandlerB : SampleBaseHandler
{
public override Task OnHandle(string input)
{
Console.WriteLine($"HandlerB says: {input}");
return Task.CompletedTask;
}
}
All these handlers are in different project to the web app (Library type project) and are registered to DI container dynamically on app start up using reflection. When we need to handle the input, we have logic to determine the type of the handler and then we use the service provider to get the handler as dynamic
type and then call the Handle
method on it. The following sample console app code is the short version of what we actually have instead but it’s enough to show the idea.
var serviceCollection = new ServiceCollection();
var allTypes = typeof(ISampleHandler).Assembly
.GetTypes()
.Where(x => !x.IsAbstract);
var allHandlerTypes = from x in allTypes
from z in x.GetInterfaces()
let y = x.BaseType
where
(y != null && typeof(ISampleHandler).IsAssignableFrom(y)) ||
(z != null && typeof(ISampleHandler).IsAssignableFrom(z))
select x;
foreach (var handlerType in allHandlerTypes)
{
serviceCollection.AddScoped(handlerType);
};
var serviceProvider = serviceCollection.BuildServiceProvider();
foreach (var handlerType in allHandlerTypes)
{
dynamic handler = serviceProvider.GetRequiredService(handlerType);
handler.Handle("ABC");
}
This is the result when running it.
It’s all working as expected. Recently, we’re able to remove those common operations in the abstract before handling the input and so this abstract class can be removed. So we did and the handlers now look like the following
public class HandlerA : ISampleHandler
{
public Task Handle(string input)
{
Console.WriteLine($"HandlerA says: {input}");
return Task.CompletedTask;
}
}
class HandlerB : ISampleHandler
{
public Task Handle(string input)
{
Console.WriteLine($"HandlerB says: {input}");
return Task.CompletedTask;
}
}
I thought it was pretty straight forward, but if we run this code again, we’ll get this exception
Making it public will fix the problem. However, given that we have many handlers and this is a runtime exception, so to be sure, we use reflection to invoke the handle method instead. This will work with handlers without the public access modifier.
var serviceProvider = serviceCollection.BuildServiceProvider();
foreach (var handlerType in allHandlerTypes)
{
dynamic handler = serviceProvider.GetRequiredService(handlerType);
var method = handlerType.GetMethod("Handle");
await (Task)method.Invoke(handler, new object[] { "Handle" });
}