55

I am trying to figure out how to use hostbuilder pattern to run a console app (not a windows service). Intent is to keep the flow as similar to a WebApi to keep development practices similar. I have seen samples for using HostedService or BackGroundService, where they want to run it as a windows service. But If I am looking to run a simple console app, where do I specify my entrypoint class and method? From hostbuilder.Build(), I can see Run() and RunAsync() methods. But I am unable to figure out what will it execute?

I have seen other examples of where you can create servicecollection and then use serviceprovider.GetService().SomeMethod() to start the process. But that kind of deviates from what we want to do. So please suggest how to specify startup process. We are using 3.1 .Net Core.

class Program
{
    static async void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        await host.RunAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostBuilderContext, serviceCollection) => new Startup(hostBuilderContext.Configuration).ConfigureServices(serviceCollection))
        .UseSerilog()
        ; 

}
abdusco
  • 9,700
  • 2
  • 27
  • 44
askids
  • 1,406
  • 1
  • 15
  • 32
  • 3
    Use `dotnet new worker` to create a worker app. It uses the hostbuilder pattern and works right out of the box. – abdusco Jul 15 '21 at 11:17
  • Thanks for the suggestion. I tried it. It uses HostedService. So is using HostedService the only way to use it, even if I am NOT running console app as windows service? – askids Jul 15 '21 at 11:43
  • No, you don't have to use a hosted service. You can use the worker project template as a reference to how to set up the DI container. Once you have the host, you can use it to resolve services and call them in your `Main` method, no need to specify an entrypoint. Alternatively use the hosted service directly, it doesn't need to be a windows/systemd service to function. – abdusco Jul 15 '21 at 13:19
  • Does this answer your question? [Startup.cs in a self-hosted .NET Core Console Application](https://stackoverflow.com/questions/41407221/startup-cs-in-a-self-hosted-net-core-console-application) – Michael Freidgeim May 09 '22 at 21:11

2 Answers2

84

EDIT: An update for .NET 6 is below ↓
Not much has changed with .NET 7.


I'd start off with the default worker template. It comes with necessary packages pre-installed. If you already have a project, install Microsoft.Extensions.Hosting package.

dotnet new worker -n MyCli

Then open up the Program.cs and build the host. Remove the Worker hosted service if you don't want to go with the hosted service route.

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // remove the hosted service
                // services.AddHostedService<Worker>();

                // register your services here.
            });
}

Build your logic:

internal class MyService
{
    // you can also inject other services
    private ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        _logger.LogInformation("Doing something");
    }
}

Then register the class inside .ConfigureServices method

Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.AddTransient<MyService>();
    });

Now you can resolve and call it inside the Main method:

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    var myService = host.Services.GetRequiredService<MyService>();
    myService.DoSomething();
}

.NET 6 update

With .NET 6, boilerplate is reduced significantly. We can rewrite our Program.cs as:

using Microsoft.Extensions.Hosting; // Requires NuGet package

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services => { services.AddTransient<MyService>(); })
    .Build();

var my = host.Services.GetRequiredService<MyService>();
await my.ExecuteAsync();

class MyService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public async Task ExecuteAsync(CancellationToken stoppingToken = default)
    {
        _logger.LogInformation("Doing something");
    }
}
Juliën
  • 9,047
  • 7
  • 49
  • 80
abdusco
  • 9,700
  • 2
  • 27
  • 44
  • 3
    Note: using "Microsoft.Extensions.Hosting". Might need a NuGet package for that. – Heinrich Ulbricht Jan 30 '22 at 20:05
  • 1
    @HeinrichUlbricht True. But `worker` template comes with that package pre-installed. – abdusco Jan 31 '22 at 08:25
  • 2
    Someone maybe will need to add configuration as well- see [How to read configuration settings before initializing a Host in ASP .NET Core?](//stackoverflow.com/a/58594026) – Michael Freidgeim May 09 '22 at 08:27
  • Wondering what kind of scope is managed for DI by `CreateDefaultBuilder` or `IHostedService`? I had a few `AddScoped` services registered and I was afraid they would fail to resolve but, surprisingly, they worked just fine without me having to explicitly create a scope. So, there's some magic going on in `CreateDefaultBuilder` or DI for `IHostedService` but I could not find any info about its DI scope. – JustAMartin Jul 21 '22 at 10:53
  • You can always resolve scoped services, they just become a singleton outside a scope. https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#scoped-service-as-singleton . Or if you mismatch lifetimes (inject a scoped service into a singleton), you get a captive dependency. – abdusco Jul 21 '22 at 12:48
  • How would you Unit Test the MyService class ? Would you create an interface for it instead and then register that ? Can you then Mock that interface ? – John Long Sep 30 '22 at 13:24
  • @JohnLong If `MyService` is the system under test you wouldn't mock _it_. You should mock or fake its _dependencies_. – Marc L. Aug 03 '23 at 17:11
7

Basically:

  • Instantiate your host builder and configure your services and whatnot.

  • Make a class with a method for your programme and register that class as a service.

  • Build the host, create a scope, get an instance of your class, call your method.

My programme is the mehod MainAsync in my class ProgramAsync.

    class Program
    {
        static void Main(string[] args)
        {
            using (IHost host = Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration(cfg =>
                {
                    cfg.AddJsonFile("appsettings.json");
                })
                .ConfigureServices((context, services) =>
                {
                    services.AddDbContext<EquitiesDbContext>(options => { options.UseSqlServer(context.Configuration.GetConnectionString("Equities")); });
                    services.AddScoped<ProgramAsync>();
                })
                .ConfigureLogging((context, cfg) =>
                {
                    cfg.ClearProviders();
                    cfg.AddConfiguration(context.Configuration.GetSection("Logging"));
                    cfg.AddConsole();
                })
                .Build()
                )
            {
                using( IServiceScope scope = host.Services.CreateScope() )
                {
                    ProgramAsync p = scope.ServiceProvider.GetRequiredService<ProgramAsync>();
                    p.MainAsync().Wait();
                }
            }
            Console.WriteLine("Done.");
        }
    }

Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54
  • 2
    declaring `Program.Main(..)` as `async Task Main(..)` is legal, and allows you to `await p.MainAsync();` – Marc L. Aug 03 '23 at 17:10