-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Microsoft.Extensions.Hosting.Host creates hosted services too early. #14585
Comments
Why is DI still being modified in Configure? |
Because we need a fully configured and built MS.DI container to cross-wire to framework services such as |
@davidfowl @halter73 who are more familiar with DI interop. Configure is not the place for that, it's just too late. There are several different supported ways to configure DI containers and we should be able to find one of them that works for you. Have you tried working with the IServiceProviderFactory? That should let you intercept the container as it's completed and wrap it as needed. |
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. |
I think this is not so much about the serviceprovider, but more the configuration phase. In Configure, routes are setup, additional options set, etc. They wouldn't be available now, or components wrongly setup with defaults when hosted services start if I'm understanding this correctly. e.g.
|
So is this specifically about resolving things from an |
This is about hosted services being resolved too early in the pipeline, breaking non-conforming containers. |
Help me understand the actual problem though. Are you saying non-conforming containers are broken all up? As in you replace the controller factory and instantiate MVC controllers anymore? Or is something more narrow broken? Sorry, I'm being a bit dense but I'm not putting it altogether and this issue is very specific. Some background would help. |
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. Let me give you a full repro to understand. Here are ASP.NET Core v3 public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Startup
{
// Create the application container (in this case Simple Injector)
private readonly Container container = new Container();
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// Adds Simple Injector integration (using SimpleInjector.Intagration.ServiceCollection)
services.AddSimpleInjector(this.container);
// Registers application components into the application container
container.RegisterSingleton<MyApplicationService>();
// Register the hosted service in the application container
container.RegisterSingleton<MyHostedService>();
// Cross-wire the hosted service into the (MS.DI) framework container.
// The ASP.NET Core Host resolves all hosted services.
// Here it is done by hand, but Simple Injector also contains an AddHostedService
// extension method, but that does basically the same.
services.AddSingleton<IHostedService>(
p => container.GetInstance<MyHostedService>()); // ## <-- called before Configure
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Finalizes the Simple Injector configuration by enabling cross-wiring.
app.ApplicationServices.UseSimpleInjector(this.container);
// What .UseSimpleInjector() does under the covers (among other things) is cross-wiring
// an Microsoft.Extensions.DependencyInjection.IServiceScope.
container.Register<IServiceScope>( // ## <-- This breaks with the new Host class.
()=>app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope(),
Lifestyle.Scoped);
// Usual ASP.NET Core stuff here.
}
} The // Some application component
public class MyApplicationService { }
// Some hosted service depending on an application component
public class MyHostedService : IHostedService
{
private readonly MyApplicationService service;
public MyHostedService(MyApplicationService service)
{
this.service = service;
}
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
} 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 With the new 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 |
OK thanks for that. The public class SimpleInjectorServiceProviderFactory : IServiceProviderFactory<(Container, IServiceCollection)>
{
public (Container, IServiceCollection) CreateBuilder(IServiceCollection services)
{
// Adds Simple Injector integration (using SimpleInjector.Intagration.ServiceCollection)
var container = new Container();
services.AddSimpleInjector(container);
return (container, services);
}
public IServiceProvider CreateServiceProvider((Container, IServiceCollection) containerBuilder)
{
var (container, services) = containerBuilder;
var serviceProvider = services.BuildServiceProvider();
// Finalizes the Simple Injector configuration by enabling cross-wiring.
serviceProvider.UseSimpleInjector(container);
return serviceProvider;
}
} public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseServiceProviderFactory(new SimpleInjectorServiceProviderFactory()); // Wire up simple injector
} The factory is wiring up simple injector before ConfigureServices is called and also gets a chance to run logic before the service provider is built. Notice I'm still returning the default service provider to the system but in the mean time, the simple injector container has been configured and cross wires framework dependencies at the right and appropriately. Now for the part that you may not like: using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SimpleInjector;
namespace WebApplication351
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
public void ConfigureContainer((Container, IServiceCollection) builder)
{
var (container, services) = builder;
// Registers application components into the application container
container.RegisterSingleton<MyApplicationService>();
// Register the hosted service in the application container
container.RegisterSingleton<MyHostedService>();
// Cross-wire the hosted service into the (MS.DI) framework container.
// The ASP.NET Core Host resolves all hosted services.
// Here it is done by hand, but Simple Injector also contains an AddHostedService
// extension method, but that does basically the same.
services.AddSingleton<IHostedService>(
p => container.GetInstance<MyHostedService>()); // ## <-- called before Configure
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
} The The above IMO plugs in a bit more cleaning to the container lifetime while not forcing non-conforming containers to suddenly conform. Thoughts? |
@davidfowl, I have to research what the consequences are of this approach and whether or not we would run into different problems using this approach. The My main observation, though, is that ASP.NET Core 3 introduces this breaking change that forces us to completely remodel, retest, and redocument our integration packages, and communicate these changes to our users. This will raise new questions, and support. This is a major effort on our part, and can be a major frustration or confusion for our users. Two questions:
|
I can appreciate that frustration and in part, it's why we chose not to deprecate the WebHost in this release cycle. Also, we shipped 10 preview versions which unfortunately nobody tested this specific scenario with
The generic host has a single extensibility point, the Changing this behavior would mean introducing some new phase in the host that happens before hosted services are run and then re-implementing parts of the WebHost on top of that. The other thing we did was make sure there was a single container for the entire application. As you know the WebHost ends up with 2 or 3 containers depending on the scenario which could lead to different instances of singletons existing in the dependency tree (which was always confusing to customers).
Yes, we'll likely deprecate it in 5.0 and remove it in 6 or 7. Which should be more than enough time to adjust. PS: The extensibility point above is a much cleaner way to expose the non-conforming container (albeit a new way), that plugs in at the "right time". |
I just published Simple Injector v4.8, which now allows Simple Injector users to integrate hosted services while using the new ASP.NET Core 3 During my research I found a way to circumvent the behavioral difference between |
Should I look? |
Upgraded to 4.8 and now hosted services works very well... I had to change some behaviors of my code because of the 3.0 changes. 👍 |
Describe the bug
ASP.NET Core 3.0's new
Microsoft.Extensions.Hosting.Host
creates hosted services betweenStartup
'sConfigureServices
andConfigure
methods, where the 'old'Microsoft.AspNetCore.WebHost
creates the hosted services only after theConfigure
method has run.To Reproduce
MyHostedService
class that implementsIHostedService
.Startup.ConfigureServices
method:services.AddSingleton<IHostedService>(p => new MyHostedService());
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
andMicrosoft.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 theStartup.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 forMyHostedService
if you assume the hosted service to be added usingp => 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.The text was updated successfully, but these errors were encountered: