Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create child scope with additional services

With Autofac it pretty easy to create a child scope of the container and register addtional services. How would I achieve the same with the .net's dependency injection?

The IServiceProvider that I tried to inject to a class that needs to create child containers only offers the CreateScope() method that creates an IServiceScope that agian has only the ServiceProvider property and no way to register additional services. Is there anything else I should inject that would allow me to register more services with the container?

like image 932
t3chb0t Avatar asked Mar 30 '26 23:03

t3chb0t


2 Answers

I don't know how Autofac works, but I can explain you how Microsoft DI works.

First of all, the Microsoft DI container is designed so that you have two main abstractions:

  • IServiceCollection: this is the object you use to register services in your applications. Think of this as the builder object for the actual DI container.
  • ServiceProvider: this is the actual DI container, that you obtain from the IServiceCollection object by invoking the IServiceCollection.BuildServiceProvider extension method. The behavior of this object is described by the IServiceProvider interface (ServiceProvider class implements the IServiceProvider interface)

So working with the DI container is a two steps operation: first of all you need an IServiceCollection object so that you can register services by specifying the implementing type and the lifetime (transient, scoped or singleton), then you can build a ServiceProvider and use it to resolve services in your application. The concrete type actually used for the IServiceCollection interface is the ServiceCollection class.

When you build the service collection in order to get a ServiceProvider instance, you actually get the root container for your application. It is called root container because you can create a hierarchy of service providers having its root in the root container of the application.

Given the root container for your application, in order to create a child container you need to create a scope, which is basically a scope used to resolve services. Each scope has its own container, which is an object implementing the IServiceProvider interface that you can use to resolve services inside of that scope.

This is the general pattern to do that:

// create the IServiceCollection instance
var services = new ServiceCollection();

// register services on the IServiceCollection instance
services.AddSingleton<IFooService, FooService>();
services.AddScoped<IBarService, BarService>();
services.AddTransient<IBuzzService, BuzzService>();

// create the root container
using var rootContaier = services.BuildServiceProvider();

// create a scope
using var scope = rootContainer.CreateScope();

// gets a reference to the container for the scope
var scopeContainer = scope.ServiceProvider;

// use the scope container to resolve services
var fooService = scopeContainer.GetRequiredService<IFooService>();
var barService = scopeContainer.GetRequiredService<IBarService>();
var buzzService = scopeContainer.GetRequiredService<IBuzzService>();

// do whatever you want with the resolved services
fooService.Foo();
barService.Bar();
buzzService.Buzz();

These dependency resolution scopes are really important because they define the lifetimes of the services resolved by using the scope service provider. These are the rules:

  1. services registered with the singleton lifetime are always resolved by the application root container. Even if you use a child container to resolve a singleton service, the actual service resolution is delegated to the root container. Singleton services implementing the IDisposable interface are disposed when the root container is disposed, which usually happens at the application shutdown. Each singleton service is actually resolved once from the root container and the very same instance is reused for the entire lifetime of the application.
  2. services registered with the transient lifetime are basically the opposite of a singleton service. A brand new instance is created each time the service is resolved. Given a scope, each time the scope service provider is used to resolve a transient service a brand new instance of the implementing type is created and tracked by the scope service provider. When the scope is disposed all the transient services that were resolved from that scope and having a disposable implementing type will be disposed too.
  3. services registered with the scoped lifetime behave like singletons for the scope they were created from. If you have a scope and you use the scope service provider to resolve a scoped service, a brand new instance of the implementing type is created only the first time the service is resolved. All the subsequent requests to resolve the same service inside of the same scope will be fulfilled by reusing the instance of the implementing type created the first time the service was resolved in the scope. Scoped services are tracked by the scope: this is useful because a scoped service whose implementing type is disposable, will be disposed when the scope is disposed (same behavior as the transient services).

From the previous rules you can derive a fourth rule: never use the root container to resolve services, always create a scope and resolve services from that scope (remember to dispose the scope when you are done).

You should do that because scoped and transient services are tracked by the scope and disposed when the scope is disposed. If you try to resolve transient and scoped services from the root container they will be tracked by the root container and they will be disposed only when the root container is disposed: this usually happens at the application shutdown, because the lifetime of the root container is basically the application lifetime. Put in other words, you will create a memory leak if you are not compliant with the fourth rule.

Notice that if you use the overload of IServiceCollection.BuildServiceProvider taking a boolean parameter, you can ask the service collection to build a service provider which actually checks that scoped services are never resolved from the root container. This way you will get a partial check for the rule number four (only scoped services are checked for the dangerous resolution from the root container).

Going back to your question, the service registration phase is all done on the very same IServiceCollection instance and is not aware of the concept of service resolution scope. Scopes are only useful in the later stage of the services resolution, to define the lifetime of the resolved services as explained above.

Once you have built the root container from the service collection instance, the service registration phase is done and based on my knowledge you can only resolve services, no additional registrations will be allowed.

like image 167
Enrico Massone Avatar answered Apr 02 '26 11:04

Enrico Massone


You could register a scoped container type for your dynamic parameters. Then

  • create a new scope

  • retrieve the instance of the container type from the scope and set values on it.

  • Then the get instance of the service you want from the scope. It can inject the container type and retrieve the values it needs.

     //the container type
     public class SocketContext
     {
         public SocketContext()
         {
             SocketId = Guid.NewGuid().ToString();
         }
    
         public string SocketId { get; }
     }
    
     using var scope = context.RequestServices.CreateScope();
     var socketContext = scope.ServiceProvider.GetService<SocketContext>();
     var socketHandler = scope.ServiceProvider.GetService<TSocketHandler>();
    

In this case I wanted a unique socket id per instance of TSocketHandler. If you don't control the source of the constructor you want to call you can register a Func that makes use of the container type. This approach will also keep the container type from showing up in your own constructors

like image 22
Sam Avatar answered Apr 02 '26 13:04

Sam



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!