只有累積,沒有奇蹟

2021年9月2日 星期四

[NETCore] ASP.NET Core 建立排程服務 - 使用 Generic Host 搭配 Quartz.Net - Part 1

前言
最近有個需求是固定時間取得特定資料進行修改,在查詢相關資料之後決定使用 ASP.NET Core Generic Host 為出發,在搭配 .NET 中熱門的排程套件 Quartz.Net,測試完畢之後再將程式註冊為 Windows Service 服務就可滿足使用者的需求,這篇文章是整理開發時的重點流程為系列文,給有需要使用 ASP.NET Core 開發排程相關應用程式需求的朋友一些參考若有問題或是錯誤的地方歡迎各位高手給予指導
GenericHostLab SampleCode 傳送門 : http://bit.ly/2Y1mqYN
Generic Host
在過去處理 HTTP 請求時可以使用 WebHostBuilder 類別來處理,在開發 ASP.NET Core 應用程式設定及建立 WebHost 以及使用 Kestrel 服務接收請求並處理應用程式的生命週期,使用 DI 設定或配置 Logging 等重要功能,但並非所有的需求都依賴於 HTTP ,如 BackgroundService 背景執行程序或是處理 Queue 的資料等工作就不需要依賴於 HTTP 請求,從 ASP.NET Core 2.1 開始提供 Generic Host (泛型主機),提供開發者可以有新的選項來開發沒有 HTTP 請求的應用程式,可以透過 MSDN 示意圖了解

CreateHostedBuilder

如果選擇 Generic Host 開發程式,建議先了解 WebHostBuilder 背後預設配置的設定方式以及做了哪些事情,在之前 ASP.NET Core 環境佈署設定 appsettings.json 文章中有介紹過,建立新的 ASP.NTE Core 應用程式預設模板可以看到在 program.cs 中內建  CreateWebHostBuilder ,此方法代碼中設定很多預設的配置,像是指定讀取資料夾中的 appsetting.json、環境變數、logging 設定、使用 Kestrel Server 等預設內容。了解 WebHostBuilder 是如何設定的也有了基本概念,就可以發現有部分內容在 HostBuilder 是可以共用的,就可以開始著手開發 Generic Host 程式,首先先建立新的 ASP.NET Core Console 專案,並開啟 Program.cs 建立 CreateHostBuilder 運算式,代碼如下
  1. private static IHostBuilder CreateHostBuilder() =>
  2. new HostBuilder()
  3. .ConfigureHostConfiguration(configHost =>
  4. {
  5. // setup Host configuration
  6. configHost.SetBasePath(Directory.GetCurrentDirectory())
  7. .AddJsonFile("hostsettings.json", optional: true)
  8. .AddEnvironmentVariables();
  9. })
  10. .ConfigureAppConfiguration((hostContext, configApp) =>
  11. {
  12. // setup App configuration
  13. configApp.AddJsonFile("appsettings.json")
  14. .AddJsonFile(
  15. $"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json",
  16. optional: true, reloadOnChange: true)
  17. .AddEnvironmentVariables();
  18. })
  19. .ConfigureServices((services =>
  20. {
  21. services.Configure<HostOptions>(option =>
  22. {
  23. option.ShutdownTimeout = TimeSpan.FromSeconds(30);
  24. });
  25. }))
  26. .ConfigureLogging((hostingContext, logging) =>
  27. {
  28. // Get Config from Logging section
  29. logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"))
  30. .AddEventLog();
  31.  
  32. if (hostingContext.HostingEnvironment.IsDevelopment())
  33. {
  34. logging.AddConsole();
  35. }
  36. });
在 CreateHostBuilder 中首先先建立一個 HostBuilder,命名空間為 Microsoft.Extensions.Hosting,透過 HostBuilder 來定義創建的主機後,定義以下幾件事情

  • ConfigureHostConfiguration : 設定 host 相關設定檔案
  • ConfigureAppConfiguration : 設定 application 設定檔格示為 json,檔名為 appsettings.json
  • ConfigureLogging : 設定 logging 內容設定由 config 的 Logging 區段定義,且寫到 eventLog 中
  • ConfigureServices : 設定關機餘時間延長為 30 秒 (預設為 5 秒),用意是希望收到關機指令時可以有時間處理正在的任務
  • 上述代碼要編譯成功,需要至 Nuget 安裝下列相關套件
    1. Install-Package Microsoft.Extensions.Hosting -Version 2.2.0
    2. Install-Package Microsoft.Extensions.Configuration.FileExtensions -Version 2.2.0
    3. Install-Package Microsoft.Extensions.Configuration.Json -Version 2.2.0
    4. Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables -Version 2.2.0
    5. Install-Package Microsoft.Extensions.Configuration.CommandLine -Version 2.2.0
    6. Install-Package Microsoft.Extensions.Logging.Console -Version 2.2.0
    7. Install-Package Microsoft.Extensions.Logging.Debug -Version 2.2.0
    8. Install-Package Microsoft.Extensions.Logging.EventLog
    如果對於 WebHostBuilder 原理想了解更多,可以參考 andrewlock 文章 :  HostBuilder v.s WebHostBuilder 

    TimeHotedService
    下一步是建立 TimeHostedService 類別並實作 IHostedSercice 介面,IHostedService interface 中提供 StartAsync 和 StopAsync 方法,在 MSDN 提到 Start 是在 IApplicationLifetime.ApplicationStarted 觸發之後會接續執行,反之 Stop 是主機停止停止時會觸發,source code 如下
    1. namespace Microsoft.Extensions.Hosting
    2. {
    3. //
    4. // Summary:
    5. // Defines methods for objects that are managed by the host.
    6. public interface IHostedService
    7. {
    8. //
    9. // Summary:
    10. // Triggered when the application host is ready to start the service.
    11. Task StartAsync(CancellationToken cancellationToken);
    12. //
    13. // Summary:
    14. // Triggered when the application host is performing a graceful shutdown.
    15. Task StopAsync(CancellationToken cancellationToken);
    16. }
    17. }
    起手式我們可以先建立簡單的 HostedService 來驗證是否正常,如果正常了在把 Service 替換為我們想要的 Quartz.Net 實作內容,在這邊建立 TimeHostedService 類別主要功能是每秒 console 中顯示現在的時間,代碼如下
    1. namespace GenericHostLab.Service
    2. {
    3. internal class TimedHostedService : IHostedService, IDisposable
    4. {
    5. private readonly ILogger _logger;
    6. private Timer _timer;
    7.  
    8. public TimedHostedService(ILogger<TimedHostedService> logger)
    9. {
    10. _logger = logger;
    11. }
    12.  
    13. public Task StartAsync(CancellationToken cancellationToken)
    14. {
    15. _logger.LogInformation("Timed Background Service is starting.");
    16.  
    17. _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
    18.  
    19. return Task.CompletedTask;
    20. }
    21.  
    22. private void DoWork(object state)
    23. {
    24. _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd hh:mm:ss}] Timed Background Service is working.");
    25. }
    26.  
    27. public Task StopAsync(CancellationToken cancellationToken)
    28. {
    29. _logger.LogInformation("Timed Background Service is stopping.");
    30.  
    31. _timer?.Change(Timeout.Infinite, 0);
    32.  
    33. return Task.CompletedTask;
    34. }
    35.  
    36. public void Dispose()
    37. {
    38. _timer?.Dispose();
    39. }
    40. }
    41. }
    將 TimedHostedService 加入到 Service 中
    1. .ConfigureServices((hostContext, services) =>
    2. {
    3. // ...
    4. services.AddHostedService<TimedHostedService>();
    5. }
    在 main 方法中直接使用  RunConsoleAync 
    1. static async Task Main(string[] args)
    2. {
    3. try
    4. {
    5. var builder = CreateHostBuilder();
    6.  
    7. await builder.RunConsoleAsync();
    8. }
    9. catch (Exception e)
    10. {
    11. Console.WriteLine(e);
    12. throw;
    13. }
    14. }
    這邊需要注意的是由於 async 是 C# 7.1 開始支援的功能,因此如果專案預設編譯需要設定為使用 C# 7.1 來進行編譯,否則上面代碼可能會造成編譯錯誤,錯誤訊息為 : Feature 'default literal' is not available in C# 7.0. Please use language version 7.1 or greater.,解決方法只要去設定專案編譯 C# 版本即可,設定方式為 專案點選右鍵 > property > Advance 
    完成了基本功能設定,按下執行可以看到 Console 跑出來的結果如下
    這一篇介紹 Generic Host 基本設定,其中包含應用程式起來時候的配置,為了避免一篇文章太冗長看了想睡覺,下一篇再來介紹剩下的部分也就是 Quartz.Net 的方式,也可以趁這機會稍微消化一下,看是否有不懂的地方也歡迎隨時提出來,謝謝

    參考
    Creating a Quartz.NET hosted service with ASP.NET Core
    .NET 泛型主機

    Related Posts:

    • [.NET] 使用 WebAPI 回收 Application Pool 說明 最近常聽維運同仁在群組提到某應用程式在尖峰時間 CPU 與 Memory 持續飆高,處理方式都是通知 RD 同仁連回公司將 App Pool 進行回收的動作,雖然可以暫時解決問題但如果提供工具讓維運同仁可以自行回收的動作,相信可以讓雙方節省更多的時間,這種方式屬於 work around,如果很頻繁發生代表 Code 有問題要找出 root cause 才是正解  Recyle Application 以下就簡單介… Read More
    • [UnitTest] NUnit 入門教學前言 NUnit 是一個 open source 的 .Net 單元測試框架,根據官方統計下載次數達到 3000 萬次,支援 .Net Framework 與 .Net Core,近幾年開始在寫單元測試以來一直是使用 NUnit 寫單元測試為主,最近計畫把常用的或是上課學到好用的功能寫成文章分享出來,以下簡單介紹如何在.Net Framework 使用 NUnit 寫單元測試 安裝 NUnit 首先我們到 Visual Studio … Read More
    • [.NET] 使用 ParseQueryString 取得網址參數說明 在古早的時代要取得網址參數時,都會自己寫一段 function 的方式來 Parse 需要解析的 Querystring 值,透過 For 迴圈將帶進來的字串取得 key 與 value 後放到 dictionary 集合中,Code 如下 public static Dictionary QueryParse(string url) { Dictionary qDict = new Dictionary(); foreac… Read More
    • [CheatSheets] ASP.NET 狀態比較表 ASP.NET 狀態比較表 幸運的找到一張圖說明ASP.NET的各種狀態(ASP.NET State Management)比較表,透過這張圖表可以更詳細的瞭解各狀態之間的關係比較 圖表來源 Bubblog,如有侵權或是不妥請告知 … Read More
    • [.NET] 檔案名稱: redirection.config 錯誤: 無法讀取設定檔案,因為權限不足問題 假日在自己練習要透過 WebAPI 對 IIS Server 進行 App Pool 的操作,呼叫寫好的 API 接口時一直噴錯誤,檔案名稱: redirection.config 錯誤: 無法讀取設定檔案,因為權限不足,這篇文章簡單紀錄處理問題過程 處理方式 An error has occurred. 檔案名稱: redirection.config 錯誤: 無法讀取設定檔案,因為權限不足 System.UnauthorizedA… Read More

    0 意見:

    張貼留言

    Copyright © 2025 m@rcus 學習筆記 | Powered by Blogger

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com