.NET5でWindowsServiceを作成する

# はじめに

.NET5が登場してからこの記事の執筆時点でもうすぐ半年が過ぎようとしています。
しかしながら、日本語での.NET5の記事があまりありませんでしたので、せっかくなので作成しようと思いました。
よろしければご参考程度にご確認ください。
また、この記事で紹介しているコードは以下のリポジトリに配置しております。

https://github.com/cocoalix/WindowsServiceTestWithDotNet5

## 実行環境

- VisualStudio2019
- Windows10Pro 20H2 (Build: 19042.867)
- .NET5 SDK 5.0.1

# 参考

https://devblogs.microsoft.com/ifdef-windows/creating-a-windows-service-with-c-net5/

# 実装

## プロジェクトの作成

1. まず、VisualStudioで新しいプロジェクトを作成します。
1. 数多く作成できるプロジェクトはありますが、その中から `WorkerService` を選択します。
![Microsoft Visual Studio 2021_03_28 12_57_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/66a1cf63-ca32-b509-677b-ac74c6c6d224.png)
1. いつも通り、プロジェクト名と作成先の場所を指定します
![Microsoft Visual Studio 2021_03_28 12_58_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/a67b31a3-5160-4438-f6d0-e822da6c75bc.png)
1. `WorkerService` の作成の画面が出てきます。作成しましょう。
    1. ここでは私は.NET5を左上で選択しておきます。
    1. Dockerサポートは使用しない為、チェックがされている場合はチェックを外しておきます。
![Microsoft Visual Studio 2021_03_28 12_58_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/1234451a-12f1-e8e0-a037-7f73c5e38c83.png)
1. 次のようにいつものソリューションエクスプローラーが表示されたなら、プロジェクトの作成に成功です。
![WindowsServiceTest - Microsoft Visual Studio 2021_03_28 12_58_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/a3911687-0911-10df-b40c-e42ce4e9e8ac.png)

## 実際のコードの実装

### 事前準備

コードの実装をいきなり行いたいのはやまやまですが、このままでは.NET5でのWindowsServiceは行えません。
ので、事前準備を実施します。

---

ソリューションエクスプローラーからプロジェクトを右クリックし、「NuGetパッケージの管理」を選択します。
開かれたNuGetの管理画面から「参照」タブを選択し、 `Microsoft.Extensions.Hosting.WindowsService` で検索します。
一番上にMicrosoft公式のパッケージが出てくるかと思います。そちらをインストールしてください。
![WindowsServiceTest - Microsoft Visual Studio 2021_03_28 13_03_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/f7fc029e-d15d-b056-ee0b-97974a395c7a.png)

次に、ソリューションエクスプローラーから `Program.cs` を開き、次のように、最後に `.UseWindowsService()` を追加してあげてください。

```diff_c_sharp

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

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

```

これで、WindowsServiceの一番ミニマムな実装は完了です。

### せっかくなので気象庁のAPIもどきを叩いてお天気を60分に1回取ってきてみてもいいよね

この記事を書く少し前の時期に「気象庁のサイトにAPIが生えている」という話題が上がりました。
何もしないWindowsServiceも味気ないので、せっかくなのでこのAPIにアクセスしてみようと思います。
ちなみに、ここまでのミニマムな実装のままだと、WindowsServiceは1秒に1回実行する待機処理(ポーリング)をしており、
そのポーリング処理にAPIアクセスをまともに乗せると後でめちゃくちゃ怒られそうなので、1時間に1回にしようと考えます。

#### まずは1時間に1回の実行に変更

ポーリング処理とは言いましたが、その実態は `ExecuteAsync()` 内で `while()` があり、その中で `Task.Delay()` して実行間隔を制御しているにすぎません。
ですので、その `Task.Delay()` の間隔を変更してやればよいです。

```diff_c_sharp
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
-               await Task.Delay(1000, stoppingToken);
+               await Task.Delay(360000, stoppingToken);
            }
        }
```

#### 気象庁APIにアクセスする

C#だと、[HttpClientクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient?view=net-5.0)がありますので、そちらを使用します

```diff_c_sharp
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
+               var client = new HttpClient();
+               var response = await client.SendAsync(new(HttpMethod.Get, @"https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json"));
+               var body = await response?.Content.ReadAsStringAsync() ?? "";
+               _logger.LogInformation("response content: " + body);
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(360000, stoppingToken);
            }
        }
```

このように取ってきたデータが表示されていればOKです。
なお、WindowsServiceと仰々しい名前がついていますが、VisualStudioで起動したり、exeを単体実行する分には **ただのコンソールアプリケーションとして扱え** ますので、普通にデバッグ実行してあげるとこのようにデバッグコンソールから確認することができます。

![Microsoft Visual Studio デバッグ コンソール 2021_03_28 13_58_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507143/4b596f48-17ac-aeb6-51a4-a4b266104596.png)


### せっかくなので、WindowsServiceへのサービス起動時処理、サービス終了時処理の生やし方も

このように生やせます。 (VisualStudioを使っているなら、 `override` キーワードを挿入すると勝手にサジェストされるので、そちらを確認したほうが良いかもしれませんね。)

```diff_c_sharp
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

+       public override Task StartAsync(CancellationToken cancellationToken)
+       {
+           return base.StartAsync(cancellationToken);
+       }

+       public override Task StopAsync(CancellationToken cancellationToken)
+       {
+           return base.StopAsync(cancellationToken);
+       }

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

# 備考

## サービスの登録の方法について

アプリを配布する際にサービスへの登録が必要かと思いますが、普通、サービスの登録はユーザには操作させることはできないでしょう。 (一般ユーザーにとって、それは難易度が非常に高いものです。)
ですので、次の記事でWindowsServiceをインストール時に登録ができるインストーラーと、その使用方法を紹介したいと考えます。

# おわりに

このような形で、.NET5でWindowsServiceは作成することができます。
同じ開発者の助けになる記事になることを望みます。

また、何か誤っている個所がありましたらご指摘ください。
ここまで読んでくださり、ありがとうございました。

[次回](https://qiita.com/nao-a/items/b021ca548fab266e7e32)