分享程式代碼相關筆記
目前文章總數:157 篇
最後更新:2024年 12月 07日
打開範例專案後,專案分成以下 5 個:
1. Aspire Host | : | 主程式,此為專案的啟動點,進行管理、監控 |
2. MinIo Form 程式 | : | 加入現有 WindowsForm 專案,補充說明用 |
3. Mysql Web 網站 | : | Asp.net Core Web MVC 專案,用於說明容器化、實體切換 |
4. Redis Web 網站 | : | Asp.net Core Web MVC 專案,用於說明容器化、實體切換 |
5. Aspire 預設設定 | : | 此專案為 Aspire 預設的 OpenTelemetry 配置,使其他專案參考可以加入可觀察、監控 |
5 個專案相依如下,可以知道每個專案要可觀察、監控都需要參考 Aspire 預設設定 專案:
專案建立後,如何加入到 Aspire Host 可參考上篇:如何通過 Microsoft .NET Aspire 建立可觀察、具生產導向的分散式應用程式並與現有系統無縫整合
Aspire Host 專案分成以下:
1. 設定檔 | : | Appsettings.json 可以依照設定檔案,動態配置監控的專案 |
2. 主程式 | : | Aspire 主程式的配置,可透過當前配置,讓開發者決定使用的連線配置 |
以下為 Aspire 的設定檔案
1. ServiceSettings | : | 控制 3 個專案是否啟動 |
2. ConnectionStrings | : | 使用的 Redis、Mysql 連線字串,對應的專案也必須有相同的 Key、Value 以便於切換 |
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
},
"ServiceSettings": {
"MinIOFormExample": false,
"StartRedisConnectionExample": true,
"StartMysqlConnectionExample": true
},
"ConnectionStrings": {
"RedisDb": "127.0.0.1:1582,abortConnect=false,defaultDatabase=0",
"MySqlConnection": "Server=127.0.0.1;Port=3306;Database=testdb;User=root;Password=1qaz@WSX;"
}
}
以下為 Aspire 的主程式,取得設定檔、依照設定檔案配置決定啟動的專案、使用的 Redis 、 Mysql 連線對象
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
// 1. 取得服務啟動設定
var serviceSettings = builder.Configuration
.GetSection("ServiceSettings")
.Get<Dictionary<string, bool>>();
// 若無配置檔案 - 可直接關閉
if (serviceSettings == null)
return;
#region 2. 依照設定啟動服務 - 並且監控
// 方法處理 MinIO 服務
HandleMinIOForm(builder, serviceSettings);
// 方法處理 Redis Web測試網站
HandleRedisConnection(builder, serviceSettings);
// 方法處理 Mysql Web測試網站
HandleMysqlConnection(builder, serviceSettings);
#endregion
builder.Build().Run();
// 方法:2-1. 處理 MinIO 表單
void HandleMinIOForm(IDistributedApplicationBuilder builder, Dictionary<string, bool> settings)
{
// 權限開啟才啟動觀察
if (settings.TryGetValue("MinIOFormExample", out bool startMinIO) && startMinIO)
{
builder.AddProject<Projects.MinIOFormExample>("minioformexample");
}
}
// 方法:2-2. 處理 Redis 連接
void HandleRedisConnection(IDistributedApplicationBuilder builder, Dictionary<string, bool> settings)
{
// 權限開啟才啟動觀察
if (settings.TryGetValue("StartRedisConnectionExample", out bool startRedis) && startRedis)
{
// 示意 - 若環境為 Development 時,啟動開發環境的 Redis 連接
if (builder.Environment.IsDevelopment())
{
var redis = builder.AddConnectionString("RedisDb");
builder.AddProject<Projects.RedisConnectionWebExample>("redisconnectionexample")
.WithReference(redis);
}
else// 示意 - 若其他情況下,可以使用容器化的 Redis 連接
{
var redis = builder.AddRedis("RedisDb");
builder.AddProject<Projects.RedisConnectionWebExample>("redisconnectionexample")
.WithReference(redis);
}
}
}
// 方法:2-3. 處理 Mysql 連接
void HandleMysqlConnection(IDistributedApplicationBuilder builder, Dictionary<string, bool> settings)
{
// 權限開啟才啟動觀察
if (settings.TryGetValue("StartMysqlConnectionExample", out bool startRedis) && startRedis)
{
// 示意 - 若環境為 Development 時,啟動開發環境的 Mysql 連接
if (builder.Environment.IsDevelopment())
{
var mysql = builder.AddConnectionString("MySqlConnection");
builder.AddProject<Projects.MysqlConnectionWebExample>("mysqlconnectionexample")
.WithReference(mysql);
}
else// 示意 - 若其他情況下,可以使用容器化的 Mysql 連接
{
var mysql = builder.AddMySql("MySqlConnection");
builder.AddProject<Projects.MysqlConnectionWebExample>("mysqlconnectionexample")
.WithReference(mysql);
}
}
}
若要關閉整個專案的啟動,可以直接將 Appsettings.json 切換為 False
※這裡為了示意,實務上應要依照 Develop、Staging、Production 建立對應的 Appsettings.{Enviroment}.json 實體檔案
"MinIOFormExample": false,
接著啟動 Aspire.Host 專案後,可以觀察到 Minio 這個專案不會被加入 Aspire 的資源中
Redid Web 專案分成以下 4 點:
1. 設定檔 | : | Appsettings.json 需要與 Aspire.Host 一致,兩者才能動態傳遞 |
2. 主程式 | : | 啟動主程式,依賴注入 StackExchange.Redis 、連線資源 |
3. Html 檢視 | : | 提供驗證當前 Redis 專案,連線到的服務 |
4. 控制器 | : | 連線 Redis Server 邏輯,並回傳檢視當前的連線對象(容器 或 實體服務) |
設定檔的 Key : ConnectionStrings 與 Aspire Host 上的設定檔相同,兩者才可配對到
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RedisDb": "127.0.0.1:6379,abortConnect=false,defaultDatabase=0"
}
}
簡單的注入 Redis 連線配置,啟動時仍取 RedisDb 的 Value 做設定
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
//... 略
// 注入Redis連接字符串
var redisConnectionString = builder.Configuration.GetSection("ConnectionStrings:RedisDb").Value;
builder.Services.AddSingleton<IConnectionMultiplexer>(provider =>
{
return ConnectionMultiplexer.Connect(ConfigurationOptions.Parse(redisConnectionString));
});
var app = builder.Build();
//... 略
app.Run();
會從控制器將當前的Redis 連線字串、Redis 連線狀態 顯示於畫面。
@{
ViewData["Title"] = "Redis 測試頁面";
}
<div class="text-center">
<h1>Redis 連線字串</h1>
<p>@ViewBag.ConnectionString</p>
<h1>Redis 連線狀態</h1>
<p>@ViewBag.Message</p>
</div>
分成主要的 3 種控制器,用於取得顯示畫面、連接字串、連線狀態
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConnectionMultiplexer _redis;
public HomeController(ILogger<HomeController> logger,
IConnectionMultiplexer redisDb)
{
_logger = logger;
_redis = redisDb;
}
/// <summary>
/// 1. 取得頁面
/// </summary>
public IActionResult Index()
{
ViewBag.ConnectionString = RedisConnectionStr();
ViewBag.Message = RedisConnection();
return View();
}
/// <summary>
/// 2. 獲取連接字串
/// </summary>
private string RedisConnectionStr()
{
_logger.LogInformation(_redis.Configuration);
return _redis.Configuration ?? string.Empty;
}
/// <summary>
/// 3. 獲取連線狀態
/// </summary>
private string RedisConnection()
{
var message = string.Empty;
try
{
var db = _redis.GetDatabase();
// 使用 PING 命令來檢查連線
var response = db.Execute("PING");
if (response.ToString() == "PONG")
{
message = "成功連線到 Redis 伺服器!";
}
else
{
message = "無法確認連線狀態,回應:" + response;
}
}
catch (RedisConnectionException ex)
{
message = "無法連線到 Redis 伺服器:" + ex.Message;
}
catch (Exception ex)
{
message = "發生錯誤:" + ex.Message;
}
finally
{
// 關閉連接
if (_redis != null)
{
_redis.Close();
}
}
_logger.LogDebug(message);
return message;
}
}
現在模擬情境,在 開發 階段,都共用一個固定的 Redis 實體機器,Port:1582
在 部署 階段,都會啟用一個容器做持久化
※開發階段的 Redis 服務如下圖:
開啟 Aspire Host 專案 -> 檢視 Program.cs 代碼
加入以下 C# 代碼,當為開發環境時,使用 1582 這台機器的 Reids
否則都是使用產生容器化的 Redis 連接
// 方法:2-2. 處理 Redis 連接
void HandleRedisConnection(IDistributedApplicationBuilder builder, Dictionary<string, bool> settings)
{
// 權限開啟才啟動觀察
if (settings.TryGetValue("StartRedisConnectionExample", out bool startRedis) && startRedis)
{
// 示意 - 若環境為 Development 時,啟動開發環境的 Redis 連接
if (builder.Environment.IsDevelopment())
{
var redis = builder.AddConnectionString("RedisDb");
builder.AddProject<Projects.RedisConnectionWebExample>("redisconnectionexample")
.WithReference(redis);
}
else// 示意 - 若其他情況下,可以使用容器化的 Redis 連接
{
var redis = builder.AddRedis("RedisDb");
builder.AddProject<Projects.RedisConnectionWebExample>("redisconnectionexample")
.WithReference(redis);
}
}
}
我們啟動 Debug 模式,將 Aspire Host 啟動後,出現以下:
為了測試容器是否正常,可以將對應代碼註解,再次將 Aspire Host 啟動後,出現以下:
成功切換成 Redis 容器連線
// 示意 - 若環境為 Development 時,啟動開發環境的 Redis 連接
//if (builder.Environment.IsDevelopment())
//{
// var redis = builder.AddConnectionString("RedisDb");
// builder.AddProject<Projects.RedisConnectionWebExample>("redisconnectionexample")
// .WithReference(redis);
//}
若要使用容器化,需要在 Aspire Host 專案上 Nuget 安裝 Aspire.Hosting.Redis 套件
並且此容器化的套件要在 Debug 模式下使用,一定要先啟用 Windwos Desktop Docker
Redid Web 專案分成以下 4 點:
1. 設定檔 | : | Appsettings.json 需要與 Aspire.Host 一致,兩者才能動態傳遞 |
2. 主程式 | : | 啟動主程式,依賴注入 Mysql 、連線資源 |
3. Html 檢視 | : | 提供驗證當前 Mysql 專案,連線到的服務 |
4. 控制器 | : | 連線 Mysql Server 邏輯,並回傳檢視當前的連線對象、資料(容器 或 實體服務) |
設定檔的 Key : ConnectionStrings 與 Aspire Host 上的設定檔相同,兩者才可配對到
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MySqlConnection": "Server=127.0.0.1;Port=3306;Database=testdb;User=root;Password=1qaz@WSX;"
}
}
簡單的注入 Mysql 連線配置,啟動時仍取 MySqlConnection 的 Value 做設定
using MySql.Data.MySqlClient;
var builder = WebApplication.CreateBuilder(args);
//... 略
// 依賴注入 MySQL 連線服務
builder.Services.AddTransient<MySqlConnection>(sp =>
{
// 讀取 ConnectionString,並設定 MySQL DbContext
var connectionString = builder.Configuration.GetConnectionString("MySqlConnection");
return new MySqlConnection(connectionString);
});
var app = builder.Build();
//... 略
app.Run();
會從控制器將當前的MySql 連線字串、MySql 當前資料 顯示於畫面。
@{
ViewData["Title"] = "Mysql 測試頁面";
}
<div class="text-center">
<h1>MySql 連線字串</h1>
<p>@ViewBag.ConnectionString</p>
<h1>MySql 當前資料</h1>
<p>@ViewBag.Message</p>
</div>
分成主要的 3 種控制器,用於取得顯示畫面、連接字串、資料
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly MySqlConnection _mySqlConnection;
public HomeController(ILogger<HomeController> logger,
MySqlConnection mysql)
{
_logger = logger;
_mySqlConnection = mysql;
}
/// <summary>
/// 1. 取得頁面
/// </summary>
public IActionResult Index()
{
ViewBag.ConnectionString = MysqlConnectionStr();
ViewBag.Message = MysqlGetTableData();
return View();
}
/// <summary>
/// 2. 獲取連接字串
/// </summary>
private string MysqlConnectionStr()
{
_logger.LogInformation(_mySqlConnection.ConnectionString);
return _mySqlConnection.ConnectionString ?? string.Empty;
}
/// <summary>
/// 3. 獲取連線資料
/// </summary>
private string MysqlGetTableData()
{
var message = string.Empty;
try
{
_mySqlConnection.Open();
var collectionData = new List<TestAspireTable>();
var command = new MySqlCommand("SELECT SeqNo, Comment FROM testaspiretable", _mySqlConnection);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
// 讀取資料
var newItem = new TestAspireTable();
newItem.SeqNo = int.Parse(reader["SeqNo"].ToString());
newItem.Comment = reader["Comment"].ToString();
collectionData.Add(newItem);
}
}
// 關閉連接
_mySqlConnection.Close();
// Json 序列化參數
var options = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
message = JsonSerializer.Serialize(collectionData, options);
}
catch (Exception ex)
{
message = "發生錯誤:" + ex.Message;
}
_logger.LogDebug(message);
return message;
}
/// <summary>
/// 測試 Mysql 連線的資料表
/// </summary>
private class TestAspireTable
{
public int SeqNo { get; set; }
public string Comment { get; set; } = string.Empty;
}
}
現在模擬情境,在 開發 階段,都共用一個固定的 Mysql 實體機器,Port:3306
在 部署 階段,都會啟用一個容器做持久化
※開發階段的 Mysql 服務如下圖:
開啟 Aspire Host 專案 -> 檢視 Program.cs 代碼
加入以下 C# 代碼,當為開發環境時,使用 3306 這台機器的 Mysql 資料庫
否則都是使用產生容器化的 Mysql 連接
// 方法:2-3. 處理 Mysql 連接
void HandleMysqlConnection(IDistributedApplicationBuilder builder, Dictionary<string, bool> settings)
{
// 權限開啟才啟動觀察
if (settings.TryGetValue("StartMysqlConnectionExample", out bool startRedis) && startRedis)
{
// 示意 - 若環境為 Development 時,啟動開發環境的 Mysql 連接
if (builder.Environment.IsDevelopment())
{
var mysql = builder.AddConnectionString("MySqlConnection");
builder.AddProject<Projects.MysqlConnectionWebExample>("mysqlconnectionexample")
.WithReference(mysql);
}
else// 示意 - 若其他情況下,可以使用容器化的 Mysql 連接
{
var mysql = builder.AddMySql("MySqlConnection");
builder.AddProject<Projects.MysqlConnectionWebExample>("mysqlconnectionexample")
.WithReference(mysql);
}
}
}
我們啟動 Debug 模式,將 Aspire Host 啟動後,出現以下:
為了測試容器是否正常,可以將對應代碼註解,再次將 Aspire Host 啟動後,出現以下:
成功切換成 Mysql 容器連線
// 示意 - 若環境為 Development 時,啟動開發環境的 Mysql 連接
//if (builder.Environment.IsDevelopment())
//{
// var mysql = builder.AddConnectionString("MySqlConnection");
// builder.AddProject<Projects.MysqlConnectionWebExample>("mysqlconnectionexample")
// .WithReference(mysql);
//}
若要使用容器化,需要在 Aspire Host 專案上 Nuget 安裝 Aspire.Hosting.MySql 套件
並且此容器化的套件要在 Debug 模式下使用,一定要先啟用 Windwos Desktop Docker
Aspire Host 空專案,即使把專案做關聯,但不會自動加入追蹤,追蹤還需要做些配置
對整個專案滑鼠右鍵 -> 加入 -> 新增專案
添加 .NET Aspire 服務預設值 專案
建立專案名稱:
建立完成後會有 Extension.cs ,為了便於閱讀,這邊將代碼做英文翻譯成中文如下:
namespace Microsoft.Extensions.Hosting
{
// 新增常見的 .NET Aspire 服務:服務發現、彈性、運行狀況檢查和 OpenTelemetry。
// 您的解決方案中的每個服務項目都應引用該項目。
// 要了解有關使用此項目的更多信息,請參閱 https://aka.ms/dotnet/aspire/service-defaults
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// 預設開啟 彈性處理程序
http.AddStandardResilienceHandler();
// 預設開啟 服務發現
http.AddServiceDiscovery();
});
// 取消以下註釋內容可限制"允許"的服務發現方案。
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
// {
// options.AllowedSchemes = ["https"];
// });
return builder;
}
/// <summary>
/// 配置分散式追蹤和指標收集,允許應用程式的監控和可觀察性。
/// </summary>
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
// 取消註釋以下行以啟用 gRPC 檢測(需要 OpenTelemetry.Instrumentation.GrpcNetClient 套件)
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation();
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
// 取消註解以下行以啟用 Azure Monitor 匯出器(需要 Azure.Monitor.OpenTelemetry.AspNetCore 套件)
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
//{
// builder.Services.AddOpenTelemetry()
// .UseAzureMonitor();
//}
return builder;
}
/// <summary>
/// 健康檢查
/// </summary>
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
// 新增預設的"健康檢查"以確保自己的應用程式能夠回應
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder;
}
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// 將運行狀況檢查端點新增至非開發環境中的應用程式會產生安全隱患。
// 在非開發環境中啟用這些端點之前,請參閱 https://aka.ms/dotnet/aspire/healthchecks 以了解詳細資訊。
if (app.Environment.IsDevelopment())
{
// 所有的運行"健康檢查"必須通過,此應用程式才能在啟動後視為準備好接受傳輸流量
app.MapHealthChecks("/health");
// 只有標有「live」標籤的"健康檢查"通過,此應用程式才會被視為處於 live 狀態
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}
}
開啟 Redis Web 的 Program.cs 將以下代碼添加
完成對 .Net Aspire 預設值服務的參考 & 依賴注入
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
// 1. [加入結構化、計量、追蹤] Aspire 要能可觀察此專案,需要增加以下服務
builder.AddServiceDefaults();
// 略...
var app = builder.Build();
// 略...
//// 2. [加入結構化、計量、追蹤] Aspire 要能可觀察此專案,需要增加以下服務
app.MapDefaultEndpoints();
app.Run();
開啟 Mysql Web 的 Program.cs 將以下代碼添加
完成對 .Net Aspire 預設值服務的參考 & 依賴注入
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
// 1. [加入結構化、計量、追蹤] Aspire 要能可觀察此專案,需要增加以下服務
builder.AddServiceDefaults();
// 略...
var app = builder.Build();
// 略...
//// 2. [加入結構化、計量、追蹤] Aspire 要能可觀察此專案,需要增加以下服務
app.MapDefaultEndpoints();
app.Run();
再次開啟追蹤,可以發現下拉式選單,已經上述的 2 個專案加入,可以進行追蹤
照下面步驟,我們測試當 API 錯誤時,追蹤的變化
透過觀察,追蹤 的好處是讓生產環境的維護 IT、開發人員更好追蹤錯誤
若本機、部署環境機器資源配置過低,很容易發生以下錯誤:
錯誤資訊:未預期的錯誤
原因是因為本機記憶體過少、導致並不是每次啟動應用程式都會正常,這是因為 Aspire Host 依賴所有專案,並且啟用追蹤,需要大量硬體資源