﻿# 使用 .NET Generic Host 建立 Console 主控台應用程式 (.NET Core 3.1+)

我們會將 [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686) 用在 ASP.NET Core 或 Worker Service (背景服務) 這類需要運行在**長時間執行**的應用程式中。不過，若只是僅執行一次的這種單純的 Console 主控台應用程式適合用 .NET Generic Host 來建立應用程式架構嗎？是的，當然適合，而且還有很多附加的好處。

> 建議閱讀新版文章: [從 .NET 7 開始就不鼓勵使用 .NET Generic Host 建立應用程式](/post/2024/05/06/net-7-net-generic-host)

### 何謂 `Host` 物件？

其實 `Host` 是一個來自 [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) 套件中的一個**靜態類別**，主要用來提供應用程式一個標準的 Hosting (裝載) 與 Startup (啟動) 基礎建設(infrastructures)，原始碼位於 GitHub 的 [dotnet/runtime](https://github.com/dotnet/runtime/tree/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting) Repo 下。

這裡我們最常看到的 `Host` 使用方式，是呼叫該類別下的 [Host.CreateDefaultBuilder(args)](https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.hosting.host.createdefaultbuilder?view=dotnet-plat-ext-5.0&WT.mc_id=DT-MVP-4015686) 靜態方法，例如：

-   .NET 5.0 的 **ASP.NET Core Web API** 專案範本的 `Program.cs` 主程式內容
    
    ```csharp
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
    
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
    ```
    
-   .NET 5.0 的 **Worker** 專案範本的 `Program.cs` 主程式內容
    
    ```csharp
    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>();
                });
    }
    ```
    

上述兩段程式碼片段，都有執行到 `Host.CreateDefaultBuilder(args)` 方法，我們可以從 [原始碼](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs#L56-L134) 清楚的看出，這個方法幫我們初始化主控台應用程式所需的各種設定，其中包括：

-   **基本組態設定** (Configuration)
    -   Host Configuration
    -   App Configuration
-   **應用程式紀錄** (Logging)
    -   自動讀入組態設定的 `Logging` 區段
    -   加入 Console 與 Debug 紀錄提供者 (Logger Provider)
    -   如果是 Windows 平台則還包含 EventLog 紀錄提供者
    -   活動追蹤選項(ActivityTrackingOptions)包含 `SpanId`, `TraceId` 與 `ParentId` 等資訊
-   **基本 Dependency injection (DI) 設定**
    -   [ValidateScopes](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceprovideroptions.validatescopes?view=dotnet-plat-ext-5.0&WT.mc_id=DT-MVP-4015686)
    -   [ValidateOnBuild](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceprovideroptions.validateonbuild?view=dotnet-plat-ext-5.0&WT.mc_id=DT-MVP-4015686)

基本上，執行 `Host.CreateDefaultBuilder(args)` 方法會取得一個 [IHostBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostbuilder?view=dotnet-plat-ext-5.0&WT.mc_id=DT-MVP-4015686) 物件，如果再呼叫 [Build()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostbuilder.build#Microsoft_Extensions_Hosting_IHostBuilder_Build) 就會得到一個 [IHost](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihost) 的實體，最後再執行 [Run()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostingabstractionshostextensions.run) 或 [RunAsync()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostingabstractionshostextensions.runasync) 就可以開始執行 Host 應用程式，正式進入 Generic Host 所控管的執行生命週期。

> 注意：[.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686) 從 .NET Core 3.1 開始有了一次小改版，跟之前 .NET Core 2.1 的版本稍微有點不同。而 .NET 5 的 .NET Generic Host 則跟 .NET Core 3.1 完全相同。

### Worker 背景服務

我以 .NET 5.0 的 **Worker** 專案範本為例，如下是 `Worker` 類別的範例程式：

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

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

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

這裡的 `ConfigureServices` 主要用來設定 DI 服務註冊，而裡面的 `services.AddHostedService<Worker>();` 就是把 `Worker` 類別註冊為 Hosted Service，該類別繼承了 [BackgroundService](https://github.com/dotnet/runtime/blob/e35526f2e7e004681708849d1e35cff8f71c8a29/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs#L13-L85) 抽象類別，而該抽象類別又實作了 [IHostedService](https://github.com/dotnet/runtime/blob/e35526f2e7e004681708849d1e35cff8f71c8a29/src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.cs#L112-L116) 介面。

雖然我們只需要在 `Worker` 實作 `ExecuteAsync(CancellationToken stoppingToken)` 方法，但事實上只是透過 `BackgroundService` 的 [StartAsync(CancellationToken cancellationToken)](https://github.com/dotnet/runtime/blob/e35526f2e7e004681708849d1e35cff8f71c8a29/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs#L38-L54) 幫你呼叫 `ExecuteAsync` 而已。

> 直接閱讀 [BackgroundService](https://github.com/dotnet/runtime/blob/e35526f2e7e004681708849d1e35cff8f71c8a29/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs#L13-L85) 的原始碼，可以讓你對 [IHostedService](https://github.com/dotnet/runtime/blob/e35526f2e7e004681708849d1e35cff8f71c8a29/src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.cs#L112-L116) 的用法更加清晰！

### 取得 .NET Generic Host 預設提供的 DI 服務

所有的 .NET Generic Host 應用程式，預設都可以透過 DI 注入以下服務：

-   [IHostApplicationLifetime](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686#ihostapplicationlifetime)
    -   用來控制 .NET Generic Host 應用程式的生命週期，你可以用 [ApplicationStarted](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationlifetime.applicationstarted?view=dotnet-plat-ext-5.0#Microsoft_Extensions_Hosting_IHostApplicationLifetime_ApplicationStarted), [ApplicationStopped](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationlifetime.applicationstopped?view=dotnet-plat-ext-5.0#Microsoft_Extensions_Hosting_IHostApplicationLifetime_ApplicationStopped) 與 [ApplicationStopping](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationlifetime.applicationstopping?view=dotnet-plat-ext-5.0#Microsoft_Extensions_Hosting_IHostApplicationLifetime_ApplicationStopping) 來註冊一些不同時期的 Callback 方法，也可以呼叫 [StopApplication()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationlifetime.stopapplication?view=dotnet-plat-ext-5.0#Microsoft_Extensions_Hosting_IHostApplicationLifetime_StopApplication) 關閉 Host 應用程式。
-   [IHostLifetime](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686#ihostlifetime)
    -   用來控制 .NET Generic Host 應用程式何時啟動、何時關閉。
    -   .NET Core 3.1 與 .NET 5 內建的 `IHostLifetime` 預設實作為 [Microsoft.Extensions.Hosting.Internal.ConsoleLifetime](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.cs) (原始碼)，你可以從原始碼看出實際的邏輯與行為。
-   [IHostEnvironment](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686#ihostenvironment)
    -   你可以透過此服務取得以下資訊：
        -   **應用程式名稱** ([IHostEnvironment.ApplicationName](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostenvironment.applicationname#Microsoft_Extensions_Hosting_IHostEnvironment_ApplicationName))
        -   **應用程式根目錄的檔案提供者** ([IHostEnvironment.ContentRootFileProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostenvironment.contentrootfileprovider#Microsoft_Extensions_Hosting_IHostEnvironment_ContentRootFileProvider))
        -   **應用程式根目錄** ([IHostEnvironment.ContentRootPath](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostenvironment.contentrootpath#Microsoft_Extensions_Hosting_IHostEnvironment_ContentRootPath))
        -   **當前的環境名稱** ([IHostEnvironment.EnvironmentName](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostenvironment.environmentname#Microsoft_Extensions_Hosting_IHostEnvironment_EnvironmentName))

### 關於 .NET Generic Host 優雅的關閉應用程式

使用 .NET Generic Host 有個非常棒的地方，就是他可以幫你處理**優雅的關閉**(graceful shutdown)應用程式，這個特性特別適用於**容器化應用程式**執行，當你的應用程式部署在 Docker 或 Kubernetes 環境下，當容器需要被關閉或重啟時，會向應用程式送出一個 [SIGINT](https://en.wikipedia.org/wiki/Signal_\(IPC\)#SIGINT) 或 [SIGTERM](https://en.wikipedia.org/wiki/Signal_\(IPC\)#SIGTERM) 訊號，這就如同你對應用程式按下 `Ctrl+C` 中斷程式執行一樣。

> 這個部分你只要看 [Microsoft.Extensions.Hosting.Internal.ConsoleLifetime](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.cs) 原始碼，就可以知道確切的實作方式！

### 建立含有 DI 能力的 Console 主控台應用程式

終於要進入重點了！當我們需要撰寫一份**不用長時間執行**的 Console 應用程式，但又希望能夠使用 .NET Core DI、Logging、Configuration 等常用功能，這時我們就可以利用 .NET Generic Host 既有提供的架構進行撰寫，不但程式碼更容易上手，整體程式碼結構也更漂亮。以下就是實作的完整步驟：

1.  建立基礎 Console 專案
    
    ```bash
    dotnet new console -n GenericHostConsole
    cd GenericHostConsole
    ```
    
2.  建立 Git 版控
    
    ```bash
    dotnet tool update -g dotnet-ignore
    dotnet ignore get -n VisualStudio
    git init && git add . && git commit -m "Initial commit"
    ```
    
3.  安裝 [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) 套件
    
    ```bash
    dotnet add package Microsoft.Extensions.Hosting
    ```
    
4.  修改 `Program` 類別
    
    ```csharp
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace GenericHostConsole
    {
        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<App>();
                    });
        }
    }
    ```
    
5.  新增 `App` 類別並實作 `IHostedService` 介面
    
    ```csharp
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Hosting;
    
    namespace GenericHostConsole
    {
        public class App : IHostedService
        {
            public Task StartAsync(CancellationToken cancellationToken)
            {
                throw new System.NotImplementedException();
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                throw new System.NotImplementedException();
            }
        }
    }
    ```
    
6.  加入建構式(Constructor)並注入 ILogger 與 IHostApplicationLifetime 服務
    
    ```csharp
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    
    namespace GenericHostConsole
    {
        public class App : IHostedService
        {
            private readonly ILogger<App> logger;
            private readonly IHostApplicationLifetime appLifetime;
    
            public App(ILogger<App> logger, IHostApplicationLifetime appLifetime)
            {
                this.logger = logger;
                this.appLifetime = appLifetime;
            }
    
            public Task StartAsync(CancellationToken cancellationToken)
            {
                throw new System.NotImplementedException();
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                throw new System.NotImplementedException();
            }
        }
    }
    ```
    
7.  完成基礎 Console 應用程式開發
    
    這裡我不寫太多複雜的邏輯，本文主要用來示範如何利用 .NET Generic Host 的架構，做到**不用長時間執行**的 Console 應用程式。
    
    ```csharp
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    
    namespace GenericHostConsole
    {
        public class App : IHostedService
        {
            private readonly ILogger<App> logger;
            private readonly IHostApplicationLifetime appLifetime;
    
            public App(ILogger<App> logger, IHostApplicationLifetime appLifetime)
            {
                this.logger = logger;
                this.appLifetime = appLifetime;
            }
    
            public async Task StartAsync(CancellationToken cancellationToken)
            {
                logger.LogWarning("App running at: {time}", DateTimeOffset.Now);
    
                await Task.Yield();
    
                appLifetime.StopApplication();
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now);
                return Task.CompletedTask;
            }
        }
    }
    ```
    
    上述程式碼有幾個重點：
    
    1.  這裡的 `StartAsync()` 主要是程式的起點，而 `StopAsync()` 則是在程式結束時呼叫
    2.  .NET Generic Host 會自動判斷 [SIGTERM](https://en.wikipedia.org/wiki/Signal_\(IPC\)#SIGTERM) 訊號來結束 Host 執行
    3.  無論啟動或關閉都會傳入 `CancellationToken cancellationToken` 可以讓你**優雅的**結束 Task 執行
    4.  當程式執行完畢，可直接呼叫 `appLifetime.StopApplication();` 即可結束 Host 執行
8.  調整紀錄等級 (`LogLevel`)
    
    如果我們透過 `dotnet run` 執行程式，你也會從 Console 看到以下 Log 訊息，這是因為 [Host.CreateDefaultBuilder(args)](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs#L56-L134) 預設已經幫我們透過 [AddConsole()](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerExtensions.cs#L21-L33) 設定好了 [ConsoleLoggerProvider](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs)，而預設的 [LogLevel](https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-5.0&WT.mc_id=DT-MVP-4015686) 被設定在 `LogLevel.Information` 等級以上，所以會顯示許多紀錄：
    
    ```log
    warn: GenericHostConsole.App[0]
          Worker running at: 12/09/2020 00:17:28 +08:00
    info: Microsoft.Hosting.Lifetime[0]
          Application is shutting down...
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Production
    info: Microsoft.Hosting.Lifetime[0]
          Content root path: G:\Projects\GenericHostConsole
    warn: GenericHostConsole.App[0]
          Worker stopped at: 12/09/2020 00:17:28 +08:00
    ```
    
    若要調整最小紀錄等級為 `Warning` 以上，可以調整 `Program.cs` 裡面的 `CreateHostBuilder()` 方法如下：
    
    ```csharp
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging => {
                logging.SetMinimumLevel(LogLevel.Warning);
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<App>();
            });
    ```
    
    也可以直接新增一個 `appsettings.json` 檔案，並設定以下內容：
    
    ```json
    {
        "Logging": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
    ```
    
    ```log
    warn: GenericHostConsole.App[0]
          Worker running at: 12/09/2020 00:35:09 +08:00
    warn: GenericHostConsole.App[0]
          Worker stopped at: 12/09/2020 00:35:09 +08:00
    ```
    
9.  改用 [RunConsoleAsync()](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L170-L173) 啟動 Host 應用程式
    
    事實上，你的 `Program.Main()` 除了以下這種方法啟動 Host 應用程式外：
    
    ```csharp
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    ```
    
    其實還有另一種啟動方式：
    
    ```csharp
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).RunConsoleAsync();
    }
    ```
    
    這個 `RunConsoleAsync()` 如果你從[原始碼](https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L170-L173)去看，其實還蠻好理解的，其實它只是明確的使用了「預設」的 `UseConsoleLifetime()` 而已，但是程式的語意會更清楚一點，你也可以考慮用這個 API 來啟動 Console 應用程式：
    
    ```csharp
    public static Task RunConsoleAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
    {
        return hostBuilder.UseConsoleLifetime().Build().RunAsync(cancellationToken);
    }
    ```
    
10.  加入應用程式結束碼 (Exit Code)
     
     撰寫 Console 應用程式如果要搭配批次檔或 Shell Script 執行，指定其「結束碼」是非常重要的！
     
     以下是完整的實作範例：
     
     ```csharp
     using System;
     using System.Threading;
     using System.Threading.Tasks;
     using Microsoft.Extensions.Hosting;
     using Microsoft.Extensions.Logging;
     
     namespace GenericHostConsole
     {
         public class App : IHostedService
         {
             private int? _exitCode;
     
             private readonly ILogger<App> logger;
             private readonly IHostApplicationLifetime appLifetime;
     
             public App(ILogger<App> logger, IHostApplicationLifetime appLifetime)
             {
                 this.logger = logger;
                 this.appLifetime = appLifetime;
             }
     
             public Task StartAsync(CancellationToken cancellationToken)
             {
                 try
                 {
                     logger.LogWarning("Worker running at: {time}", DateTimeOffset.Now);
                     _exitCode = 1;
                 }
                 catch (Exception ex)
                 {
                     logger.LogError(ex, "Unhandled exception!");
                     _exitCode = 1;
                 }
                 finally
                 {
                     appLifetime.StopApplication();
                 }
     
                 return Task.CompletedTask;
             }
     
             public Task StopAsync(CancellationToken cancellationToken)
             {
                 logger.LogWarning("Worker stopped at: {time}", DateTimeOffset.Now);
     
                 Environment.ExitCode = _exitCode.GetValueOrDefault(-1);
     
                 return Task.CompletedTask;
             }
         }
     }
     ```
     

### 結語

你從這篇文章的範例程式中，應該可以發現從 .NET Core 開始，到現在最新的 .NET 6 都一樣，大量的**非同步程式碼**已經變成常態，如果你目前還對 C# 非同步沒有一個完整的概念，歡迎你來報名我在 2022-06-12(日) 與 2022-06-19(日) 的「[C# 開發實戰：非同步程式開發技巧](https://www.accupass.com/event/2202110214416242167820)」課程，我將會在兩天的時間內，幫助你徹頭徹尾的全面理解 C# 非同步程式開發觀念與技巧！ 👍

### 相關連結

-   [.NET Generic Host | Microsoft Docs](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686)
-   [.NET Generic Host in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-5.0&WT.mc_id=DT-MVP-4015686)
-   [ASP.NET Core Web Host](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-5.0&WT.mc_id=DT-MVP-4015686)
-   [runtime/src/libraries/Microsoft.Extensions.Hosting/src/](https://github.com/dotnet/runtime/tree/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Hosting/src)
-   [Generic Host Builder in ASP .NET Core 3.1 | Wake Up And Code!](https://wakeupandcode.com/generic-host-builder-in-asp-net-core-3-1/)
-   [Building a Console App with .NET Generic Host | David's Blog](https://dfederm.com/building-a-console-app-with-.net-generic-host/)
-   [Using HostBuilder and the Generic Host in .NET Core Microservices - Steve Gordon - Code with Steve](https://www.stevejgordon.co.uk/using-generic-host-in-dotnet-core-console-based-microservices)
