Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where Is the ability to RegisterAll with Registration Contexts (aka Func<T>)?

I can register a single registration item with instanceCreator context (aka Func<T>), but there doesn't seem to be the same allowance with a RegisterAll.

TL;DR - Find the accepted answer and look at update 2 (or skip down to Update 3 on this question)

This is what I want to do:

container.RegisterAll<IFileWatcher>(
    new List<Func<IFileWatcher>>
    {
        () => new FileWatcher(
            @".\Triggers\TriggerWatch\SomeTrigger.txt",
            container.GetInstance<IFileSystem>()),
        () => new FileWatcher(
            @".\Triggers\TriggerWatch\SomeOtherTrigger.txt",
            container.GetInstance<IFileSystem>())
    });

I tried adding an extension based on a previous Stack Overflow answer for multiple registrations, but it seems that last one in wins:

public static class SimpleInjectorExtensions
{
    public static void RegisterAll<TService>(this Container container,
        IEnumerable<Func<TService>> instanceCreators) 
        where TService : class
    {           
        foreach (var instanceCreator in instanceCreators)
        {
            container.RegisterSingle(typeof(TService),instanceCreator);
        }

        container.RegisterAll<TService>(typeof (TService));
    }
}

I'm also curious why there is a need for RegisterAll to exist in the first place. This is the first dependency injection container out of 5 that I've used that makes the distinction. The others just allow you to register multiple types against a service and then load them all up by calling Resolve<IEnumerable<TService>> (autofac) or GetAllInstances<TService> (both SimpleInjector and Ninject).

Update

For more clarity, I'm trying to build a list of items that I can pass to a composite that handles each of the individual items. It suffers from the same problem as the above since it falls into a group of tasks that all get registered to be run based on schedules, triggers, and events (Rx). To remove the register all for a moment and rip out some of the other stuff:

container.Register<ITask>(() => new FileWatchTask(
    container.GetInstance<IFileSystem>(),
    container.GetInstance<IMessageSubscriptionManagerService>(),
    configuration,
    container.GetAllInstances<IFileWatcher>()));

You can see that I am grabbing all instances of the previously registered file watchers.

What I need to know is a simple workaround for this issue and when it will be implemented (or if not, why it won't be). I will also accept that this is not possible given the current limitations of Simple Injector's design. What I will not accept is that I need to change and adapt my architecture to meet the limitations of a tool.

Update 2

Let's talk about OCP (Open Closed Principle aka the O in SOLID) and the impression I'm getting in how SimpleInjector breaks this particular principle in some cases.

Open Closed Principle is just that, open for extension, but closed for modification. What this means is that you can alter the behavior of an entity without altering its source code.

Now let's shift to an example that is relevant here:

var tasks = container.GetAllInstances<ITask>();
foreach (var task in tasks.OrEmptyListIfNull())
{
  //registers the task with the scheduler, Rx Event Messaging, or another trigger of some sort  
  task.Initialize();
}

Notice how clean that is. To be able to do this though, I need to be able to register all instances of an interface:

container.RegisterAll<ITask>( 
  new List<Func<ITask>>{
    () => new FileWatchTask(container.GetInstance<IFileSystem>(),container.GetInstance<IMessageSubscriptionManagerService>(),configuration,container.GetAllInstances<IFileWatcher>()),
    () => new DefaultFtpTask(container.GetInstance<IFtpClient>(),container.GetInstance<IFileSystem>()),
    () => new DefaultImportFilesTask(container.GetInstance<IFileSystem>())
  }
);

Right? So the lesson here is that this is good and meeting OCP. I can change the behavior of the task runner simply by adding or removing items that are registered. Open for extension, closed for modification.

Now let's focus on trying to do it the way suggested in the answer below (prior to the second update, which finally answers this question), which the author is giving the impression to be a better design.

Let's start with what the answer from the maintainer mentions is good design for registration. The viewpoint that I'm getting is that I have to make a sacrifice to my code to somehow make the ITask more flexible to work with SimpleInjector:

container.Register<ITask<SomeGeneric1>(() => new FileWatchTask(container.GetInstance<IFileSystem>(),container.GetInstance<IMessageSubscriptionManagerService>(),configuration,container.GetAllInstances<IFileWatcher>()));
container.Register<ITask<SomeGeneric2>(() => new DefaultFtpTask(container.GetInstance<IFtpClient>(),container.GetInstance<IFileSystem>()));
container.Register<ITask<SomeGeneric3>(() => new DefaultImportFilesTask(container.GetInstance<IFileSystem>()));

Now let's see how that makes our design change:

var task1 = container.GetInstances<ITask<SomeGeneric1>();
task1.Initialize();
var task2 = container.GetInstances<ITask<SomeGeneric2>();
task2.Initialize();
var task3 = container.GetInstances<ITask<SomeGeneric3>();
task3.Initialize();

Ouch. You can see how every time I add or remove an item from the container registration, I now need to also update another section of code. Two places of modification for one change, I'm breaking multiple design issues.

You might say why am I asking the container for this? Well this is in the startup area, but let's explore if I wasn't.

So I will use constructor injection to illustrate why this is bad. First let's see my example as construction injection.

public class SomeClass {
    public SomeClass(IEnumerable<ITask> tasks){}
}

Nice and clean.

Now, let's switch back to my understanding of the accepted answer's view (again prior to update 2):

public class SomeClass {
    public SomeClass(ITask<Generic1> task1,
                     ITask<Generic2> task2,
                     ITask<Generic3> task3
                     ) {}
}

Ouch. Everytime I have to edit multiple areas of code, and let's not even get started at how poor this design is.

What's the lesson here? I'm not the smartest guy in the world. I maintain (or try to maintain :)) multiple frameworks and I don't try to pretend I know more than or better than others. My sense of design might be skewed or I might be limiting others in some unknown way that I have not even thought of yet. I'm sure the author means well when he gives advice on design, but in some cases it may come across annoying (and a little condescending), especially for those of us that know what we are doing.

Update 3

So the question was answered in Update 2 from the maintainer. I was trying to use RegisterAll because it hadn't occurred to me that I could just use Register<IEnumerable<T>> (and unfortunately the documentation didn't point this out). It seems totally obvious now, but when people are making the jump from other IoC frameworks, they are carrying some baggage with them and may miss this awesome simplification in design! I missed it, with 4 other DI containers under my belt. Hopefully he adds it to the documentation or calls it out a little better.

like image 657
ferventcoder Avatar asked Mar 08 '13 13:03

ferventcoder


1 Answers

From your first example (using the List<Func<IFileWatcher>>) I understand that you want to register a collection of transient filewatchers. In other words, every time you iterate the list, a new file watcher instance should be created. This is of course very different than registering a list with two (singleton) filewatchers (the same instances that are always returned). There's however some ambiguity in your question, since in the extension method you seem to register them as singleton. For the rest of my answer, I'll assume you want transient behavior.

The common use case for which RegisterAll is created, is to register a list of implementations for a common interface. For instance an application that has multiple IEventHandler<CustomerMoved> implementations that all need to be triggered when a CustomerMoved event got raised. In that case you supply the RegisterAll method with list of System.Type instances, and the container is completely in control of wiring those implementations for you. Since the container is in control of the creation, the collection is called 'container-controlled'.

The RegisterAll however, merely forward the creation back to the container, which means that by default the list results in the creation of transient instances (since unregistered concrete types are resolved as transient). This seems awkward, but it allows you to register a list with elements of different lifestyles, since you can register each item explicitly with the lifestyle of choice. It also allows you to supply the RegisterAll with abstractions (for instance typeof(IService)) and that will work as well, since the request is forwarded back to the container.

Your use case however is different. You want to register a list of elements of the exact same type, but each with a different configuration value. And to make things more difficult, you seem to want to register them as transients instead of singletons. By not-passing the RegisterAll a list of types, but an IEnumerable<TService> the container does not create and auto-wire those types , we call this a 'container-uncontrolled' collection.

Long story short: how do we register this? There are multiple ways to do this, but I personally like this approach:

string[] triggers = new[]
{
    @".\Triggers\TriggerWatch\SomeTrigger.txt",
    @".\Triggers\TriggerWatch\SomeOtherTrigger.txt"
};

container.RegisterAll<IFileWatcher>(
    from trigger in triggers
    select new FileWatcher(trigger,
        container.GetInstance<IFileSystem>())
);

Here we register a LINQ query (which is just an IEnumerable<T>) using the RegisterAll method. Every time someone resolves an IEnumerable<IFileWatcher> it returns that same query, but since the select of that query contains a new FileWatcher, on iteration new instances are always returned. This effect can be seen using the following test:

var watchers = container.GetAllInstances<IFileWatcher>();
var first1 = watchers.First();
var first2 = watchers.First();
Assert.AreNotEqual(first1, first2, "Should be different instances");
Assert.AreEqual(first1.Trigger, first2.Trigger);

As this test shows, we resolve the collection once, but every time we iterate it (.First() iterates the collection), a new instance is created, but both instances have the same @".\Triggers\TriggerWatch\SomeTrigger.txt" value.

So as you can see, there is not limitation that prevents you from doing this effectively. However, you might need to think differently.

I'm also curious why there is a need for RegisterAll to exist in the first place.

This is a very explicit design decision. You are right that most other containers just allow you to do a bunch of registrations of the same type and when asked for a collection, all registrations are returned. Problem with this is that it is easy to accidentally register a type again and this is something I wanted to prevent.

Further more, all containers have different behavior of which registration is returned when requesting for a single instance instead of requesting the collection. Some return the first registration others return the last. I wanted to prevent this ambiguity as well.

Last but not least, please note that registering collections of items of the same type should usually be an exception. In my experience 90% of the time when developers want to register multiple types of the same abstraction, there is some ambiguity in their design. By making registering collections explicit, I hoped to let this stick out.

What I will not accept is that I need to change and adapt my architecture to meet the limitations of some tool.

I do agree with this. Your architecture should be leading, not the tools. You should chose your tools accordingly.

But please do note that Simple Injector has many limitations and most of those limitations are chosen deliberately to stimulate users to have a clean design. For instance, every time you violate one of the SOLID principles in your code, you will have problems. You will have problems keeping your code flexible, your tests readable, and your Composition Root maintainable. This in fact holds for all DI containers, but perhaps even more for Simple Injector. This is deliberate and if the developers are not interested in applying the SOLID principles and want a DI container that just works in any given circumstance, perhaps Simple Injector is not the best tool for the job. For instance, applying Simple Injector to a legacy code base can be daunting.

I hope this gives some perspective on the design of Simple Injector.

UPDATE

If you need singletons instead, this is even simpler. You can register them as follows:

var fs = new RealFileSystem();

container.RegisterSingle<IFileSystem>(fs);

container.RegisterAll<IFileWatcher>(
    new FileWatcher(@".\Triggers\TriggerWatch\SomeTrigger.txt", fs),
    new FileWatcher(@".\Triggers\TriggerWatch\SomeOtherTrigger.txt", fs)
);

UPDATE 2

You explicitly asked for RegisterAll<T>(Func<T>) support to lazily create a collection. In fact there already is support for this, just by using RegisterSingle<IEnumerable<T>>(Func<IEnumerable<T>>), as you can see here:

container.RegisterSingle<IEnumerable<IFileWatcher>>(() =>
{
    return
        from 
    var list = new List<IFileWatcher>
    {
        new FileWatcher(@".\Triggers\TriggerWatch\SomeTrigger.txt", container.GetInstance<IFileSystem>()),
        new FileWatcher(@".\Triggers\TriggerWatch\SomeOtherTrigger.txt", container.GetInstance<IFileSystem>())        
    };

    return list.AsReadOnly();
});

The RegisterAll<T>(IEnumerable<T>) is in fact a convenient overload that eventually calls into RegisterSingle<IEnumerable<T>>(collection).

Note that I explicitly return a readonly list. This is optional, but is an extra safety mechanism that prevents the collection from being altered by any application code. When using RegisterAll<T> collections are automatically wrapped in a read-only iterator.

The only catch with using RegisterSingle<IEnumerable<T>> is that the container will not iterate the collection when you call container.Verify(). However, in your case this would not be a problem, since when an element of the collection fails to initialize the call to GetInstance<IEnumerable<IFileWatcher>> will fail as well and with that the call to Verify().

UPDATE 3

I apologize if I gave to the impression that I meant your design is wrong. I have no way of knowing this. Since you explicitly asked about why some features where missing, I tried my best to explain the rationale behind this. That doesn't mean however that I think your design is bad, since there is no way for me of knowing.

let's switch back to what that would look like with the maintainer's view of good design

I'm not sure why you think that this is my view on good design? Having a SomeClass with a constructor that need to be changed every time you add a task in the system is definitely not a good design. We can safely agree on this. That breaks OCP. I would never advice anyone to do such thing. Besides having a constructor with many arguments is a design smell at least. The next minor release of Simple Injector even adds a diagnostic warning concerning types with too many dependencies since this often is an indication of a SRP violation. But again see how Simple Injector tries to ‘help’ developers here by providing guidance.

Still however, I do promote the use of generic interfaces, and that’s a case that the Simple Injector design is optimized for especially. An ITask interface is a good example of this. In that case, the ITask<T> will often be an abstraction over some business behavior you wish to execute, and the T is a parameter object that holds all parameters of the operation to execute (you can see it as a message with a message handler). This however is only useful when a consumer needs to execute an operation with a specific set of parameters (a specific version of T), for instance it wants to execute ITask<ShipOrder>. Since you are executing a batch of all tasks without supplying parameter, a design based on ITask<T> would probably be awkward.

But let's assume for a second that it is appropriate. Let's assume this, so I can explain how Simple Injector is optimized in this case. At the end of this update, I’ll show you how Simple Injector might still be able to help in your case, so hold your breath. In your code sample, you register your generic tasks as follows:

container.Register<ITask<SomeGeneric1>(() => new FileWatchTask(container.GetInstance<IFileSystem>(),container.GetInstance<IMessageSubscriptionManagerService>(),configuration,container.GetAllInstances<IFileWatcher>()));
container.Register<ITask<SomeGeneric2>(() => new DefaultFtpTask(container.GetInstance<IFtpClient>(),container.GetInstance<IFileSystem>()));
container.Register<ITask<SomeGeneric3>(() => new DefaultImportFilesTask(container.GetInstance<IFileSystem>()));

This is a rather painful way of registering all tasks in the system, since every time you change a constructor of a task implementation, you'll have to change this code. Simple Injector allows you to auto-wire types by looking at their constructor. In other words, Simple Injector allows you to simplify this code to the following:

container.Register<ITask<SomeGeneric1>, FileWatchTask>();
container.Register<ITask<SomeGeneric2>, DefaultFtpTask>();
container.Register<ITask<SomeGeneric3>, DefaultImportFilesTask>();

This already is much more maintainable, results in better performance and allows you to do add other interesting scenarios later on such as context based injection (since Simple Injector is in control of the whole object graph). This is the advised way of registering things in Simple Injector (prevent the use of a Func if possible).

Still, when having a architecture where a task is the center element, you would probably add new task implementations quite regularly. This will result in having dozens of registration lines and having to go back to this code to add a line every time you add a task. Simple Injector however has a batch registration feature that allows you to shrink this back to one single line of code:

// using SimpleInjector.Extensions;
container.RegisterManyForOpenGeneric(typeof(ITask<>), typeof(ITask<>).Assembly);

By calling this line, the container will search for all ITask<T> implementations that are located in the interface’s assembly and it will register them for you. Since this is done at runtime using reflection, the line does not have to be altered when new tasks are added to the system.

And since you're talking about the OCP, IMO Simple Injector has great support for the OCP. At some points it even beats all other frameworks out there. When I think about OCP, I particularly think about one specific pattern: the decorator pattern. The decorator pattern is a very important pattern to use when applying the OCP. Cross-cutting concerns for instance should not be added by changing some piece of business logic itself, but can best be added by wrapping classes with decorators. With Simple Injector, a decorator can be added with just a single line of code:

// using SimpleInjector.Extensions;
container.RegisterDecorator(typeof(ITask<>), typeof(TransactionTaskDecorator<>));

This ensures that a (transient) TransactionTaskDecorator<T> is wrapped around all ITask<T> implementations when they got resolved. Those decorators are integrated in the container’s pipeline, which means that they can have dependencies of their own, can have initializers, and can have a specific lifestyle. And decorators can be stacked easily:

container.RegisterDecorator(typeof(ITask<>), typeof(TransactionTaskDecorator<>));
container.RegisterDecorator(typeof(ITask<>), typeof(DeadlockRetryTaskDecorator<>));

This wraps all tasks in a transaction decorator and wraps that transaction decorator again in a deadlock retry decorator. And you can even apply decorators conditionally:

container.RegisterDecorator(typeof(ITask<>), typeof(ValidationTaskDecorator<>),
    context => ShouldApplyValidator(context.ServiceType));

And if your decorator has a generic type constraint, Simple Injector would automatically apply the decorator when the generic type constraints match, nothing you have to do about this. And since Simple Injector generates expression trees and compiles them down to delegates, this is all a one-time cost. That doesn’t mean it’s for free, but you’ll pay only once and not per resolve.

There's no other DI library that makes adding decorators as easy and flexible as Simple Injector does.

So this is where Simple Injector really shines, but that doesn't help you much :-). Generic interfaces don't help you in this case, but still, even in your case, you might be able make your registration more maintainable. If you have many task implementations in the system (that is, much more than three), you might be able to automate things like this:

var taskTypes = (
    from type in typeof(ITask).Assemby.GetTypes()
    where typeof(ITask).IsAssignableFrom(type)
    where !type.IsAbstract && !type.IsGenericTypeDefinition
    select type)
    .ToList();

// Register all as task types singleton
taskTypes.ForEach(type => container.Register(type, type, Lifestyle.Singleton));

// registers a list of all those (singleton) tasks.
container.RegisterAll<ITask>(taskTypes);

Alternatively, with Simple Injector 2.3 and up, you can pass in Registration instances directly into the RegisterAll method:

var taskTypes =
    from type in typeof(ITask).Assemby.GetTypes()
    where typeof(ITask).IsAssignableFrom(type)
    where !type.IsAbstract && !type.IsGenericTypeDefinition
    select type;

// registers a list of all those (singleton) tasks.
container.RegisterAll(typeof(ITask),
    from type in taskTypes
    select Lifestyle.Singleton.CreateRegistration(type, type, container));

This does assume however that all those task implementations have a single public constructor and all constructor arguments are resolvable (no configuration values such as int and string). If this is not the case, there are ways to change the default behavior of the framework, but if you want to know anything about this, it would be better to move that discussion to a new SO question.

Again, I’m sorry if I have annoyed you, but I rather annoy some developers than missing the opportunity in helping a lot others :-)

like image 86
Steven Avatar answered Sep 22 '22 03:09

Steven