1

I'm sure not the first one to run into this, but I couldn't find any good example patterns. When working with .Net Core 3 and newer and creating a web app we can use a Startup class

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

One nice feature about the Startup class is that it first calls ConfigureServices in which we can add things into the dependency injection container and then calls the Configure method which supports dependency injection (we can add arguments to the method to get instances of those classes registered with the container in ConfigureServices).

This is nice as it allows us to make setup and initialization calls that are only available after instantiation, and can save considerable code if the classes being instantiated have a chain of dependencies. The Configure method allows the infrastructure to chain up those dependences where we would have to do it ourselves in the ConfigureServices method.

When building console apps we don't have the option of a Startup class. We directly call ConfigureServices.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
          ...
        })

Where is a good clean place to do work that would normally consider placing in the Startup.Configure method? I'm especially interested in where to place things you want to occur before the Hosted Services are started up (for example I have a class responding to database changes that needs subscriptions wired up and I want this to occur before any Hosted Services startup which may alter the database). So in other words I'm looking for an opportunity to leverage dependency injection before the Hosted Services are started.

denver
  • 2,863
  • 2
  • 31
  • 45
  • 1
    Do whatever work you need between _building_ the host and _running_ the host. That applies to web-apps, too, where using `Configure` for initialisation work isn't really the best approach. See the second part of my answer [here](https://stackoverflow.com/questions/58574899/asp-net-core-3-cannot-resolve-scoped-service-microsoft-aspnetcore-identity-use/58575145#58575145) to see what I mean, or see my answer [here](https://stackoverflow.com/questions/55707736/where-should-i-perform-custom-application-initialization-steps-in-asp-net-core/55707949#55707949) for an extension-method approach. – Kirk Larkin Feb 10 '21 at 15:25
  • 1
    Have you looked at this example? https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/host/generic-host/samples/2.x/GenericHostSample/Program.cs – GlennSills Feb 10 '21 at 15:26
  • @KirkLarkin The extension method approach is pretty clean looking. I think that is the pattern I'll adopt. Thanks. – denver Feb 10 '21 at 15:39
  • Possible duplicate of https://stackoverflow.com/questions/41407221/startup-cs-in-a-self-hosted-net-core-console-application – Michael Freidgeim May 08 '22 at 07:13

1 Answers1

1

Building on the referenced answers in comments I created extension methods to IHost which provides DI to act as a substitute for the Startup.Configuration method which is not available for use in console applications.

public static class HostExtensions
{
    public static IHost PostBuildConfiguration(this IHost host, Action action)
    {
        action();
        return host;
    }

    public static IHost PostBuildConfiguration<T1>(this IHost host, Action<T1> action)
    {
        using (var scope = host.Services.CreateScope())
        {
            action(scope.ServiceProvider.GetRequiredService<T1>());
        }
        return host;
    }

    public static IHost PostBuildConfiguration<T1, T2>(this IHost host, Action<T1, T2> action)
    {
        using (var scope = host.Services.CreateScope())
        {
            action(scope.ServiceProvider.GetRequiredService<T1>(),
                scope.ServiceProvider.GetRequiredService<T2>());
        }
        return host;
    }

    public static IHost PostBuildConfiguration<T1, T2, T3>(this IHost host, Action<T1, T2, T3> action)
    {
        using (var scope = host.Services.CreateScope())
        {
            action(scope.ServiceProvider.GetRequiredService<T1>(),
                scope.ServiceProvider.GetRequiredService<T2>(),
                scope.ServiceProvider.GetRequiredService<T3>());
        }
        return host;
    }

    public static IHost PostBuildConfiguration<T1, T2, T3, T4>(this IHost host, Action<T1, T2, T3, T4> action)
    {
        using (var scope = host.Services.CreateScope())
        {
            action(scope.ServiceProvider.GetRequiredService<T1>(),
                scope.ServiceProvider.GetRequiredService<T2>(),
                scope.ServiceProvider.GetRequiredService<T3>(),
                scope.ServiceProvider.GetRequiredService<T4>());
        }
        return host;
    }
}

This can then be used after the call to Build in your main as such:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
          ...
        })
        .Build()
        .PostBuildConfiguration((Type1 a, Type2 b) => 
        {
           a.DoSomething();
           b.DoSomething();
        })
        .Run();
denver
  • 2,863
  • 2
  • 31
  • 45