.NET 6通过Worker Service创建Windows Service

通过Visual Studio中的Windows Service模板,我么可以创建.NET Framework版本的Windows Service,网络上对此已有详细且丰富的各路教程。但在我们升级到.NET Core 3.1或.NET 6后(这里仅讨论两个LTS版本),情况发生了相当大的变化。我们需要根据新的Worker Service模板,基于BackgroundService这个类来创建最新版本的Windows Service。.

.NET 6通过Worker Service创建Windows Service

某软的官方文档更新的很快,文档中列出的创建Windows Service的必要条件都已经是6.0或更新。那是不是说.NET Core 3.1就无法创建呢?答案当然不是。今天我们就稍作修改,创建3.1版本的Windows Service。

.NET Core 3.1作为LTS(Long-term support)版本,是存在大量基于该版本的生产项目的,有时候条件不允许立刻升级到VS2022和.NET 6.0。所以本篇我们将使用VS2019,首先找到Worker Service模板,如果我们仔细观察,会发现除了Windows平台,更多了Linux和macOS的支持。

.NET 6通过Worker Service创建Windows Service

点击两次Next按钮后,选择.NET Core 3.1版本创建工程。默认仅包含Program和Worker两个cs文件。接下来我们通过NuGet安装Microsoft.Extensions.Hosting.WindowsService包。最新的版本是6.0,且同时依赖6.0版的Microsoft.Extension.Hosting。这里我们有两个选择,第一是在NuGet界面的下拉框中,选择安装3.1.17版本的WindowsService包,保持和默认Hosting包的版本相同。第二是将两者都升级到最新的6.0版本。所幸.NET的向后兼容做的不错,在这里我选择升级。在完成后的简单测试中,无论是部署在.NET 6 Runtime或.NET Core 3.1 Runtime的机器上,均可正常工作。当前状态应该如下图所示。

.NET 6通过Worker Service创建Windows Service

此时我们可以开始创建业务类了,就是你想用Service干点啥,在本篇的示例中,我只想躺平……疫情期间,不要用眼过度和上头648充值抽卡,打游戏实在是极其健康的选择了。

    public class LieFlatService
    {
        public static int Count { get; set; }
        public string LieFlat()
        {
            return $"Lie flat and play PS4 & Switch... {++Count}";
        }
    }

接着我们通过继承自BackgrounService的Worker类来调用LieFlatService实现躺平。真的是啥也不干,就是每隔一秒宣告一下躺平。

    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private readonly LieFlatService _lieFlatSrvice;

        public Worker(LieFlatService lieFlatService, ILogger<Worker> logger)
        {
            _lieFlatSrvice = lieFlatService;
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string message = _lieFlatSrvice.LieFlat();
                _logger.LogWarning(message);
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }

然后我们还要修改一下Program类,通过UseWindowsService扩展方法将我们这个Console程序配置成Windows Service类型。如果我们之前选择WindowsServices 3.1的包,除了不能方便的设置SeviceName,其他倒是没啥区别。在CreateHostBuilder方法中,记得注册Worker和LieFlatService类型供.NET内置的依赖注入(Dependency Injection)框架使用。

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args)
                .UseWindowsService(o =>
            {
                o.ServiceName = "Lie Flat Service";
            })
                .Build()
                .Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                    services.AddSingleton<LieFlatService>();
                });
    }

代码部分至此就全部完成了。相对于.NET Framework的Windows Service,调试起来要容易的多,就直接当成Console程序Debug就行了。部署的操作也很简单,首先在project文件上右键选择发布到文件夹。

.NET 6通过Worker Service创建Windows Service

通常默认的发布配置就可以了,点击发布按钮之后,会在项目的bin\Release\netcoreapp3.1\win-x64\publish\目录下生成如下文件。如果修改发布文件选择Self Contained,则可以脱离对部署环境是否安装.NET的依赖,缺点就是会生成一大堆文件。

.NET 6通过Worker Service创建Windows Service

我们可以将publish文件夹拷贝到想要的位置,然后用admin权限打开PowerShell,通过命令行来创建Windows Service。

sc.exe create "Lie Flat Service" binpath="C:\Users\dell\Desktop\publish\NetCoreWorkerService.exe"

此时打开Services界面,将创建的Lie Flat Service启动。

.NET 6通过Worker Service创建Windows Service

再打开Event Viewer检查Windows Logs,就可以发现Lie Flat Service发出的躺平宣言了。

.NET 6通过Worker Service创建Windows Service

删除Windows Service需要通过stop和delete两个命令。

.NET 6通过Worker Service创建Windows Service

本篇我们介绍了如何在.NET Core 3.1(.NET 6.0也几乎相同)的环境下创建Windows Service。同时.NET 4.5.2, .NET 4.6和.NET 4.6.1将于2022年4月26日终止支持。升级吧同学们。

示例代码:

https://github.com/manupstairs/NetCoreWorkerService

https://gitee.com/manupstairs/WorkerServiceTest1