There is a service IFoo from an external library, which has its own mechanisms for registering its components. I need to register the service in the Dry IoC container, along with its additional components inside it, and then call the Start() method of the service, which will start the service to perform the tasks it is meant to handle.
public interface IFoo
{
public void RegisterSomething<T>();
public void Start();
}
The NuGet package contains the AddFooServices() method, which registers this service and all necessary services for the library to work in the IServiceCollection.
We want to register IFoo in our Register() method of class ModuleA, which will be called in Program.cs, similarly to how we register all services in the Dry IoC container in our project. However, to obtain an instance of this service, we need an instance of the IServiceCollection, which has not yet been created at this point because the ConfigureServices method from the Startup class has not been called yet.
public static class Program
{
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseDryIoc(
ModuleA.Register,
ModuleB.Register)
.ConfigureWebHostDefaults(web =>
{
web.UseStartup<Startup>()
.UseIIS();
});
public static void Main(string[] args)
=> CreateHostBuilder(args)
.Build()
.Run();
}
The question is: how can we obtain an instance of the IFoo service from IServiceCollection during registration in Dry IoC?
We made an attempted to solve this issue, but we don’t like the following approach. We register a delegate using RegisterInitializer<IFoo>((service, resolver) => ...), which will be called upon the first request to the service.
public static class ModuleA
{
public static IServiceCollection AddServices(this IServiceCollection services)
{
services.AddFooServices();
return services;
}
public static void Register(IContainer container)
{
container.Register<Bar>(Reuse.Singleton);
container.RegisterInitializer<IFoo>((service, resolver) =>
resolver.Resolve<Bar>());
}
internal class Bar
{
public IFoo Service { get; init; } = null!;
public Bar()
{
Service.RegisterSomething<Something>();
Service.Start();
}
}
}
However, this leads to overhead when the application runs, as the first request to the service will be slow. At that point, we also have to register the service, start it, and wait for it to start, but we would prefer to do this beforehand, at the registration stage.
Additionally, there is a question regarding the principles of how RegisterInitializer works. Suppose we have a service IFoo, which is injected as a dependency into class Bar. Both the service and the class are registered in the Dry IoC container as Singletons. In this case, by using RegisterInitializer<IFoo>((service, resolver) => ...), we register the Bar() constructor as a delegate, which will be called upon accessing IFoo.
Next, we create instances of two other services that depend on IFoo. When these services access the IFoo instance, will the delegate passed as a parameter to RegisterInitializer() be called more than once?
Based on the documentation, the delegate should be called more than once; however, when I checked this in a separate project, it was called only once.
Pass RegisterSomething<Something>() as a parameter to your registration method AddServices, similar to how you do with registration in DryIoc
services.AddServices(RegisterSomething<Something>())
Then register IFoo so that its instance creation delegate is defined, and use it for the passed method RegisterSomething<Something>()
services.AddSingleton<IFoo>((provider) =>
{
var foo = ... // use Resolve/GetService/Activator/what do you want to create an instance
somethingRegistration(foo);
return foo;
});
And to initialize in advance, you can use IHostedService
services.AddHostedService<AbcHostedService>();
where AbcHostedService:
class AbcHostedService: BackgroundService
{
private readonly IFoo foo;
public AbcHostedService(IFoo foo)
{
this.foo = foo; // here we get an initialized instance for which RegisterSomething has already been applied
}
protected override async Task ExecuteAsync(CancellationToken token)
{
this.foo.Start();
}
}
This way, when your application starts, your instance will be initialized.
You could explicitly resolve IFoo somewhere in your app. That would move the overhead of waiting for the service to start away from the first use of it. Maybe in Startup.
Alternatively, you could have a completely separate container inside of ModuleA, just to resolve IFoo, perform its sartup logic and then register that instance into your main container, e.g. using the container from microsoft
using Microsoft.Extensions.DependencyInjection;
public static class ModuleA
{
public static void Register(IContainer container)
{
var services = new ServiceCollection();
services.AddFooServices();
var serviceProvider = services.BuildServiceProvider();
var foo = serviceProvider.GetRequiredService<IFoo>();
foo.RegisterSomething<Something>();
foo.Start();
container.RegisterInstance(foo);
}
}
You should additionally tie the lifetime of serviceProvider to that of your main container, such that it will be Dispose()d when your main container is.
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