Open dotnetjunkie opened 7 years ago
There are many ways to design a fluent API, but it all comes down to returning objects from registration methods that have some similarity, because this allows a single extension method to be reused on top of multiple Registration
methods.
There are two philosophies that I can think of:
IRegistrationBuilder<out TLimit, out TActivatorData, out TRegistrationStyle>
. This results in a very flexible system because extension methods can make use of generic type constraints to allow being reused in different contexts. The downside, however, is that this easily results in a very complex system, causing mental gymnastics to the user, or at least, it can easily scare the shit out of a user. We might even consider it to conflict with our design philosophy.Either way, designing a API that is usable is difficult. We need good use cases, while they might be scarce. Even the previously presented examples are not easily implemented using such API. A WithConstructorArgument
extension method would currently only work (even with a fluent API) when the user override the default dependency injection behavior (IDependencyInjectionBehavior
), because Register
prevent registrations for implementations that contain constructor arguments of ambiguous types.
Another issue is that those extension methods should allow users to tune that specific registration or set of registrations that they made for that single call to Register
, but for the most part, Simple Injector works more globally. For instance:
container.Collection.Append<ILogger, DiskLogger>().WithConstructorArgument("c:\\log.txt");
container.Collection.Append<ILogger, DiskLogger>().WithConstructorArgument("d:\\log.txt");
Here the user would expect that DiskLogger
is registered twice, but both with a different string
constructor argument. Both calls to Register<ILogger, DiskLogger>
, however, reuse the same Registration
instance, which means that both loggers will log to "d:\log.txt". Oops.
This same problem could appear for InitializeWith
or for instance an DecoratorWith
extension method:
Type[] handlerTypes1 = ...
Type[] handlerTypes2 = ...
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(LoggingDecorator<>));
container.Register(typeof(ICommandHandler<>), handlerTypes1)
.DecorateWith(typeof(ValidationDecorator<>)
.DecorateWith(typeof(PerformanceMonitoring<>);
container.Register(typeof(ICommandHandler<>), handlerTypes2)
.DecorateWith(typeof(DeduplicationDecorator<>);
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(SecurityDecorator<>));
In the example above, the user would expect the handlers of list 1 to be decorated with: logging, validation, performance, and security (in that order), but the handlers of list 2 to be decorated with: logging, de-duplication, and security.
Although a nice feature to have, these extension methods can't simply be put on top of Simple Injector without other changes.
In the end, I'm starting to think adding a fluent API is less and less attractive, because:
Because of this I'm moving this feature out of the v5 milestone. Moving it back to the backlog.
How about the simple step of returning the container instead of void? This would allow a series of regitrations:
container.Register(typeof(ICommandHandler<>))
.RegisterDecorator(typeof(ICommandHandler<>), typeof(LoggingDecorator<>))
.RegisterDecorator(typeof(ICommandHandler<>), typeof(SecurityDecorator<>));
Instead of having each registration on its own line prefixed with container.
@CasperWSchmidt, I would argue against this. The added benefit of not having to type container
is IMO limited. But, more importantly, after Simple Injector starts returning Container
from its registration methods (say in v6), it becomes almost impossible to extend this further with a Fluent API as discussed above. Or, at least, not without introducing big breaking changes that would cause 90% of the users to have to make changes to their Composition Root.
So I rather wait until we found a compelling way to implement a fluent API, than returning Container
and been stuck with this till kingdom comes.
@dotnetjunkie That's alright. Perhaps I misread the monologue/discussion above, but as I understood it, you don't see any good ways to implement a fluent API. That's why I suggested the small change that would make most use cases easier but have no effect on more complex use cases (that would still require special handling and custom implementation of IDependencyInjectionBehavior
).
All
Register
overloads of the registration API returnvoid
. By instead returning an object that describes the made registration, it would allow users and third parties to build extension methods on top of this.Here are some examples of what can be done on top of this: