Multiple registrations of generic with multiple interfaces on singleton classes

Jan 23, 2015 at 4:02 PM
Edited Jan 23, 2015 at 4:31 PM
Did not know how to properly name this thread, but here is my problem:

I have a few classes of the following type:
class UserService : IUserService, IObserver<User>
class FolderService : IFolderService, IObserver<User>
because I want different services to be notified of some events that are going on. All of these services are configured as Singletons. Now there are some classes that want to send notification to multiple Observers, according to SimpleInjector documentation (thanks for that part by the way!) I created a CompositeObserver that takes IEnumerable<IObserver> as a parameter. And what I want really is that I have only one object of the services by any interface it's queried.

This is what I have done so far:
// Registrations of IUserService, IFolderService as Singletons first
container.RegisterManyForOpenGeneric(typeof(IObserver<>), 
container.RegisterAll(), typeof(IObserver<>).Assembly);
container.RegisterOpenGeneric(typeof(IObserver<>), typeof(CompositeObserver<>), Lifestyle.Singleton);
The problem that I get that enumerating over the IEnumerable yield new objects of generic types as they are transient by default, but I can't understand how I can manage to register them to the existing singleton.

P.S. I just thought that I really need to register it manually with RegisterAll (after creating proper singleton registrations for both interfaces) and that I cannot register it automatically in that complex scenario.
Coordinator
Jan 24, 2015 at 10:35 AM
Edited Jan 26, 2015 at 2:38 PM
Although one might be tempted to override the default lifestyle selection behavior to allow marking the classes with an attribute that marks it as singleton (as shown in the documentation), this will unfortunately not do the trick, because the container will still create a new Registration object per abstraction. So both the IUserService and IObserver<User> will have their own singleton.

The solution here is to use the same registration for both the IUserService registration and the registration of the IObserver<User> within that collection. RegisterAll will register a collection that forwards each registered type back to the container. In other words, if you register container.RegisterAll<ILogger>(typeof(FileLogger)), Simple Injector will look up a registration for FileLogger when iterating the collection. If such registration is found, the resolve takes place using that registration, otherwise it will create a new implicit registration based on the default lifestyle.

So long story short, what you need to do is the following:
var userRegistration = Lifestyle.Singleton.CreateRegistration<UserService>(container);
container.AddRegistration(typeof(IUserService), userRegistration);
container.AddRegistration(typeof(UserService), userRegistration);

var folderRegistration = Lifestyle.Singleton.CreateRegistration<FolderService>(container);
container.AddRegistration(typeof(IFolderService), folderRegistration);
container.AddRegistration(typeof(FolderService), folderRegistration);

container.RegisterManyForOpenGeneric(typeof(IObserver<>), container.RegisterAll, 
    typeof(IObserver<>).Assembly);
In other words, you need to create an explicit registration, such as Lifestyle.Singleton.CreateRegistration<UserService> and add that registration for both the service (IUserService) and the implementation (UserService). You need to add it for the service, because you have components that depend on that service. You need to register it for the implementation itself, because the RegisterAll collection will go look for that implementation.

If you have many of those registrations, you might want to consider extracting this to a more convenient extension method:
public static class CompositionRootExtensions {
    public static void RegisterBoth<TService, TImplementation>(
        this Container container, Lifestyle lifestyle)
        where TService : class
        where TImplementation : class, TService {
        var registration = lifestyle.CreateRegistration<TImplementation>(container);
        container.AddRegistration(typeof(TService), registration);
        container.AddRegistration(typeof(TImplementation), registration);
    }
}
This way you can change the registration to the following:
container.RegisterBoth<IUserService, UserService>(Lifestyle.Singleton);
container.RegisterBoth<IFolderService, FolderService>(Lifestyle.Singleton);

container.RegisterManyForOpenGeneric(typeof(IObserver<>), container.RegisterAll, 
    typeof(IObserver<>).Assembly);
p.s. I made an error in my Stackoverflow answer, where I added the registration for IObserver<User>, but this should have been UserService. This is probably the main reason for your confusion and your question here. I updated that answer as well.
Jan 26, 2015 at 9:42 AM
Edited Jan 26, 2015 at 11:02 AM
Thank you very much for the reply, I am trying to fix this according to what you have written, but I stumbled upon another problem, the UserService that has IObserver<T> in it is actually a CachedUserService, that is registered as a decorator and I cannot find an overload where I can use a registration there:
class UserService : IUserService

class CachedUserService: IUserService, IObserver<User>
So what I tried to do is this:
Registration registration = Lifestyle.Singleton.CreateRegistration<CachedUserService>(container);
container.AddRegistration(typeof(IUserService), registration);
container.AddRegistration(typeof(CachedUserService), registration);

container.Register<IUserService, UserService>();
container.RegisterDecorator(typeof(IUserService), typeof(CachedUserService), Lifestyle.Singleton);
But I get an exception that IUserService is already registered

P.S. I ended up doing like that:
container.Register<IObserver<User>>(() =>
                {
                    return new CompositeObserver<User>(
                        new List<IObserver<User>>()
                        {
                            (IObserver<User>) container.GetInstance<IUserService>()
                        });
                },
                Lifestyle.Singleton);
Coordinator
Jan 26, 2015 at 3:07 PM
Edited Jan 26, 2015 at 3:11 PM
There are a few things you should note:
  • RegisterManyForOpenGeneric will skip over decorators, because this would easily lead to cyclic dependencies. You have to register them manually.
  • Simple Injector does not see decorators as 'normal' registrations. This has the advantage of not having to register decorators with a key, as you need to do with Autofac, but this means that registering the decorator will not allow your observer collection to see that decorator by its IObserver<T>.
So what you could try is the following registration:
// Create the service manually (with its dependencies).
var service = new UserService();

// Create a registration for the decorator; don't register it as decorator.
var serviceRegistration = Lifestyle.Singleton.CreateRegistration<CachedUserService>(
    () => new CachedUserService(service), container);

// Register the decorator as IUserService
container.AddRegistration(typeof(IUserService), serviceRegistration);

// Register the collection of IObservers.
container.RegisterManyForOpenGeneric(typeof(IObserver<>), container.RegisterAll, 
    typeof(IObserver<>).Assembly);

// Append our decorator registration manually to the collection.
// AppendToCollection is an extension method from the SimpleInjector.Advanced namespace.
container.AppendToCollection(typeof(IObserver<User>), serviceRegistration);
Jan 26, 2015 at 3:33 PM
I actually tried to do it this way, but I don't know how to properly create a new UserService. It has some dependencies on it's own, and as soon as I do the GetInstance, container will be locked for modification. I used Unity before and we did this trick quite often with complex dependencies and with decorators.
Coordinator
Jan 26, 2015 at 7:08 PM
The reason why the container is locked down after first use is explained here.

In case UserService has dependencies of its own, you can go with the following registration:
container.RegisterSingle<UserService>();

// Create a registration for the decorator; don't register it as decorator.
var userServiceRegistration = Lifestyle.Singleton.CreateRegistration<CachedUserService>(
    () => new CachedUserService(container.GetInstance<UserService>()),
    container);

// Register the decorator as IUserService
container.AddRegistration(typeof(IUserService), userServiceRegistration);

// Register the collection of IObservers.
container.RegisterManyForOpenGeneric(typeof(IObserver<>), container.RegisterAll, 
    typeof(IObserver<>).Assembly);

// Append our decorator registration manually to the collection.
// AppendToCollection is an extension method from the SimpleInjector.Advanced namespace.
container.AppendToCollection(typeof(IObserver<User>), userServiceRegistration);
Jan 27, 2015 at 8:19 AM
Thanks again for help! I know the reasons for doing that, but it's a bit odd if you want to make a more complex registration this way, not abuse it after registration phase. But we can always use it with delegates the way you wrote.