How to make controllers scoped or singleton instead of transient in ASP.NET Core?
Asked Answered
S

2

21

How to make controllers scoped or singleton instead of transient in ASP.NET Core?

I now that by default the controllers are registered with the default DI container with the transient lifetime.

In case I would like to register them with a different lifetime, how could I do that?

I want to know that solely for the educational purposes, to better wrap my head around the controller types management by the DI container.

Sudor answered 8/7, 2020 at 8:46 Comment(0)
L
33

I now that by default the controllers are registered with the default DI container with the transient lifetime.

By default, controllers are not registered at all. It is the default IControllerActivator that is creating that type without an explicit registration.

If you want to change this, you should call:

services.AddMvc().AddControllersAsServices();

This will ensure that controllers are registered and the original IControllerActivator is replaced with one (ServiceBasedControllerActivator) that resolves controllers from the DI container.

AddControllersAsServices, unfortunately, always registers the controllers using the Transient lifestyle and there's no way to override that behavior. So you have to re-implement AddControllersAsServices:

public static IMvcBuilder AddControllersAsServices(
    this IMvcBuilder builder, ServiceLifetime lifetime)
{
    var feature = new ControllerFeature();
    builder.PartManager.PopulateFeature(feature);

    foreach (var controller in feature.Controllers.Select(c => c.AsType()))
    {
        builder.Services.Add(
            ServiceDescriptor.Describe(controller, controller, lifetime));
    }

    builder.Services.Replace(ServiceDescriptor
        .Transient<IControllerActivator, ServiceBasedControllerActivator>());

    return builder;
}

This new extension method can be used as follows:

services.AddMvc().AddControllersAsServices(ServiceLifetime.Singleton);

WARNING: Do not register your controllers as Singleton in case they derive from Controller or ControllerBase, as these classes contain state (as @MarcoPelegrini correctly notes in the comments) and will malfunction when reused by multiple requests.

Lulita answered 8/7, 2020 at 10:30 Comment(6)
Beware that if your controller keeps state making it singleton is a problem. E.g. in .NET 3.1 if you inherit from Controller / ControllerBase, my understanding is that ViewData and other fields are state that shouldn't be shared.Washko
@MarcoPelegrini, you are absolutely correct. You should never try this using ASP.NET (classic), as controllers store state. This question (and answer) targets ASP.NET Core, but even then you need to be careful not to store any state in your controller.Lulita
Even for .NET Core there is state kept in the controller/controllerbase: github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.ViewFeatures/…Washko
@MarcoPelegrini: that's a good observation. I will add a warning.Lulita
>"...Singletons should be stateless" I understand that you want to avoid registering as Transient to avoid the performance overhead creating the instance on every HTTP request. However what makes you think singletons in general should be stateless? One of the reasons why you would want to use a singleton object (from other than performance reasons) is that it maintains a state. Isn't a better formulation that that MVC Controllers (does not apply to REST API controllers) should not be singletons because it breaks their implementation?Shirline
@user3625699: this previous statement of mine is too simplistic. Singletons certainly can have state and they sometimes do in the applications I build.Lulita
D
1

The validated answer strictly responds to the question because it implicitly asks for how to register all controllers with a different (and same) lifetime. For those who wants to do it only for some specific controllers, a simple way consist in using:

services.AddSingleton<MySingletonController>();

before:

services.AddMvc().AddControllersAsServices();

It works fine because AddControllersAsServices use builder.Services.TryAddTransient. Then it won’t register MySingletonController as transient because it’s already done under the Singleton Lifetime.

And, as mentioned, do not derive MySingletonController from Controller or ControllerBase as it should be stateless.

Donne answered 16/5, 2024 at 7:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.