Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a Windows service from .Net Core 3 [closed]

Recently I had a need to convert a .Net Core 3.0 console application into a Windows Service.

As I didn't have a requirement to port this process to Linux, I could dispense with the multiple platform solutions that I had seen on Stackoverflow that dealt with any combination of .Net Framework, .Net Standard and .Net Core.

EDIT: There is coming up for Visual Studio 2019 a worker template. As it is pre-release, there will be some potential stability issues. I could not get one of the references to work, so the solution I pose below should suffice until the template is stable. (see https://devblogs.microsoft.com/aspnet/net-core-workers-as-windows-services/)

like image 701
Mark McWhirter Avatar asked Jun 17 '19 20:06

Mark McWhirter


1 Answers

  • First create a .Net Core console application in Visual Studio 2019. You will need to install the .Net Core 3 SDK prior to use. Also insure you can reference the .Net Core 3 preview by specifying settings in Tools->Options->Preview Features, 'Use previews of the .NET Core SDK'
  • Set the language version to at least 7.1 to support async Task for the Main method. (Access the language version from the project settings->Build->Advanced->Language Settings). .Net Core 3 and Visual Studio 2019 support C# 8.0
  • Add the Microsoft.Extensions.Hosting and the System.ServiceProcess.ServiceController packages.

Now go to Program.cs and copy the following:

using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AdvancedHost
{
    internal class Program
    {
        private static async Task Main(string[] args)
        {
            var isService = !(Debugger.IsAttached || args.Contains("--console"));

            var builder = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<LoggingService>();
                });

            if (isService)
            {
                await builder.RunAsServiceAsync();
            }
            else
            {
                await builder.RunConsoleAsync();
            }
        }
    }
}

This code will support interactive debugging and production execution, and runs the example class LoggingService.

Here is a skeleton example of the service itself:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace AdvancedHost
{
    public class LoggingService : IHostedService, IDisposable
    {

        public Task StartAsync(CancellationToken cancellationToken)
        {
            // Startup code

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {           
            // Stop timers, services
            return Task.CompletedTask;
        }

        public void Dispose()
        {
            // dispose of non-managed resources
        }
    }
}

The final two files necessary to complete the project:

ServiceBaseLifetime.cs:

using Microsoft.Extensions.Hosting;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;

namespace AdvancedHost
{

    public class ServiceBaseLifetime : ServiceBase, IHostLifetime
    {
        private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();

        public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
        {
            ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
        }

        private IApplicationLifetime ApplicationLifetime { get; }

        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            cancellationToken.Register(() => _delayStart.TrySetCanceled());
            ApplicationLifetime.ApplicationStopping.Register(Stop);

            new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
            return _delayStart.Task;
        }

        private void Run()
        {
            try
            {
                Run(this); // This blocks until the service is stopped.
                _delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
            }
            catch (Exception ex)
            {
                _delayStart.TrySetException(ex);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Stop();
            return Task.CompletedTask;
        }

        // Called by base.Run when the service is ready to start.
        protected override void OnStart(string[] args)
        {
            _delayStart.TrySetResult(null);
            base.OnStart(args);
        }

        // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
        // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
        protected override void OnStop()
        {
            ApplicationLifetime.StopApplication();
            base.OnStop();
        }
    }
}

ServiceBaseLifetimeHostExtensions.cs:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AdvancedHost
{

    public static class ServiceBaseLifetimeHostExtensions
    {
        public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
        {
            return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
        }

        public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
        {
            return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
        }
    }
}

To maintain the service I use the 'sc' utility: To create: sc create AdvancedHost binPath="C:\temp\AdvancedHost\AdvancedHost.exe" where 'AdvancedHost' is the service name and the value for binPath is the compiled executable.

For status: sc query AdvancedHost

To start: sc start AdvancedHost

To stop: sc stop AdvancedHost

To delete (once stopped): sc delete AdvancedHost

There are many more features contained in sc; just type 'sc' alone on the command line. The results of sc can be seen in the services Windows control panel.

like image 194
Mark McWhirter Avatar answered Oct 23 '22 01:10

Mark McWhirter