I have models like below:
public interface IUserLookupService {
Guid[] GetDirectChilds(Guid userId);
Guid[] GetAllChilds(Guid userId);
Guid GetDepartment(Guid userId);
}
public interface ICanRefreshCache{
void Refresh();
}
public class XHandler : IEventHandler<UserNameChanged> { ... }
public class YHandler : IEventHandler<TeamCreated> { ... }
public class CachedUserLookupService
: IUserLookupService,
ICanRefreshCache,
IEventHandler<UserNameChanged>
{
private Func<ISession> _sessionFactory;
private IDictionary<Guid, UserInfo> _users = new Dictionary<Guid, UserInfo>();
public CachedUserLookupService(Func<ISession> sessionFactory) {
_sessionFactory = sessionFactory;
}
public void Handle(UserNameChanged ev) {
// change cache with new event parameters
}
public void Refresh() {
var session = _sessionFactory();
// get user from db then create userinfo...
}
public Guid[] GetDirectChilds(Guid userId) {
// code
}
public Guid[] GetAllChilds(Guid userId) {
// code
}
public Guid GetDepartment(Guid userId) {
// code
}
public class UserInfo
{
public Guid Id { get; set; }
public string FullName { get; set; }
public Guid? ParentId {get;set;}
}
}
public class CachedTeamService
: ITeamService,
ICanRefreshCache,
IEventHandler<TeamCreated>{
// similar with CachedUserLookupService
}
My registration:
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
(closedServiceType, implementations) =>
{
container.RegisterAll(closedServiceType, implementations);
},
applicationAssembly, typeof(Constants).Assembly);
var serviceRegistrations =
from type in applicationAssembly.GetExportedTypes()
where type.Name.EndsWith("Service")
where !type.IsAbstract
where !type.IsInterface
select new
{
Services = type.GetInterfaces(),
Implementation = type
};
var lifeStyles = new Dictionary<Type, Lifestyle>()
{
{typeof(CachedUserLookupService),Lifestyle.Singleton},
{typeof(CachedTeamService),Lifestyle.Singleton}
};
List<Type> cacheableComponents = new List<Type>();
foreach (var reg in serviceRegistrations)
{
foreach (var service in reg.Services)
{
Lifestyle lifeStyle;
if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
{
lifeStyle = Lifestyle.Singleton;
}
if (typeof(ICanRefreshCache) == service)
{
cacheableComponents.Add(reg.Implementation);
continue;
}
container.Register(service, reg.Implementation, lifeStyle);
}
}
container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);
I want to refresh all cache at system startup using ICanRefreshCache->Refresh method so I call:
Step 1:
container.GetAllInstances<ICanRefreshCache>().Each(c=>c.Refresh());
Step 2:
after I called IEventHandler<UserNameChanged>
or any other interfaces type its belonging to CachedUserLookupService (or CachedTeamService) any time, returning instance is different from Step 1 instance therefore this registration does not help me.
I need Simple Injector registration to supply below calls.
// must return Singleton CachedUserLookupService + other
// IEventHandler<UserNameChanged> implementations
container.GetAllInstances<IEventHandler<UserNameChanged>>();
// must return Singleton CachedTeamService + other
// IEventHandler<TeamCreated> implementations
container.GetAllInstances<IEventHandler<TeamCreated>>();
// must return Singleton CachedUserLookupService
container.GetInstance<IUserLookupService>();
// must return Singleton CachedTeamService
container.GetInstance<ITeamService>();
// must return Singleton CachedUserLookupService + Singleton CachedTeamService
container.GetAllInstances<ICanRefreshCache>();
NOTE: The information in this answer is outdated. Things have changed considerably in Simple Injector v4.
If I understand correctly, you have a component that implements multiple interfaces and you want each registration to map to the same instance of that component. So no matter whether you resolve an ITeamService
, ICanRefreshCache
, or IEventHandler<TeamCreated>
, you want to get the same instance of CachedTeamService
.
The general way to do this in Simple Injector is to create an Registration
instance manually and register it for each interface as follows:
var registration =
Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);
container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
container.AddRegistration(typeof(IEventHandler<TeamCreated>), registration);
This is explained here.
Your case however is a bit more complicated, because you mix batch registration using the RegisterManyForOpenGeneric
with normal registrations. So you should either suppress the batch-registration of the IEventHandler<TeamCreated>
that you want as singleton, or you need to replace the registration completely.
Replacing the registration in this case however is not possible, because Simple Injector does not allow you to replace an registration that is part of a collection. So we can suppress registration of that type as follows:
Type[] typesToRegisterManually = new[]
{
typeof(CachedTeamService)
};
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
(service, impls) =>
{
container.RegisterAll(service, impls.Except(typesToRegisterManually));
},
assemblies);
var registration =
Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);
container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
// Using SimpleInjector.Advanced
container.AppendToCollection(typeof(IEventHandler<TeamCreated>), registration);
My experience however is that complex registrations are often an indication of SOLID principle violations in your code. It's hard to give any specific feedback on your design, but I find it very likely that the classes with those multiple interfaces have multiple responsibilities and will have multiple reasons to change (they violate SRP), causing you to change them while adding new features (which is a OCP violation).
Instead, there are a few things that I can advice:
IEventHandler<T>
into its own class. An event handler is clearly a different responsibility and the interface name is already telling you that. This class could then depend upon the old class the logic was extracted from, or extract the logic that the event handler depends upon into its own class and let the 'old' class and the event handler both depend on this new shared class.IUserLookupService
interface isself seems like a repository in disguise, and this basically means that you are violating SRP, OCP and ISP (as explained in this article). So as that article describes, custom queries deserve their own abstraction: IQueryHandler<TQuery, TResult>
. This makes it very easy to apply cross-cutting concerns on them, makes them SOLID, and makes it really straightforward to register them using container.RegisterManyForOpenGeneric
.What this leaves you is classes that in most cases just implement one abstraction, except the case that a class implements ICanRefreshCache
. For this special case I suggest making a composite ICanRefreshCache
implementation that allows triggering all ICanRefreshCache
in the application. But instead of injecting an IEnumerable<ICanRefreshCache>
into that composite, make that composite part of your Composition Root and let it depend on the container. This allows you to search through complete configuration at runtime to find all ICanRefreshCache
implementations.
This is what such composite might look like:
public class CanRefreshCacheComposite : ICanRefreshCache
{
private readonly Lazy<InstanceProducer[]> canRefreshProducers;
public CanRefreshCacheComposite(Container container) {
this.canRefreshProducers =
new Lazy<InstanceProducer[]>(() => GetProducers(container).ToArray());
}
public void Refresh() {
foreach (var producer in this.canRefreshProducers.Value) {
var refresher = (ICanRefreshCache)producer.GetInstance();
refresher.Refresh();
}
}
private IEnumerable<InstanceProducer> GetProducers(Container container) {
return
from producer in container.GetCurrentRegistrations()
where typeof(ICanRefreshCache).IsAssignableFrom(
producer.Registration.ImplementationType)
select producer;
}
}
And you can register it as follows:
container.RegisterSingle<ICanRefreshCache, CanRefreshCacheComposite>();
// To make sure all all ICanRefreshCache implementations that are part of
// a collection are known to the container, call Verify() when you're done
// registering.
container.Verify();
This way you can simply depend upon ICanRefreshCache
from within your code, call the Refresh
method on it, and the composite will do the rest.