0%

[DotnetCore]ConsoleApp擁有Configuration及ServiceCollection機制

前情提要

筆者收到主管的指示,要來寫一個執行檔(exe)程式,筆者離這種exe程式好久遠了,自從轉換到Web領域後幾乎都是寫API為主,一開始從.net MVC5開始進入Web領域,寫了幾年的Razor View後,前一份工作剛好前後端分離,前幾年都在寫dotnet core web apiangular為主,Console Application離我好遙遠阿,但筆者已經被dotnet core的內建DI機制及AppSettingsConfig已經習以為常,對於Console Application來說這些都是要自己實作上去的,不妨藉由這次機會來實作看看。

筆者看了幾篇部落格後,想法是仿照dotnet core web api標準配置那樣

  • 有一個Startup類別
  • Startup類別中有public getConfiguration,令其他有需求的人可以存取Configuration物件
  • Startup中宣告ConfigureServices,並將配置好的ServiceCollection回傳
  • 宣告一個App類別,主要有Run這個Method,最終於Program中執行

製作商業邏輯Service

筆者這支的主要用意是檢查某個服務是否正常,可以想見驗證方式分為

  • ping test
  • http request test

對於應用程式來說,由注入時決定該用哪個方式即可,直接來看Code

1
2
3
4
public interface IPingTestService
{
Task<bool> PingHost(string hostName);
}

接著實作上面列到的兩種方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PingTestByPingService : IPingTestService
{
public async Task<bool> PingHost(string hostName)
{
bool pingable = false;
Ping pinger = null;
try
{
pinger = new Ping();
PingReply reply = await pinger.SendPingAsync(hostName);
pingable = reply.Status == IPStatus.Success;
}
finally
{
if (pinger != null)
{
pinger.Dispose();
}
}

return pingable;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class PintTestByHttpRequestService : IPingTestService
{
public async Task<bool> PingHost(string hostName)
{
var result = false;
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync(hostName);
result = response.StatusCode == HttpStatusCode.OK;
return result;
}
}

到這邊商業邏輯實作告一段落了,就先放在一邊,回頭把基礎建設都用好

製作Startup類別

我們要製作Startup類別中的AppSettingsDI機制需要爾外安裝相關的微軟提供套件,就先來安裝nuget套件們吧

1
2
3
4
5
6
# 以下為Configuration機制所需套件
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Binder
dotnet add package Microsoft.Extensions.Configuration.Json
# 以下為DI注入建置ServiceCollection所需套件
dotnet add package Microsoft.Extensions.DependencyInjection

安裝好nuget套件後,要來製作Startup類別了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Startup
{
// 主要規範由Startup初始化時建置設定,對於外部來說只能取得使用
public IConfiguration Configuration { get; private set; }
public Startup()
{
// 預設為appsettings.json
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false);
// 讀取環境變數
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
// 有環境變數設定則將appsettings.{environment}.json才是我們要建置的
if (!string.IsNullOrEmpty(environment))
{
builder = builder.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: false);
}
// 最後建置並指派給Configuration變數
this.Configuration = builder.Build();
}

public ServiceCollection ConfigureServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<App>();
// 由這邊切換一般Ping Test或Htpp Request Test
serviceCollection.AddTransient<IPingTestService, PintTestByHttpRequestService>();
return serviceCollection;
}
}

配置AppSettings

主要是新增各個環境的AppSettings檔案,這邊主要是要提醒大家,要將新增的appsettings檔案要去設定Copy always,筆者以Development檔案為準,列出筆者這邊的設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"EmailSetting": {
"SmtpServer": "SMTP Host",
"SmtpPort": 587,
"Account": "SMTP Account",
"Password": "SMTP Password"
},
// 欲測試服務對象FQDN
"TestUrl": "https://google.com.tw",
// 寄信相關設定
"MailToDo": {
"MailTo": "test@gmail.com;test2@gmail.com",
"MailCc": "",
"Subject": "Test Subject"
}
}

LaunchSettings配置

筆者這邊習慣調整LaunchSettings來切換Development, Staging, UAT等環境,Console Application的專案預設是沒有任何設定的,看不到Properties/LaunchSettings.json的,就動手加上去吧

加上去後會在Solution Explorer中會看到LaunchSettings.json

製作主要運作程式App類別

基礎建設都用完了,要來撰寫主要執行的App類別了,邏輯觀念為

  • Appsettings讀取EmailSettingMail相關設定
  • 宣告MySmtpClient物件
  • 執行主要Ping Test程式
  • 若回傳錯誤或者Exception則寄信通知對應的信箱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class App
{
private readonly IPingTestService _pingTestService;

public App(IPingTestService pingTestService)
{
_pingTestService = pingTestService;
}

public async Task Run(IConfiguration config)
{
// Step0: Data Initial
var emailSetting = config.GetSection("EmailSetting").Get<EmailSetting>();
var mailTodo = config.GetSection("MailToDo").Get<MailToDoItem>();
var mySmtpClient = new MySmtpClient(mailTodo, emailSetting);
mySmtpClient.MySendCompleted += MailSendCompleted;
try
{
// Step1: Ping test
var result = await _pingTestService.PingHost(config["TestUrl"]);
if (!result)
{
// Step2: Send mail if connection error
mailTodo.Message = "Monitor作業偵測到連線異常通知";
await mySmtpClient.SendMailProcess(null, null);
}
}
catch (Exception ex)
{
mailTodo.Subject = $"Monitor作業異常通知";
mailTodo.Message = $"{ex}";
await mySmtpClient.SendMailProcess(null, null);
}
}

private void MailSendCompleted(MailToDoItem mailToDoItem, AsyncCompletedEventArgs e)
{
// TODO: Send Complete Handler
}
}

上述程式中用到的MySmtpClientEmailSettingMailToDoItem類別宣告就不特別在此列出,可以參考筆者這篇文章

[DotnetCore]SMTP寄信服務設計

Program:Main程式

最後要來撰寫Main程式了,直接用Code說話

1
2
3
4
5
6
7
8
9
10
11
12
class Program
{
// reference: https://blog.poychang.net/dotnet-core-console-app-with-dependency-injection/
// reference: https://dev.to/iamrule/add-your-appsettingsjson-to-a-c-console-application-5gd6
public static async Task Main(string[] args)
{
var startUp = new Startup();
var serviceProvider = startUp.ConfigureServices().BuildServiceProvider();
var app = serviceProvider.GetRequiredService<App>();
await app.Run(startUp.Configuration);
}
}

結論

藉由這次,平常直接透過dotnet new webapi這種指令建置出Web API專案,基本上那些相關的設定都配置好了,這次撰寫過程,就會知道,之所以可以使用IConfiguration則,需要安裝哪些套件,DI注入宣告之ServiceCollection需要的是哪個對應的nuget套件,對於筆者來說,是滿大的收穫。

參考