-
Notifications
You must be signed in to change notification settings - Fork 308
Added support for generic host based IWebHostBuilder #1580
Conversation
- This adds an implementation of IWebHostBuilder as a facade over the IHostBuilder. This removes the 2 container issue by executing the Startup.ConfigureServies and Startup.ConfigureContainer inline as part of building the IHostBuilder. - The implementation is highly compatible implementation since it exposes the same IWebHostBuilder interface. Existing extensions mostly work. - There are some caveats with this approach. - Injecting services into Startup is not extremely constrained to the services availble on HostBuilderContext. This includes the IHostingEnvironment and the IConfiguration. - IStartup is broken when using this pattern because it isn't composable. - The IStartupConfigureServicesFilter and IStartupConfigureContainer The before and after filters added in 2.1 are also broken because there's a single container (it could maybe be fixed by downcasting and doing something specific on the GenericHostBuilder instance). - Calling into IWebHostBuilder.Build will throw a NotSupportedException since this implementation is just a facade over the IHostBuilder.
This might be the way we recommend frameworks plug into the generic host. A single top level method with a builder specific to that framework. I'm thinking that Orleans could potentially use the same approach with the SiloBuilder. cc @ReubenBond @jdom |
I'm glad you went with this approach, as it aligns with what we were thinking for Orleans too. The sub builder approach makes the target of the configuration very clear when you are hosting more than 1 app framework in the same host. |
cc @tillig Just an FYI that this breaks the pattern where you return an IServiceProvider from Startup.ConfigureServices. The preferred pattern will be an IServiceProviderFactory. That makes things compose since the web application configuration isn't the only thing existing. This will require a migration story when moving apps from 2.x to 3.x. Luckily, we're not breaking any of the WebHostBuilder behavior so it can be smooth 😄 . |
Thanks for the heads-up. I'll make sure our corresponding docs and examples get updated! |
Pinging other DI container authors (feel free to spread the word if I missed people) cc @dadhi @seesharper @ipjohnson @khellang @ENikS |
- There's 3 failures but 2 are a known incompatiblity and the last one is a bug
- Put callbacks in place first to run IHostingStartup modifications using a wrapper IWebHostBuilder (now we have 3 of them...).
- Added ISupportsStartup and ISupportsDefaultServiceProvider that circumvents the default extension method logic and delegates to the type itself.
@dotnetjunkie might be interested in this conversation |
@davidfowl Thanks for letting us know up front about changes like this. Correct me if I am wrong, but this is what I can gather from this
|
@seesharper yes! That's exactly correct. |
|
||
public void Dispose() => _host.Dispose(); | ||
|
||
public void Start() => _host.Start(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really right? Is the fact that this class is an implementation of IWebHost
even significant since users can't call .Build()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this is here so you can resolve it as a service, but if you do - is it right to have these methods do what they do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why wouldn't webhost just call base on build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only exists for testing so none of it matters.
internal class GenericWebHostApplicationLifetime : IApplicationLifetime | ||
{ | ||
private readonly Microsoft.Extensions.Hosting.IApplicationLifetime _applicationLifetime; | ||
public GenericWebHostApplicationLifetime(Microsoft.Extensions.Hosting.IApplicationLifetime applicationLifetime) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You from a year ago is trolling you now 😆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😆
services.TryAddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>(); | ||
|
||
// Ensure object pooling is available everywhere. | ||
services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth rethinking some of these kinds of things? In retrospect the choice to make object pool a built-in is really strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Not something I want to change as part of this PR but defiantly something we should revisit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now is a great time to drop it before anyone takes a dependency on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. I'll file an issue for removing things we don't like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We just need routing to add the ObjectPoolProvider https://github.com/aspnet/Routing/blob/7b16053f27ac982eb71317fa8072b55c443aa158/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs#L36.
Currently the only way to build the Autofac container and keep a reference to it if you need to do that (e.g., for later service location using the Autofac container directly) is to return the
Having the ability to return the I gather that in order to do something like this in the new system folks will need a custom |
Is there any way we can bring back the functionality that allows us to inject an logger into the I understand that with this implementation, there is only hard-coded support for I’ve already looked into the |
@poke I don't see how it's possible to bring logging back, it fundamentally depends on DI and config. It's worth noting that logging was already broken (multiple singletons) and one of the reasons we dropped the second container. |
Yeah, I understand that it’s very difficult and not possible in the current design, but I really think that it’s something that is expected to work. So if we don’t end up having a good approach for that, I am sure people will make up a way to achieve this, for example by creating a new (second) logger factory from within their startup. Just like people built the service provider within the |
At least logging can still be injected into Configure, it's only ConfigureServices that's missing it. |
Logging can't really be done because logger providers are themselves in the DI container. |
So if we are logging when we e.g.
|
If you're logging today in ConfigureServices then you're in this torn state where you're using another instance of the ILoggerFactory that's configured. What that can lead to is several background threads running for each of the logger factories (for example the console logger and blob storage logger). Sure. you can create another logger factory that is separate in there but where would you be logging to? |
@tillig Yes, you'd need a custom factory to do that, but you can capture the instance by using the Configure method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update https://github.com/aspnet/Hosting/blob/master/samples/GenericWebHost/ and then I think you're good for now.
😍 |
yep. but can we dispose of this factory (and therefore any bg threads) once we leave the scope? Then there could be issues of flushing, etc ?
Console / some 3rd party service in the example above i have:
so the logging sinks are read-in/defined -prior- to the |
Today, sure, but disposing things you didn't create is an anti pattern. Today the 2 containers hang around for the lifetime of the application. I would do that outside of the HostBuilder completely. Make another container just for logging add the providers and resolve the factory, then used that for logging and dispose it when configure services is over. |
I know but I think there still has to be some way to do it. Even if you don’t necessarily need to log stuff out from the And I really don’t want people to end up doing things like this:
But that’s totally going to happen if we don’t have an actual recommendation on what to do instead. You mentioned on Slack that you are considering a root logger as a singleton, and I think that would be totally fine. Just allow some sane way to get a logger inside of there, so we can direct people to that solution. |
That will be the way to do it regardless, it will require another container. It should not be the same service provider that you use the build up the application though. We'll discuss it more. |
PS that sample code you wrote in the answer makes 2 independent logger systems already. It's just implicit... |
Yeah, but since there already is a separate service provider at web host level by default, it just works. It even uses the same configuration and setup even if it’s technically a separate instance. And that would be my expectation with 3.0 too: That there would be a default and easy way that just works, even if this means that the framework will create a separate logger for me. |
"Just works" There's been numerous unsolvable bugs around the fact that we have 2 containers (sometimes 3!). Most of our workarounds have been to not log anything until after the application container is wired up. We want to be out of that world. |
cc @ENikS I can't remember if I mentioned you on this. |
This removes the 2 container issue by executing the Startup.ConfigureServies and Startup.ConfigureContainer inline as part of building the IHostBuilder.
Existing extensions mostly work.
services availble on HostBuilderContext. This includes the IHostingEnvironment
and the IConfiguration.
and after filters added in 2.1 are also broken because there's a single container (it could maybe be fixed by downcasting and doing something specific on the GenericHostBuilder instance) (@javiercn)
this implementation is just a facade over the IHostBuilder.
Fixes #1544
There are 3 incompatibilities found in running the existing test suite:
This is what we're aiming for with the 3.0 template: