Skip to main content

使用Host编程模型编写Windows服务应用

分类:  .Net技术 标签:  #基础 #.Net #.Net Host 发布于: 2023-06-15 15:41:58

.Net 5之前用户如果想编写运行在Windows上的服务应用,只能通过.Net Framework来编写,现在可以直接通过.Net来编写Windows的服务应用了。我们本章尝试使用.Net 6worker模板来编写Windows的服务应用。

完成本教程您将了解:

  • 如何将基于.Networker模板应用发布为一个exe可执行文件。
  • 创建一个Windows服务
  • 启动和停止Windows服务
  • 查看该服务的事件日志
  • 管理Windows服务

创建项目

你可以visual studio来创建基于Worker模板的项目,或者是使用.Net cli工具来创建。

dotnet new worker -o WindowsService
cd WindowsService

要编写Woindows的服务应用,需要额外添加必须要的包:

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

这个包是为了编写Windows服务必须的包,另外我们还需要添加另外一个包,这个包仅仅是为了演示功能:

dotnet add package Microsoft.Extensions.Http

编写代码

然后使用vs code打开文件夹, 新建一个文件:JokeService.cs, 并使用如下的代码替换:

namespace WindowsService;

public class JokeService
{
    public string GetJoke()
    {
        Joke joke = _jokes.ElementAt(
            Random.Shared.Next(_jokes.Count));

        return $"{joke.Setup}{Environment.NewLine}{joke.Punchline}";
    }

    // Programming jokes borrowed from:
    // https://github.com/eklavyadev/karljoke/blob/main/source/jokes.json
    readonly HashSet<Joke> _jokes = new()
    {
        new Joke("What's the best thing about a Boolean?", "Even if you're wrong, you're only off by a bit."),
        new Joke("What's the object-oriented way to become wealthy?", "Inheritance"),
        new Joke("Why did the programmer quit their job?", "Because they didn't get arrays."),
        new Joke("Why do programmers always mix up Halloween and Christmas?", "Because Oct 31 == Dec 25"),
        new Joke("How many programmers does it take to change a lightbulb?", "None that's a hardware problem"),
        new Joke("If you put a million monkeys at a million keyboards, one of them will eventually write a Java program", "the rest of them will write Perl"),
        new Joke("['hip', 'hip']", "(hip hip array)"),
        new Joke("To understand what recursion is...", "You must first understand what recursion is"),
        new Joke("There are 10 types of people in this world...", "Those who understand binary and those who don't"),
        new Joke("Which song would an exception sing?", "Can't catch me - Avicii"),
        new Joke("Why do Java programmers wear glasses?", "Because they don't C#"),
        new Joke("How do you check if a webpage is HTML5?", "Try it out on Internet Explorer"),
        new Joke("A user interface is like a joke.", "If you have to explain it then it is not that good."),
        new Joke("I was gonna tell you a joke about UDP...", "...but you might not get it."),
        new Joke("The punchline often arrives before the set-up.", "Do you know the problem with UDP jokes?"),
        new Joke("Why do C# and Java developers keep breaking their keyboards?", "Because they use a strongly typed language."),
        new Joke("Knock-knock.", "A race condition. Who is there?"),
        new Joke("What's the best part about TCP jokes?", "I get to keep telling them until you get them."),
        new Joke("A programmer puts two glasses on their bedside table before going to sleep.", "A full one, in case they gets thirsty, and an empty one, in case they don’t."),
        new Joke("There are 10 kinds of people in this world.", "Those who understand binary, those who don't, and those who weren't expecting a base 3 joke."),
        new Joke("What did the router say to the doctor?", "It hurts when IP."),
        new Joke("An IPv6 packet is walking out of the house.", "He goes nowhere."),
        new Joke("3 SQL statements walk into a NoSQL bar. Soon, they walk out", "They couldn't find a table.")
    };
}

public record Joke(string Setup, string Punchline);

Worker.cs to WindowsBackgroundService

将文件Worker.cs重命名为WindowsBackgroundService.cs, 并使用如下的代码替换:

namespace App.WindowsService;

public sealed class WindowsBackgroundService : BackgroundService
{
    private readonly JokeService _jokeService;
    private readonly ILogger<WindowsBackgroundService> _logger;

    public WindowsBackgroundService(
        JokeService jokeService,
        ILogger<WindowsBackgroundService> logger) =>
        (_jokeService, _logger) = (jokeService, logger);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            string joke = _jokeService.GetJoke();
            _logger.LogWarning(joke);

            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}

更改Program.cs

打开文件Program.cs,并使用如下的内容替换:

using WindowsService;

IHost host = Host.CreateDefaultBuilder(args)
    .UseWindowsService(
        options => 
        {
            options.ServiceName = ".Net Joke Service";
        }
    )
    .ConfigureServices(services =>
    {
        services.AddSingleton<JokeService>();
        services.AddHostedService<WindowsBackgroundService>();
    })
    .Build();

await host.RunAsync();

发布应用

如果要创建一个Windows服务,推荐将应用发布成为一个可执行的.exe文件。

为了达到这个目的,更改一下.csproj文件,使用如下的内容替换:

<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UserSecretsId>dotnet-WindowsService-EE8C2C7A-562C-46C6-AEFD-CD838916E7E6</UserSecretsId>
    <RootNamespace>App.WindowsService</RootNamespace>
    <OutputType>exe</OutputType>
    <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
  </ItemGroup>
</Project>

可以直接使用如下的命令来发布应用:

dotnet publish -c Release

可以观察到如下的输出:

D:\MyProjects\DotNetLearning\WindowsService>dotnet publish -c Release
用于 .NET 的 Microsoft (R) 生成引擎版本 17.1.0+ae57d105c
版权所有(C) Microsoft Corporation。保留所有权利。

  正在确定要还原的项目…
  已还原 D:\MyProjects\DotNetLearning\WindowsService\WindowsService.csproj (用时 267 ms)。
  WindowsService -> D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\WindowsService.dll
  WindowsService -> D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish\

D:\MyProjects\DotNetLearning\WindowsService>dir .\bin\Release\net6.0\win-x64\publish\
 驱动器 D 中的卷是 Data
 卷的序列号是 EE66-0D6B

 D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish 的目录

2022/03/14  22:36    <DIR>          .
2022/03/14  22:36    <DIR>          ..
2022/03/14  22:22               137 appsettings.Development.json
2022/03/14  22:22               137 appsettings.json
2022/03/14  22:36        65,509,074 WindowsService.exe
2022/03/14  22:36            13,348 WindowsService.pdb
               4 个文件     65,522,696 字节
               2 个目录 804,717,953,024 可用字节

D:\MyProjects\DotNetLearning\WindowsService>cd bin\Release\net6.0\win-x64\publish\

D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish>chdir
D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish

D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish>

由此可以得到exe文件的绝对路径为:D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish\WindowsService.exe

创建Windows服务

我们之前已经得到了exe的绝对路径了,那么我们现在来创建Windows服务:

需要注意的是要另外打开一个cmd,并以管理员身份来运行。

Microsoft Windows [版本 10.0.22000.556]
(c) Microsoft Corporation。保留所有权利。

C:\Windows\system32>sc.exe create ".NET Joke Service" binpath="D:\MyProjects\DotNetLearning\WindowsService\bin\Release\net6.0\win-x64\publish\WindowsService.exe"
[SC] CreateService 成功

C:\Windows\system32>

这个时候如果启动Windows上的服务界面,可以观察到如下的服务:


同时你也可以管理它:


观察日志

打开Windows的事件查看器,


移除windows服务

在管理员窗口下运行命令:

C:\Windows\system32>sc.exe delete ".NET Joke Service"
[SC] DeleteService 成功

C:\Windows\system32>

至此演示完成。