Skip to content

Add ISupportsConfigureWebHost interface to hosting. #34824

@halter73

Description

@halter73

API Proposal

namespace Microsoft.AspNetCore.Hosting.Infrastructure
{
+    public interface ISupportsStartup
+    {
+        IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
+        IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType);
+        IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory);
+    }
+
+    public interface ISupportsConfigureWebHost
+    {
+        IHostBuilder ConfigureWebHost(Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureOptions);
+    }

We would implement ISupportsStartup in ConfigureWebHostBuilder (i.e. WebApplicationBuilder.WebHost) and ISupportsConfigureWebHost in ConfigureHostBuilder (i.e. WebApplicationBuilder.Host) .

Justifcation

In order to make builder.WebHost.UseStartup and builder.WebHost.Configure throw instead of no-op in the following:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStartup<Startup>();
var app = builder.Build();
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.Configure(app =>
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello world from configure!");
    });
});
var app = builder.Build();

This is because both UseStartup and Configure will try to pass through their calls to any IWebHostBuilder implementing ISupportsStartup giving us the opportunity to throw.

Currently only GenericWebHostBuilder implements ISupportsStartup and it lives in the same assembly so it can be internal. ConfigureWebHostBuilder is in another assembly that depends on Hosting, so it needs the interface to be public to implement it.

Alternative considered

  1. namespace Microsoft.AspNetCore.Hosting
    {
    +    public interface IRejectStartup
    +    {
    +        void ThrowException();
    +    }

    With this design, IRejectStartup is not just a marker interface, but it also throws an Exception via ThrowException() which would be called by the unsupported extension methods allowing any implementers of the interface to provide more detailed error messages. I'm not sure this is necessary, and if we wanted to support throwing different exceptions from Configure and UseStartup, the interface becomes more complicated.

  2. namespace Microsoft.AspNetCore.Hosting
    {
    +    public interface ISupportsStartup
    +    {
    +        IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
    +        IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType);
    +        IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory);
    +    }

    This feels like it exposes more than we have to and doesn't allow us to throw from ConfigureWebHost().

  3. If ConfigureWebHostBuilder continued not to implement ISupportsStartup, we could look for additions of IStartup services to the WebApplicationServiceCollection instead and throw if that ever happens. This feels a lot less straightforward than implementing ISupportsStartup however.

  4. namespace Microsoft.AspNetCore.Hosting
    {
    +    /// <summary>
    +    /// A marker interface used to indicate the <see cref="IHostBuilder"/> or <see cref="IWebHostBuilder"/>
    +    /// does not support any of the following extension methods:
    +    /// * <see cref="WebHostBuilderExtensions.Configure(IWebHostBuilder,         Action{AspNetCore.Builder.IApplicationBuilder})"/>
    +    /// * <see cref="WebHostBuilderExtensions.UseStartup(IWebHostBuilder, Type)"/>
    +    /// * <see cref="GenericHostWebHostBuilderExtensions.ConfigureWebHost(IHostBuilder, Action{IWebHostBuilder})"/>
    +    /// </summary>
    +    public interface IRejectStartup
    +    {
    +    }

This allows us to check inside these extension methods whether they should throw to indicate they're not supported by the given IHostBuilder or IWebHostBuilder (namely WebApplicationBuilder.Host/WebHost) rather than effectively doing nothing.


Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-minimal-hosting

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions