Microsoft.Extensions.Hosting.Host creates hosted services too early.
See original GitHub issueDescribe the bug
ASP.NET Core 3.0’s new Microsoft.Extensions.Hosting.Host
creates hosted services between Startup
’s ConfigureServices
and Configure
methods, where the ‘old’ Microsoft.AspNetCore.WebHost
creates the hosted services only after the Configure
method has run.
To Reproduce
- Create a vanilla ASP.NET Core (Model-View-Controller) Web Application project for ASP.NET Core 3.0.
- Create an empty
MyHostedService
class that implementsIHostedService
. - Add the following registration to the
Startup.ConfigureServices
method:services.AddSingleton<IHostedService>(p => new MyHostedService());
- Run the application
- The
p => new MyHostedService()
is invoked afterStartup.ConfigureServices
ran, but beforeStartup.Configure
runs.
Expected behavior
The delegate should run only after Startup.Configure
ran.
Additional context
This behavior difference between Microsoft.AspNetCore.WebHost
and Microsoft.Extensions.Hosting.Host
is significant, because it disallows users of non-conforming containers (like Simple Injector, Castle Windsor, and Ninject) to resolve hosted services from their container, because the configuration of those containers needs to be finished in the Startup.Configure
phase, while resolving hosted services from that container before that configuration is finished, can lead to problems. Simple Injector, for instance, blocks any registrations made after the first resolve (which will be a resolve for MyHostedService
if you assume the hosted service to be added using p => container.GetInstance<MyHostedService>()
). But even if the container doesn’t block registrations, the early request for hosted services can cause trouble, because the hosted service’s dependencies might not be registered at that point.
Issue Analytics
- State:
- Created 4 years ago
- Comments:15 (7 by maintainers)
Hi @Tratcher,
Thank you for trying to help me out on this, but please keep in mind that Simple Injector is non conforming. With Simple Injector, it is not possible to replace the built-in container. This is really important to understand. The suggestions you make only work for conforming containers like Autofac; not for Simple Injector, Castle Windsor, and Ninject.
Hi @davidfowl,
Thanks for chiming in.
No. Replacing the default controller factory still works and AFAIK all non-conformers still take this approach in ASP.NET Core 3.
The specific scenario that is broken for non-conformers is the case where an application developer wants to add a hosted service (i.e.
IHostedService
) to the ASP.NET Core 3 pipeline, while resolving that hosted service from their application/non-conforming container.Let me give you a full repro to understand. Here are ASP.NET Core v3
Program
andStartup
classes.Program
is the default usingHost
, whileStartup
is adapted using Simple Injector:The
MyHostedService
andMyApplicationService
are simple stubs:The problem here is that Simple Injector needs to be further configured inside the Configure method to be able to achieve “cross wiring.” With cross wiring, the application container pulls in missing registrations from the framework container. The ability to cross wire is important, because application components obviously need to integrate with framework and third-party components.
This cross-wiring can only be applied after an
IServiceProvider
has become available. TheIServiceProvider
is used, for instance, to resolve theIServiceScopeFactory
, and it is used to register an event that is triggered when Simple Injector detects an unregistered service. BecauseIServiceProvider
is not available in during theStartup.Configure
stage, it is impossible to complete Simple Injector’s configuration at that point.With the new
Host
however,IHostedService
implementations are requested from the builtIServiceProvider
beforeStartup.ConfigureServices
is called and, more importantly, the hosted services are started at that point. So when users lets Simple Injector resolve their hosted services, it means that Simple Injector’s build process is triggered at that point. This locks the container, which is similar to the two-phase build process that MS.DI uses (use aServiceCollection
to build and aServiceProvider
to resolve). But after a ‘lock down’, no changes can be made to the container, which means that the application breaks at that point.Note that, even though other non-conforming containers might not lock the container, they still have the same problem, because a resolved hosted service might require a framework component that hasn’t been cross-wired at that point. This would also break the application.
I’ve been thinking about ways to mitigate this, for instance by adding new abstractions to ASP.NET Core. But as long as
Host
keeps requesting and starting hosted services beforeStartup.Configure
, the problem will remain and it becomes impossible for non-conformers to register hosted services as application components.