Skip to content

[API Proposal]: Add IHostApplicationBuilder interface #85486

Closed

Description

@eerhardt

Background and motivation

In .NET 6, ASP.NET added a WebApplicationBuilder "minimal hosting" API. This was a new, non-callback approach to the HostBuilder Generic Host API. In .NET 7, Extensions.Hosting added a similar "generic host" API that followed the same non-callback API design.

The problem is these two hosting APIs have the same members (mostly), but don't have a common base abstraction that libraries can add extension methods to. In the previous, callback API IHostBuilder was the interface libraries could extend and add their library-specific DI services, etc. to.

To enable this, we should add a general abstraction to these hosting builder APIs.

See the conversations at:

API Proposal

namespace Microsoft.Extensions.Hosting;

public interface IHostApplicationBuilder
{
    IDictionary<object, object> Properties { get; }

    IConfigurationManager Configuration { get; }
    IHostEnvironment Environment { get; }
    ILoggingBuilder Logging { get; }
    IServiceCollection Services { get; }

    IHost Build();  // See Note below on `Build()`
    void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder>factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder: notnull;
}

-public sealed class HostApplicationBuilder {}
+public sealed class HostApplicationBuilder : IHostApplicationBuilder {}

namespace Microsoft.Extensions.Configuration;

public interface IConfigurationManager : IConfiguration, IConfigurationBuilder
{
}

-public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable {}
+public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable, IConfigurationManager {}

Note on Build()

If we wanted to make this API work for all ApplicationBuilders like MauiAppBuilder and WebAssemblyHostBuilder, we would need to think about IHost Build(). Those 2 app models follow this same ApplicationBuilder pattern, but the things they build don't implement IHost. Options here are:

  • Don't put Build() in the interface. But the name of the interface is "Builder", so it doesn't feel right to not have a Build method.
  • For the app models that don't support IHost, they would throw NotSupported for this method. This is my recommendation.

API Usage

Using an existing extension method on IHostBuilder as an example, a new extension method could be written like:

public static class HostBuilderExtensions
{
    public static IHostBuilder AddSteeltoe(this IHostApplicationBuilder hostBuilder, IEnumerable<string> exclusions = null, ILoggerFactory loggerFactory = null)
    {
...
        hostBuilder.Properties[LoggerName] = logger;

        hostBuilder.Configuration.AddSteeltoeConfigServer();
        hostBuilder.Services.AddMongoClient()
    }
}

Alternative Designs

We could make WebApplicationBuilder and HostApplicationBuilder explicitly implement IHostBuilder, but this breaks the API design of making the hosting code minimal, and running inline. It would cause a lot of confusion on when services, config, etc gets added in which order. For this reason, we don't want to keep using the IHostBuilder interface in new code.

Risks

N/A

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Apr 27, 2023
  2. eerhardt commented on Apr 27, 2023

    @eerhardt
    MemberAuthor
  3. davidfowl commented on Apr 27, 2023

    @davidfowl
    Member

    We have to fix the Configuration API to allow reading it. Maybe we need an interface for the ConfigurationManager (this is why interfaces are great 😄).

  4. eerhardt commented on Apr 27, 2023

    @eerhardt
    MemberAuthor

    We have to fix the Configuration API to allow reading it.

    How do you feel about 2 properties?

    public interface IHostApplicationBuilder
    {
        IConfiguration Configuration { get; }
        IConfigurationBuilder ConfigurationBuilder { get; }

    ?

  5. davidfowl commented on Apr 27, 2023

    @davidfowl
    Member

    Doesn't feel good, but it's an option.

  6. eerhardt commented on Apr 28, 2023

    @eerhardt
    MemberAuthor

    Updated the proposed API to include a new interface IConfigurationManager : IConfiguration, IConfigurationBuilder

  7. eerhardt commented on Apr 28, 2023

    @eerhardt
    MemberAuthor

    cc @dotnet/area-extensions-configuration as well, since this proposal spans both Hosting and Configuration.

  8. steveharter commented on May 3, 2023

    @steveharter
    Contributor

    Who is planning on driving and implementing this?

  9. ericstj commented on May 4, 2023

    @ericstj
    Member

    @eerhardt @halter73 -- does this proposal imply libraries need to extend both IHostBuilder and IHostApplicationBuilder if they'd want to work in both minimal APIs and not?

  10. halter73 commented on May 4, 2023

    @halter73
    Member

    The majority of libraries can get by just extending IServiceCollection. But if libraries need to also add config source and add services in a single method, or read the config/environment before adding services, they'll need to extend both IHostBuilder and IHostApplicationBuilder as you say.

    I was able to update UseWindowsService and UseSystemd which previously targeted IHostBuilder to target IServiceCollection instead by reading the IHostEnvironment when constructing services rather than when adding them collection. I was hoping that most IHostBuilder extension methods could be updated to do something similar to avoid adding yet another builder interface.

    If you extend just IHostBuilder, it will often still work with WebApplicationBuilder via the WebApplicationBuilder.Host property unless the extension method tries changing the host environment (e.g. by changing the content root) or calling Build() which is simply unsupported.

    We could consider adding a similar IHostBuilder property to HostApplicationBuilder. We've already done most of the work to implement it because that's what's exposed via the "Microsoft.Extensions.Hosting" DiagnosticListener, but we decided against this in the original HostApplicationBuilder API review because of the inability to change certain host configuration keys like HostDefaults.ContentRootKey in ConfigureHostConfiguration or call Build() was clunky.

    internal static DiagnosticListener LogHostBuilding(HostApplicationBuilder hostApplicationBuilder)
    {
    var diagnosticListener = new DiagnosticListener(HostBuildingDiagnosticListenerName);
    if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(HostBuildingEventName))
    {
    Write(diagnosticListener, HostBuildingEventName, hostApplicationBuilder.AsHostBuilder());
    }
    return diagnosticListener;
    }

    // It's okay if the ConfigureHostConfiguration callbacks either left the config unchanged or set it back to the real ContentRootPath.
    // Setting it to anything else indicates code intends to change the content root via HostFactoryResolver which is unsupported.
    string? currentContentRootConfig = config[HostDefaults.ContentRootKey];
    if (!string.Equals(previousContentRootConfig, currentContentRootConfig, StringComparison.OrdinalIgnoreCase) &&
    !string.Equals(previousContentRootPath, HostBuilder.ResolveContentRootPath(currentContentRootConfig, AppContext.BaseDirectory), StringComparison.OrdinalIgnoreCase))
    {
    throw new NotSupportedException(SR.Format(SR.ContentRootChangeNotSupported, previousContentRootConfig, currentContentRootConfig));
    }

  11. eerhardt commented on May 4, 2023

    @eerhardt
    MemberAuthor

    The majority of libraries can get by just extending IServiceCollection

    Agreed. That is what I found most libraries doing. Only a few, bigger libraries actually needed to extend IHostBuilder.

    https://grep.app/search?q=this%20IHostBuilder

    There are quite a few of cases I found that are just using .ConfigureServices.

    We could consider adding a similar IHostBuilder property to HostApplicationBuilder

    I am not in favor of doing this. I think we need to break away from the old, callback approach. My reasoning is in the Alternative Designs section above.

  12. davidfowl commented on May 4, 2023

    @davidfowl
    Member

    Let's push forward with @eerhardt 's proposal.

  13. 14 remaining items

  14. tarekgh commented on May 19, 2023

    @tarekgh
    Member

    @eerhardt

    @noahfalk is currently working on creating the proposal. So, it is not out yet.

  15. davidfowl commented on May 19, 2023

    @davidfowl
    Member

    Part of that was that we know of new things that will be on these builders. Not saying it will be an abstract base class (you know how much I like those!) but we do need to evolve this API.

  16. eerhardt commented on May 19, 2023

    @eerhardt
    MemberAuthor

    We've established using DIMs in Microsoft.Extensions.* for net6.0+ targets. For example, see #50406.

  17. davidfowl commented on May 19, 2023

    @davidfowl
    Member

    I love it!

  18. noahfalk commented on May 19, 2023

    @noahfalk
    Member

    @noahfalk is currently working on creating the proposal. So, it is not out yet.

    I have my working draft here: https://gist.github.com/noahfalk/32bb919907c972ac06ac128bc767d48d#integration-with-hosting-apis

    I've never worked in the hosting APIs before so the current design follows the philosophy "duplicate whatever ILoggingBuilder did". Feedback is welcome if you think there is something we should be doing differently.

  19. terrajobst commented on May 30, 2023

    @terrajobst
    Contributor

    Video

    • We should remove the Build method
      • The people consuming the abstractions don't need it and the final result will have different types to be useful
     namespace Microsoft.Extensions.Hosting;
     
     public interface IHostApplicationBuilder
     {
         IDictionary<object, object> Properties { get; }
     
         IConfigurationManager Configuration { get; }
         IHostEnvironment Environment { get; }
         ILoggingBuilder Logging { get; }
         IServiceCollection Services { get; }
     
         void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder>factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder: notnull;
     }
    
    -public sealed class HostApplicationBuilder {}
    +public sealed class HostApplicationBuilder : IHostApplicationBuilder {}
     namespace Microsoft.Extensions.Configuration;
     
     public interface IConfigurationManager : IConfiguration, IConfigurationBuilder
     {
     }
    
    -public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable {}
    +public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable, IConfigurationManager {}
  20. added
    api-approvedAPI was approved in API review, it can be implemented
    and removed
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    on May 30, 2023
  21. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on May 31, 2023
  22. added a commit that references this issue on Jun 8, 2023
    19dbb7f
  23. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Jun 8, 2023
  24. ghost locked as resolved and limited conversation to collaborators on Jul 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions