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).
- Download the packages via NuGet manager: Microsoft.Extensions.Hosting and System.ServiceProcess.ServiceController.
- Modify project.csproj with tag: <OutputType>Exe</OutputType>, <RuntimeIdentifier>win7-x64</RuntimeIdentifier>.
- 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.
- 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:
- 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