Skip to content

Add UseHost for WebApplicationBuilder (or maybe IHostApplicationBuilder) #2268

Open
@djeikyb

Description

@djeikyb

My team maintains a bunch of http apis using asp core. We've added a cli interface to each app because tho starting kestrel is the main case, there are several others, for example

  • configure backing services (eg, an auth provider)
  • run migrations
  • self-check: is all necessary configuration available?

Some are only invoked when running on a local dev machine, others are used in deploy pipelines.

The included System.CommandLine.Hosting.HostingExtensions has a UseHost for an IHostBuilder, but the host build object I have is an WebApplicationBuilder. Building the entire host is somewhat expensive, to be sure. But it's useful enough to be worth it, and the things we're running need most of the app anyway.

So I copied the UseHost function and swapped in the WebApplicationBuilder type. Mostly it's the same, except that the host is neither stopped nor started. I want the command handlers to decide whether or not to start the host! My RunWebApi handler does start the host, but my SelfCheck handler only needs the built host's ServicesCollection.

Does it make sense to add this extension method to the library? As I wrote this out, I realized that maybe IHostApplicationBuilder is a better type than WebApplicationBuilder, but the idea stands.

using System.CommandLine.Builder;
using System.CommandLine.Invocation;

namespace System.CommandLine.Hosting;

public static class HostingExtensions
{
    private const string ConfigurationDirectiveName = "config";
    public static CommandLineBuilder UseHost(this CommandLineBuilder builder,
        Func<string[], WebApplicationBuilder> hostBuilderFactory,
        Action<WebApplicationBuilder>? configureHost = null) =>
        builder.AddMiddleware(async (invocation, next) =>
        {
            var argsRemaining = invocation.ParseResult.UnparsedTokens.ToArray();
            var hostBuilder = hostBuilderFactory.Invoke(argsRemaining);
            hostBuilder.Host.Properties[typeof(InvocationContext)] = invocation;

            hostBuilder.Host.ConfigureHostConfiguration(config =>
            {
                config.AddCommandLineDirectives(invocation.ParseResult, ConfigurationDirectiveName);
            });
            hostBuilder.Host.ConfigureServices(services =>
            {
                services.AddSingleton(invocation);
                services.AddSingleton(invocation.BindingContext);
                services.AddSingleton(invocation.Console);
                services.AddTransient(_ => invocation.InvocationResult);
                services.AddTransient(_ => invocation.ParseResult);
            });
            hostBuilder.Host.UseInvocationLifetime(invocation);
            configureHost?.Invoke(hostBuilder);

            using var host = hostBuilder.Build();

            invocation.BindingContext.AddService(typeof(WebApplication), _ => host);

            // await host.StartAsync(); // no: let cli handlers decide if they want to start the host

            await next(invocation);

            // await host.StopAsync(); // don't stop what wasn't started
        });

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions