What is the correct DefaultScopedLifestyle for an asp.net Core Web API service using Simple Injector?

1.4k Views Asked by At

I'm trying to create a .net core WebApi service using Simple Injector. In my Startup.cs I have this:

public Startup(IHostingEnvironment env)
{
    // Setup configuration stuff
    container = new Container();
    container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();   // This is as specified in the docs.
}

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.AddOptions();
    services.AddSingleton<IControllerActivator>(
        new SimpleInjectorControllerActivator(this.container));
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseSimpleInjectorAspNetRequestScoping(this.container);

    InitializeContainer(app);        // This registers stuff

    this.container.Verify();         // This blows up.
    app.UseMvc();
}

private void InitializeContainer(IApplicationBuilder app)
{
    // Add application presentation components:
    this.container.RegisterMvcControllers(app);
    this.container.RegisterSingleton(new Log4NetAdapter(LogManager.GetLogger("Services.Api").Logger));

    // Register other stuff here.
}

This compiles fine. But when I try to start the service I get the following error message on the container.Verify() step:

{"The configuration is invalid. The following diagnostic warnings were reported: -[Disposable Transient Component] MessageController is registered as transient, but implements IDisposable. See the Error property for detailed information about the warnings. Please see https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual warnings."}

-[Disposable Transient Component] MessageController is registered as transient, but implements IDisposable. The configuration is invalid. The following diagnostic warnings were reported: See the Error property for detailed information about the warnings. Please see https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual warnings.

I tried including the necessary nuget package to use the WebApiRequestLifestyle, but it appears that's only valid with standard .Net projects; not .net core.

I found this page in the docs. I'm able to run the service by ignoring the warning for my specific controllers:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseSimpleInjectorAspNetRequestScoping(this.container);

    InitializeContainer(app);        // This registers stuff

    var registration = container.GetRegistration(typeof(MessageController)).Registration;

    registration.SuppressDiagnosticWarning(DiagnosticType.DisposableTransientComponent, "This is a Web API controller. Disposal should be handled by the system.");

    this.container.Verify();         // Now this works
    app.UseMvc();
}

The problem with that is that now I have to go in and suppress warnings for every additional controller. Shouldn't that be handled by the Default Lifestyle?

1

There are 1 best solutions below

6
On BEST ANSWER

When it comes to ASP.NET Core, there is just one scoped lifestyle: AspNetRequestLifestyle. It doesn't matter whether you are building an Core MVC or Core Web API application, this lifestyle will work for all.

The error you are getting has nothing to do with the selected scoped lifestyle. You would get this exact same lifestyle if you hadn't configured a scoped lifestyle.

If you follow the integration guide, you'll see the following line of code:

container.RegisterMvcControllers(app);

The RegisterMvcControllers will register all controllers for you (which is an adviced practice) and while doing so, apply this suppression per controller for you. Suppressing the warning is essential, because there is a design flaw in MVC concerning controllers. The controller base class implements IDisposable which causes each derived controller to implement it as well. This is a Dependency Inversion Principle violation.

Because the controllers implement IDisposable, Simple Injector is warning about that, because it sees a transient component that needs disposal, since Simple Injector will not dispose transients for you (since there is not always an available scope where the transient can be stored in for later disposal).

A typical solution would be to register such disposable component as Scoped, but in the case of controllers, this leads to complexity as well, since this means that every dependency must be (at least) scoped.

So what RegisterMvcControllers does under the covers is to analyze whether the derived class overrides Dispose(bool). If it doesn't override Dispose(bool), the warning will be suppressed. If it doesn't override Dispose, the warning can be suppressed, because the base class doesn't dispose anything (which is why the base class shouldn't have implemented IDisposable in the first place). This means that once you override Dispose(bool) inside your controller, you will (rightly) see this warning popup again.

My best guess is that either MessageController overrides Dispose(bool) or MessageController derives from a custom base class that overrides Dispose(bool).

Note that your controllers should hardly ever have dispose logic:

  • Controllers should only be dealing with presentation logic, and this means that any other logic should be extracted into another class that you inject into the controller.
  • Application components (such as controllers) should not dispose any of its incoming dependencies; this is something that your DI container should handle for you.

In the rare case that the controller should actually do the disposing itself, you can override the default registration for that particular controller and register it as scoped. This will ensure that the controller gets disposed when the request ends.

So the RegisterMvcControllers method implements this ugly hack for you around this design quirk in ASP.NET Core MVC.