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?
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:
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.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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With