Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core Singleton Creation is called multiple times

I'm registering a service as a singleton in .NET Core. Yet I'm seeing the constructor for the singleton called multiple times.

services.AddSingleton<DbAuthorizationOptions, ContextAuthorizationOptions>();

My context authorization options is just Dictionary of Entity Types to IValidators, The context authorization options are passed into the DBContext, to automatically run validations.

During the registration of my services, I also register dynamic Validators with my container registered in DI.

var useDynamicValidator = serviceOption.ValidatorOptions != null;
if(useDynamicValidator)
{
    //TODO: Extract this to before the register service no sense in building the provider each time
    //TODO: Make this cleaner don't be dependent on Authorization options
    var provider = services.BuildServiceProvider();
    var authOptions = provider.GetService<DbAuthorizationOptions>();
    var validator = BuildDynamicValidatorFactory(serviceOption).Invoke(provider, null);
    authOptions.ValidatorOptions.AddValidatorForSet(validator);
}

I notice that when I call GetService on the provider I receive a new singleton instead of the existing one. Does building the provider create a new container so all of the services get re-registered?

If so, How can I call a method to register my dynamic validators in the singleton container with the existing IServiceProvider, is there a way to invoke some registration once after the service container is built?

like image 539
johnny 5 Avatar asked Feb 18 '18 20:02

johnny 5


People also ask

Can I call BuildServiceProvider multiple times?

Calling BuildServiceProvider multiple times can cause serious trouble, because each call to BuildServiceProvider results in a new container instance with its own cache. This means that registrations that are expected to have the Singleton lifestyle, suddenly are created more than once.

How does Singleton work in net core?

The Singleton Design Pattern, as the name suggests, restricts the instantiation of a class to one object only. In other words, a class that follows the Singleton Design Pattern will not allow more than one instance of it to be created. The typical use cases of the Singleton Design Pattern are: LogManager.

What is the difference between Singleton transient and scoped?

Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every request.

Should I use singleton or scoped?

Scoped services service is the better option when you want to maintain state within a request. Singletons are created only once and not destroyed until the end of the Application. Any memory leaks in these services will build up over time.


1 Answers

Does building the provider create a new container so all of the services get reregistered?

Yes. See the source code.

If so, How can I call a method to register my dynamic validators in the singleton container with the existing IServiceProvider, is there a way to invoke some registration once after the servicecontainer is built?

I'm not really understanding why this is a problem. You should be registering all of your services one time at application startup in the Composition Root.

The DI container is then responsible for resolving the object graphs of the application. The application itself shouldn't have a dependency on it, nor be required to update it.

You should be injecting DbAuthorizationOptions in the place where you need to use it.

public class Foo : IFoo
{
    private readonly DbAuthorizationOptions authOptions;

    public Foo(DbAuthorizationOptions authOptions) // <-- Inject parameters
    {
        this.authOptions = authOptions ??
            throw new ArgumentNullException(nameof(authOptions));
    }

    public void DoSomething()
    {
        // TODO: Inject the type that has the BuildDynamicValidatorFactory
        // method and the serviceOption (whatever type that is) here
        // either as a method parameter of this method, or a constructor
        // parameter of this class.
        var validator = BuildDynamicValidatorFactory(serviceOption).Invoke(provider, null);
        // Now we have an instance of authOptions that can be used
        authOptions.ValidatorOptions.AddValidatorForSet(validator);
    }
}

Note that the DI container automatically provides the DbAuthorizationOptions if injected into another type that is also resolved through DI (such as a controller or filter).

NOTE: It isn't very clear from your question where you need to do this. You mention that you want it to happen once, which usually means to put it at application startup. But users cannot interact with code that runs at startup. So, maybe you could use a filter. It really all depends on where in the lifecycle of the application it has to happen.

like image 87
NightOwl888 Avatar answered Oct 22 '22 21:10

NightOwl888