.NET 6 IWebHost vs IHost vs WebApplication: Startup class with ILogger Dependency Injection

Viewed 57

I migrated a .NET WebApp from originally 2.1 to 3.1 and 6.0.

I would like to switch to the new 'minimal hosting model' from .NET 6 but still want to keep my long and complex Startup class separated.

I am using Serilog with custom configuration as ILogger implementation: I create a LoggerBuilder in a separate class.

My main looks like this:

public static async Task Main(string[] args)
{
    // global shared logger, created BEFORE the host build to be able to log starting and ending the service.
    Log.Logger = LoggerBuilder.BuildLogger();

    try
    {
        LoggerAudit.LogAuditAsWarning("Starting " + Constants.ServiceName);
        using var source = new CancellationTokenSource();
        await CreateWebHost(args).RunAsync(source.Token).ConfigureAwait(false);
        //await CreateHost(args).RunAsync(source.Token).ConfigureAwait(false);
        //await CreateWebApp(args).RunAsync(source.Token).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        LoggerAudit.GlobalLogAudit.Fatal(ex, Constants.ServiceName + " terminated unexpectedly");
    }
    finally
    {
        LoggerAudit.LogAuditAsWarning("Closing " + Constants.ServiceName);
        Log.CloseAndFlush();
    }
}

with 3 implementations of the Host or WebApp, which should be equivalent

private static IWebHost CreateWebHost(string[] args)  // .NET Core 2.1 way
{
    IWebHostBuilder builder = WebHost.CreateDefaultBuilder(args)
        .UseSerilog()
        .SuppressStatusMessages(true)
        .ConfigureAppConfiguration((hostContext, config) =>
        {
            config.BuildConfiguration();
        })
        .UseStartup<Startup>()
        .UseUrls("http://*:5000");
    return builder.Build();
}

private static IHost CreateHost(string[] args)   // .NET Core 3.1 way
{
    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .UseSerilog()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder
            .SuppressStatusMessages(true)
            .ConfigureAppConfiguration((hostContext, config) =>
                {
                    config.BuildConfiguration();
                })
            .UseStartup<Startup>()
            .UseUrls("http://*:5000");
        });
    return builder.Build();
}

private static WebApplication CreateWebApp(string[] args)  // .NET 6 way
{
    WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
    builder.Host.UseSerilog();

    var startup = new Startup(builder.Configuration, null);
    startup.ConfigureServices(builder.Services);

    WebApplication app = builder.Build();

    IApiVersionDescriptionProvider provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
    startup.Configure(app, app.Environment, provider, app.Lifetime);

    return app;
}

All 3 ways are still compatible with .NET 6 and suppose to work fine. Now, UseSerilog() method tells me the WebHost way is obsolete, so I should migrate. So it makes sense to migrate to the newest model with WebApplication.

The catch is the constructor signature of my Startup class

public Startup(IConfiguration config, ILogger<Startup> logger)

I used to still have the CreateWebHost version active.

Switching to CreateHost throws an exception in the Startup constructor:

"Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[Siemens.BT.Edge.CloudConnector.Startup]' while attempting to activate 'Siemens.BT.Edge.CloudConnector.Startup'."

WHY ? To avoid the exception, I can use the Startup factory:

 .UseStartup(builderContext => new Startup(builderContext.Configuration, null))

Then the question is: how can I use my ILogger there with Dependency Injection? And the same apply when switching to CreateWebApp.

In any other situation, I should use the

var logger = app.Services.GetService<ILogger<Startup>>();

but obviously, I can't call that before calling the Build method and Startup ctor.

So the better question would be: why did my Startup constructor work with WebHost?

1 Answers

so the answer for the WebApplication is to use the BuildServiceProvider:

private static WebApplication CreateWebApp(string[] args)   // .NET 6
{
    WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
    builder.Host.UseSerilog();
    ILogger<Startup> logger = builder.Services.BuildServiceProvider().GetService<ILogger<Startup>>();

    var startup = new Startup(builder.Configuration, logger);
    startup.ConfigureServices(builder.Services);

    WebApplication app = builder.Build();

    IApiVersionDescriptionProvider provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
        
    startup.Configure(app, app.Environment, provider, app.Lifetime);

    return app;
}

It is working but looks a bit ugly...

Related