Closed dadhi closed 2 years ago
I would expect this functionality to be somewhere at the registration step, because at that step you define the lifetime of the service.
This "resolve immediately" concept has something to do with the lifetime of the service.
Some services may only want to be observers running in the container (so they have an empty service interface). If their lifetime is not correctly configured (e.g. "resolve immediately" not set to true) then they will not be able to observer events (because they are not automatically activated).
// Will immediatley be resolved in this method call
container.Register<IService, Service>(reuse: Reuse.Singleton, resolveImmediately: true);
// Same as above.
container.Register<IService, Service>(reuse: Reuse.Singleton);
container.Resolve<IService>();
// Will be resolved when scope opens.
container.Register<IService, Service>(reuse: Reuse.Scoped, resolveImmediately: true);
var container = container.OpenScope();
// Same as above.
container.Register<IService, Service>(reuse: Reuse.Scoped);
var scope = container.OpenScope();
container.Resolve<IService>();
// However, here this concept makes no sense.
container.Register<IService, Service>(reuse: Reuse.Transient, resolveImmediately: true);
// Here it is already created, so no need for this concept at all.
container.RegisterInstance<IService>(instance: service);
I have no idea about the specific performance aspects of "how to implement this the most efficient way". But please correct me if my idea is bad.
@onixion Hi here,
how to implement this the most efficient way
I expect this feature to be rarely used - because I was asked about this probably twice in 5 years.
Therefore, I don't want to check for this specific configuration (e.g. resolveImmediately
) every time when scope is opened.
This is the pay-for-play I was talking about, where the User is not paying any performance or increased API complexity cost.
Regarding the proposed API, when adding the option to registration, we need to eagerly (on registration) or lazily (on scope opening) collect the information about all relevant services - it will take time and memory.
Given tho two points, I would rather extend the Container.OpenScope
so you may do anything you want here.
In regard to singleton, the problem is the singleton scope is available from the start when you have called the new Container()
, but you probably need to resolve in the specific moment when the required registrations are added. Maybe it is better to hook onto the CreateServiceProvider
method in the DryIocServiceProviderFactory
.
Ah okey I understand now.
Yeah, you are correct with singletons. You have to define a point in the code when you want to resolve those services. And that call is simply a resolution with IEnumerable<T>
(see below).
For those "special" scoped services I was thinking about the following (on my side):
public static partial class ContainerExtensions
{
public static IContainer ResolveOnScoped<TService>(this IContainer container) where TService : IResolveOnScoped
{
container.RegisterMapping<IResolveOnScoped, TService>(IfAlreadyRegistered.AppendNewImplementation);
return container;
}
public static IContainer ScopeOpened(this IContainer container)
{
container.Resolve<IEnumerable<IResolveOnScoped>>();
return container;
}
}
This would work for me. But I have to call the container.ScopeOpened()
method somehow when the scope opens.
I can implement this for Microsoft DI via the DryIocServiceProviderFactory
. But I am not a big fan of this, but if there is no way to do it, I will probably do this.
I would rather extend the Container.OpenScope so you may do anything you want here
Can you explain what you have in mind? Maybe a short code snippet?
What about a way to setup the container in a way that I can define an action to be called after opening the scope? Or is this not possible because of the pay-for-play constraint?
Thanks!
@onixion I have coded the simplest implementation I can think of.
Basically, for scoped services I made the WithNewOpenScope
method virtual
and for singletons I made virtual
the DryIocServiceProviderFactory.CreateServiceProvider
method.
Here is the example usage based on the sample of Minimal Web API:
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
var container = new MyContainer(Rules.MicrosoftDependencyInjectionRules);
// register natively with DryIoc
container.Register<Bar>();
builder.Host.UseServiceProviderFactory(new MyDryIocServiceProviderFactory(container));
// register via Services collection
builder.Services.AddTransient<Foo>();
// some fun with container extensibility for #539
builder.Services.AddScoped<ScopedAutomaticallyResolved>();
builder.Services.AddSingleton<SingletonAutomaticallyResolved>();
var app = builder.Build();
app.MapGet("/", (Foo foo) => $"Hello world with `{foo}`");
app.MapGet("/bar", (Foo foo, Bar bar) => $"Hello world with `{foo}` and `{bar}`");
app.Run();
public class Foo
{
public Foo(Bar bar = null) {}
}
public class Bar {}
public class ScopedAutomaticallyResolved
{
public readonly SingletonAutomaticallyResolved Singleton;
public ScopedAutomaticallyResolved(SingletonAutomaticallyResolved singleton)
{
Singleton = singleton;
Console.WriteLine("ScopedAutomaticallyResolved created");
}
}
public class SingletonAutomaticallyResolved
{
public SingletonAutomaticallyResolved()
{
Console.WriteLine("SingletonAutomaticallyResolved created");
}
}
public class MyContainer : Container
{
public MyContainer(Rules rules) : base(rules) {}
public override IResolverContext WithNewOpenScope()
{
var scope = base.WithNewOpenScope();
scope.Resolve<ScopedAutomaticallyResolved>();
return scope;
}
}
public class MyDryIocServiceProviderFactory : DryIocServiceProviderFactory
{
public MyDryIocServiceProviderFactory(IContainer container) : base(container) {}
public override IServiceProvider CreateServiceProvider(IContainer container)
{
var provider = base.CreateServiceProvider(container);
container.Resolve<SingletonAutomaticallyResolved>();
return provider;
}
}
@dadhi yes you nailed it! This will definitely work for me! Thanks! 🙂
Originated from the SO question: https://stackoverflow.com/questions/74328129/automatically-resolve-certain-services-when-scope-opens
Think about the generic and performant way (pay-for-play) that may also include the singletons in addition to the scoped services.
One way will be to provide the respective configuration for the DryIoc.MS.DI extension.
Open for ideas on this.