-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: OnBeforeBuild Delegate - Add a hook into the ServiceCollection, allowing execution of code just before building the ServiceProvider #84847
Comments
Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection Issue DetailsBackground and motivationWhile the Microsoft Dependency Injection container works extremely well for most use cases, it has a couple of weaknesses compared to some other containers. One area where I've specifically found that it is weak in, is supporting Decorator classes. While it is currently possible to support decorators, it is dependent on the ordering of registrations. Meaning you can't register a decorator class before registering the original implementation. This means that if someone is unaware of this order, they could start refactoring the Dependency Injection setup, moving certain registrations into different locations, and this could end up breaking your application. Therefore this means this is delicate, flaky code. A Dependency Injection container shouldn't really concern itself with the ordering of registrations in my opinion. By exposing a delegate on the IServiceCollection, called something like This hook could also enable more advanced use-cases, such as scanning the collection before building, and utilising that data in any custom logic. My proposed API would look like this: + public delegate void OnBeforeBuild(IServiceCollection serviceCollection);
public interface IServiceCollection : IList<ServiceDescriptor>
{
+ OnBeforeBuild OnBeforeBuild { get; set; }
} Then where we are given a ServiceCollection, we can use this hook. We can then implement extension methods to abstract away logic like public static class DependencyInjectionExtensions
{
public static IServiceCollection AddSingletonDecorator<TService, TDecorator>(this IServiceCollection services)
where TService : class
where TDecorator : class, TService
{
services.OnBeforeBuild += collection => collection.AddSingleton<TService, TDecorator>();
return services;
}
} API Proposal+ public delegate void OnBeforeBuild(IServiceCollection serviceCollection);
public interface IServiceCollection : IList<ServiceDescriptor>
{
+ OnBeforeBuild OnBeforeBuild { get; set; }
} API Usageservices.OnBeforeBuild += collection => collection.AddSingleton<TService, TDecorator>(); Alternative DesignsNo response RisksBuilding the ServiceProvider would be marginally slower. But this is a one-time process for most applications. This shouldn't cause any breaking changes, as it just exposes a new delegate, and doesn't change any existing behaviour.
|
Can you provide an example or pseudo-code of the implementation of the event -- does the decorator class in your case need to enumerate existing services and then add a decorator for some of them and then perhaps remove the original? |
Yeah exactly. Apologies I'm on mobile but it'd be something like
|
I do think this feature has merit; moving to future based on schedule. |
Adding a property to the existing IServiceCollection interface is a breaking change. |
Oh will it? If it wasn't referenced before how does it break stuff? |
This is "DISALLOWED: Adding a member to an interface" in .NET API changes that affect compatibility. IServiceCollection is implemented by at least the following classes, and the new property would need to be added to each of them:
An interface default method is not feasible in IServiceCollection because the Microsoft.Extensions.DependencyInjection.Abstractions package targets .NET Framework too and is supported there for use with ASP.NET Core 2.1. |
If you instead did namespace Microsoft.Extensions.DependencyInjection
{
// Keep the existing interface as is.
public interface IServiceCollection : IList<ServiceDescriptor>
{
}
+ public delegate void OnBeforeBuild(IServiceCollection serviceCollection);
+ // It doesn't seem useful to be able to implement IServiceCollectionBeforeBuild
+ // without IServiceCollection, because the IServiceCollection is passed as
+ // a parameter to the delegate anyway.
+ public interface IServiceCollectionBeforeBuild : IServiceCollection
+ {
+ OnBeforeBuild OnBeforeBuild { get; set; }
+ }
- public partial class ServiceCollection : IServiceCollection
+ public partial class ServiceCollection : IServiceCollection, IServiceCollectionBeforeBuild
{
+ public OnBeforeBuild OnBeforeBuild { get; set; }
}
} then this would not be a breaking change according to those rules, but there could still be compatibility problems if a component attempts to register its delegate in IServiceCollectionBeforeBuild but the application uses a DI container library that does not support this feature. |
The thing is, a major version gets released each year anyway. By versioning standards, breaking changes are to be expected in new major versions |
This would be on the options instead of on the service collection (if we decided to do anything here) |
Allowing delegates? That'd limit libraries which wouldn't really solve registering decorators. Extensions are usually on the collection so libraries don't have access to the options |
Feature that change the core interfaces are a much bigger scope as all of the existing implementers of IServiceCollection need to buy into this new primitive. It requires getting buy in and sign off from those parties. Features of the built-in container are smaller in scope and can be executed on without that level of buy in (if the core team agrees though). If you want it to be in the first category then you need to do the work to get the DI council to buy into your feature and agree to implement it everywhere. PS: I'm not convinced this feature has reached that level of significance, but I am just one person 😄 |
Fair. I'm not saying its a huge feature, but it definitely helps enable the decorator pattern which COULD have big implications. And it basically enables a new feature that isn't possible right now. Also, I understand the interface implementation problem, but I'd argue how often those interfaces are used outside of the MEDI types. I guarantee minimally. Also sorry if that came across argumentatively, I don't mean that, but just trying to convey that I don't think it's affect many people |
Doesn’t change the stance on how we approach DI features.
|
Background and motivation
While the Microsoft Dependency Injection container works extremely well for most use cases, it has a couple of weaknesses compared to some other containers.
One area where I've specifically found that it is weak in, is supporting Decorator classes.
While it is currently possible to support decorators, it is dependent on the ordering of registrations. Meaning you can't register a decorator class before registering the original implementation. This means that if someone is unaware of this order, they could start refactoring the Dependency Injection setup, moving certain registrations into different locations, and this could end up breaking your application. Therefore this means this is delicate, flaky code.
A Dependency Injection container shouldn't really concern itself with the ordering of registrations in my opinion.
By exposing a delegate on the IServiceCollection, called something like
OnBeforeBuild
, we can add hooks that will be invoked just before building the ServiceProvider.This means that regardless of when we called (for example) an extension method called
.AddDecorator<IInterface, TDecorator>()
, which under the hood used this hook, it wouldn't break, because it would end up always being invoked at the latest stage possible.This hook could also enable more advanced use-cases, such as scanning the collection before building, and utilising that data in any custom logic.
My proposed API would look like this:
Then where we are given a ServiceCollection, we can use this hook. We can then implement extension methods to abstract away logic like
API Proposal
API Usage
Alternative Designs
No response
Risks
Building the ServiceProvider would be marginally slower. But this is a one-time process for most applications.
This shouldn't cause any breaking changes, as it just exposes a new delegate, and doesn't change any existing behaviour.
The text was updated successfully, but these errors were encountered: