Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The "correct" way to create a .NET Core console app without background services

I'm building a simple .NET Core console application that will read in basic options from the command line, then execute and terminate without user interaction. I'd like to take advantage of DI, so that lead me to using the .NET Core generic host.

All of the examples I've found that build a console app create a class that either implements IHostedService or extends BackgroundService. That class then gets added to the service container via AddHostedService and starts the application's work via StartAsync or ExecuteAsync. However, it seems that in all of these examples, they are implemementing a background service or some other application that runs in a loop or waits for requests until it gets shut down by the OS or receives some request to terminate. What if I just want an app that starts, does its thing, then exits? For example:

Program.cs:

namespace MyApp
{
    using System;
    using System.Threading.Tasks;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;

    public static class Program
    {
        public static async Task Main(string[] args)
        {
            await CreateHostBuilder(args).RunConsoleAsync();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseConsoleLifetime()
                .ConfigureLogging(builder => builder.SetMinimumLevel(LogLevel.Warning))
                .ConfigureServices((hostContext, services) =>
                {
                    services.Configure<MyServiceOptions>(hostContext.Configuration);
                    services.AddHostedService<MyService>();
                    services.AddSingleton(Console.Out);
                });
    }
}

MyServiceOptions.cs:

namespace MyApp
{
    public class MyServiceOptions
    {
        public int OpCode { get; set; }
        public int Operand { get; set; }
    }
}

MyService.cs:

namespace MyApp
{
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Options;

    public class MyService : IHostedService
    {
        private readonly MyServiceOptions _options;
        private readonly TextWriter _outputWriter;

        public MyService(TextWriter outputWriter, IOptions<MyServiceOptions> options)
        {
            _options = options.Value;
            _outputWriter = outputWriter;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _outputWriter.WriteLine("Starting work");

            DoOperation(_options.OpCode, _options.Operand);

            _outputWriter.WriteLine("Work complete");
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _outputWriter.WriteLine("StopAsync");
        }

        protected void DoOperation(int opCode, int operand)
        {
            _outputWriter.WriteLine("Doing {0} to {1}...", opCode, operand);

            // Do work that might take awhile
        }
    }
}

This code compiles and runs just fine, producing the following output:

Starting work
Doing 1 to 2...
Work complete

However, after that, the application will just sit there waiting until I press Ctrl+C. I know I could force the application to shutdown after the work is complete, but at this point, I feel like I'm not using IHostedService correctly. It seems as though it's designed for recurring background processes, and not simple console applications like this. However, in an actual application where DoOperation might take 20-30 minutes, I would like to take advantage of the StopAsync method to do cleanup before terminating. I also know I could create the service container myself and all that, but the .NET Core generic host already does a lot of stuff I would want to do anyway. It seems to be the right way to write console applications, but without adding a hosted service that kicks off the actual work, how do I get the app to actually do anything?

like image 669
Alan Shearer Avatar asked Apr 08 '21 01:04

Alan Shearer


People also ask

How to install a console application as a Windows service?

When everything is ready, and the application has built successfully, you can use sc.exe to install your console application exe as a Windows Service with the following command: sc.exe create DemoService binpath= "path/to/your/file.exe" This should have been the more latest answer and be considered with latest .netcore release.

How to make Controllers handle background tasks?

In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events.

How do I add a console application to a SharePoint project?

You add the executable of the console application to the package of the SharePoint project by using the Add Additional Assemblies feature of the Advancedtab of the Package Properties window.

How do I create a x64 platform in Configuration Manager?

In the Configuration Managerdialog, open the Active solution platformdrop-down list box and click <New> …. In the New Solution Platformdialog, select x64in the Type or select the new platformdrop-down list box.


Video Answer


2 Answers

Instead of a hosted service, I would recommend the following;

using (var host = CreateHostBuilder(args).Build())
{
    await host.StartAsync();
    var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();

    // do work here / get your work service ...

    lifetime.StopApplication();
    await host.WaitForShutdownAsync();
}
like image 58
Jeremy Lakeman Avatar answered Oct 18 '22 15:10

Jeremy Lakeman


I know I could force the application to shutdown after the work is complete, but at this point, I feel like I'm not using IHostedService correctly.

I agree it does seem odd. I actually always stop the application at the end of all my IHostedService implementations. This is true even for long-running server apps. If a hosted service stops (or faults), then I explicitly want the application to end.

It does feel like this design was unfinished when .NET Core was pushed out. There are parts of the design that are made so that hosted services and their apps can have independent lifetimes, but since they can not be restarted, this just isn't useful in practice. So it ends up feeling like a poor design because the lifetimes can't be independent, but they are independent by default.

All of my hosted services end up tying their lifetime to the application lifetime.

finally
{
  _hostApplicationLifetime.StopApplication();
}
like image 45
Stephen Cleary Avatar answered Oct 18 '22 14:10

Stephen Cleary