Create windows service using .Net Core (Console application)

LAI TOCA
4 min readApr 2, 2019

Hosting a service for processing routine task(s) under the background was no a fashion way nowadays. Traditional windows service project was no longer support for .Net core. But you can do a little tricky on it(https://stackify.com/creating-net-core-windows-services/). Or you could adapt below approaches to see if meet your requirement.

Build service from Microsoft native packages

Key point was to reference packages: Microsoft.Extensions.Hosting and System.ServiceProcess.ServiceController. That’s begun with below step:

  • Create .Net core console application(Whenever Target Framework: 2.0/2.1/2.2 would be fine).
Initial .Net Core Console
  • Download the packages via NuGet manager: Microsoft.Extensions.Hosting and System.ServiceProcess.ServiceController.
Install Additional Packages
  • Modify project.csproj with tag: <OutputType>Exe</OutputType>, <RuntimeIdentifier>win7-x64</RuntimeIdentifier>.
Edit project.csproj
  • Create class: ServiceBaseLifeTime that inherit ServiceBase(windows base service that control’s service’s life-cycle), IHostLifetime, code sample as below:
public class ServiceBaseLifeTime : ServiceBase, IHostLifetime
{
private readonly TaskCompletionSource<object> _delayStart;
private IApplicationLifetime ApplicationLifetime { get; }
public ServiceBaseLifeTime(IApplicationLifetime
applicationLifetime)
{
_delayStart = new TaskCompletionSource<object>();
ApplicationLifetime = applicationLifetime ?? throw new
ArgumentNullException(nameof(applicationLifetime));
}

public Task StopAsync(CancellationToken cancellationToken)
{
Stop();
return Task.CompletedTask;
}
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
cancellationToken.Register(() => _delayStart.TrySetCanceled());
ApplicationLifetime.ApplicationStopping.Register(Stop);
// Otherwise this would block and prevent IHost.StartAsync from finishing.
new Thread(Run).Start();
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);
}
}
// 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();
}
protected override void OnPause()
{
// Custom action on pause
base.OnPause();
}
protected override void OnContinue()
{
// Custom action on continue
base.OnContinue();
}
}
  • Create class: ServiceBaseLiveTimeHostExtension handle service running trigger point. Sample code:
public static class ServiceBaseLiveTimeHostExtension
{
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);
}}
  • Create your service class, for example XXXService:
public class XXXService : IHostedService, IDisposable
{
public Task StartAsync(CancellationToken cancellationToken)
{
var text = $"{DateTime.Now.ToString("yyyy-MM-dd
HH:mm:ss")}, Testing write." + Environment.NewLine;
File.WriteAllText(@"D:\Temp\Service.Write.txt", text);
Console.WriteLine($"[{nameof(XXXService)}] has been
started.....");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
File.Delete(@"D:\Temp\Service.Write.txt");
Console.WriteLine($"[{nameof(XXXService)}] has been
stopped.....");
Thread.Sleep(1000);
return Task.CompletedTask;
}
}
  • Cause we need make the entry main support method **async**, open the project properties, select *Language version:* over C#7.1.
Build->Advance
Advance Build Settings
  • Added below code snippet into entry main function:
// In program.cs
static async Task Main(string[] args)
{
// Run with console or service
var asService = !(Debugger.IsAttached || args.Contains("--
console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<XXXService>();
});
builder.UseEnvironment(asService ? EnvironmentName.Production :
EnvironmentName.Development);

if(asService)
{
await builder.RunAsServiceAsync();
}
else
{
await builder.RunConsoleAsync();
}
}
  • Publish application and get the exe file.

dotnet publish — configuration Release

  • Create and register service using sc.exe utility.

sc create XXXService binPath = ‘Path point to your application’s exe’-

  • Start the service

sc start XXXService

Build service from Third-party packages: Topself

Steps as below:

  • Create console application and install Topself via NuGet manager:
Topshelf Package
  • Inside the entry main method, added below code snippet:
// In program.cs
static void Main(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureAppConfiguration((hostContext, config) =>
{
// Configure application configuration here
// ....
})
.ConfigureServices((hostContext, services) =>
{
// Configure application service here
services.AddTransient<XXXService>();
// ...
});
var host = hostBuilder.Build();
// Begin topshelf configuration
HostFactory.Run(x =>
{
x.Service<App>(s =>
{
// We have injected our service already
s.ConstructUsing(() =>
host.Services.GetRequiredService<XXXService>());
// Action delegate from service start button
s.WhenStarted(service => service.OnStart());
// Action delegate from service stop button
s.WhenStopped(service => service.OnStop());
});
// Service log on as
x.RunAsLocalSystem()
.StartAutomatically()
.EnableServiceRecovery(rc => rc.RestartService(5));

x.SetServiceName("Your service name.");
x.SetDisplayName("Your service dsiplay name.");
x.SetDescription("Your service description.");
});
}
  • Publish application and get exe file, for instance: XXXService.exe.

dotnet publish — configuration Release

  • Install service

XXXService.exe install [Topshelf command]

  • Start service using sc.exe utility.

sc start XXXService (service name) or XXXService.exe start [Topshelf command]

Conclusion

You could choose whatever approaches you like. For me, I would enjoy the simple and powerful service wrapper tool of TopShelf. More functions or introduction please refer to its official website, find it yourself😊.

If you got interested how to build up Windows Service on .Net Core 3, see this link.

Reference

— https://www.stevejgordon.co.uk/running-net-core-generic-host-applications-as-a-windows-service

— https://topshelf.readthedocs.io/en/latest/

http://docs.topshelf-project.com/en/latest/overview/commandline.html

--

--

LAI TOCA

Coding for fun. (Either you are running for food or running for being food.)