Reputation: 46501
This question originates from the fact that I'm trying to create a Simple Injector implementation for MediatR: https://github.com/jbogard/MediatR/pull/14.
I'm having trouble while trying to resolve implementations of a generic handler interface. Consider the following notification handler interface:
public interface INotificationHandler<in TNotification>
where TNotification : INotification
{
void Handle(TNotification notification);
}
INotifcation
is just an empty marker interface.
I defined the following handlers for the Pinged
(which implements INotification
) event:
public class PingedHandler : INotificationHandler<Pinged>
{
public void Handle(Pinged notification) { }
}
public class PingedHandler2 : INotificationHandler<Pinged>
{
public void Handle(Pinged notification) { }
}
And also a generic handler (notice this should handle every INotification
):
public class GenericHandler : INotificationHandler<INotification>
{
public void Handle(INotification notification) { }
}
With the following registration:
var container = new Container();
container.RegisterManyForOpenGeneric(
typeof (INotificationHandler<>),
(service, impls) => container.RegisterAll(service, impls),
AppDomain.CurrentDomain.GetAssemblies());
Now I expect:
GetAllInstances<INotificationHandler<Pinged>>();
to resolve both PingedHandler
and PingedHandler2
which it does. But it doesn't resolve the GenericHandler
since it implements INotificationHandler<INotification>
and not INotificationHandler<Pinged>
. I wonder if there is a way to let Simple Injector search up the whole object graph and resolve anything that is Pinged
too.
I've found a blog post from Steven about co-variance and contra-variance, but I'm unable to get it working for my example.
Upvotes: 4
Views: 2181
Reputation: 46501
tl;dr: it was a bug/shortcoming in Simple Injector v2.6.0, which is fixed in v2.7.0. The configuration in the question (and shown below) does work now.
To summarize @qujck's answer and @Steven's comment: I was able to get it working by installing Simple Injector v2.7.0-beta2 with the following configuration (actually the same as in the question):
// Simple Injector v3.x
container.RegisterCollection(typeof(INotificationHandler<>),
AppDomain.CurrentDomain.GetAssemblies());
// Simple Injector v2.x
container.RegisterManyForOpenGeneric(
typeof(INotificationHandler<>),
container.RegisterAll,
AppDomain.CurrentDomain.GetAssemblies());
Now Simple Injector is able to resolve PingedHandler
, PingedHandler2
and the GenericHandler
when requesting:
container.GetAllInstances<INotificationHandler<Pinged>>();
Upvotes: 2
Reputation: 14580
UPDATE
As of Simple Injector release 2.7 this functionality is now standard and you no longer require the workaround shown below.
You are missing a variation of the MultipleDispatchEventHandler
described in that article. Taking the basic logic and applying it to your abstractions does give the result you are expecting:
[Fact]
public void RegisterAll_Contravariant_Succeeds()
{
var container = new Container();
container.RegisterManyForOpenGeneric(
typeof(INotificationHandler<>),
(service, impls) => container.RegisterAll(service, impls),
AppDomain.CurrentDomain.GetAssemblies());
var handlersType = typeof(IEnumerable<INotificationHandler<Pinged>>);
var handlersCollection = (
from r in container.GetCurrentRegistrations()
where handlersType.IsAssignableFrom(r.ServiceType)
select r.GetInstance())
.Cast<IEnumerable<INotificationHandler<Pinged>>>()
.ToArray();
var result =
from handlers in handlersCollection
from handler in handlers
select handler;
Assert.Equal(3, result.Count());
}
But it may be easier to make GenericHandler
generic:
public class GenericHandler<TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
{
public void Handle(TNotification notification) { }
}
Giving a simpler registration
[Fact]
public void RegisterAll_WithOpenAndClosedGenerics_Succeeds()
{
var container = new Container();
var types = OpenGenericBatchRegistrationExtensions
.GetTypesToRegister(
container,
typeof(INotificationHandler<>),
AccessibilityOption.AllTypes,
AppDomain.CurrentDomain.GetAssemblies())
.ToList();
types.Add(typeof(GenericHandler<>));
container.RegisterAll(typeof(INotificationHandler<>), types);
var result = container.GetAllInstances<INotificationHandler<Pinged>>().ToList();
Assert.Equal(3, result.Count());
}
Upvotes: 3