建立自訂 .NET.NET Aspire 用戶端整合
本文是 建立自定義 .NET.NET Aspire 主機整合 一文的延續。 它會引導您建立使用 .NET 傳送電子郵件的 .NET Aspire 用戶端整合。 接著,此整合會新增至您先前建置的電子報應用程式。 上一個範例省略了用戶端整合的建立,而是依賴現有的 .NETSmtpClient
。 最好使用 MailKit 的 SmtpClient
而不是官方的 .NETSmtpClient
來傳送電子郵件,因為它更現代化,並支援更多功能和通訊協定。 如需詳細資訊,請參閱 .NET SmtpClient:備註。
先決條件
如果您有在遵循說明,按照建立自定義.NET.NET Aspire主機整合文章中的步驟操作,那麼您應該已經擁有一個電子報應用程式。
提示
本文以現有 .NET.NET Aspire 整合為靈感,並根據團隊的官方指引。 在某些地方,指導方針有所不同,理解這些差異背後的原因是重要的。 如需詳細資訊,請參閱 .NET.NET Aspire 整合需求。
建立整合函式庫
.NET .NET Aspire 整合 會以 NuGet 套件的形式傳遞,但在此範例中,發佈 NuGet 套件超出本文的範圍。 相反地,您會建立包含整合的類別庫專案,並將其參考為專案。 .NET .NET Aspire 整合套件的目的是包裝用戶端連結庫,例如MailKit,並提供生產就緒遙測、健康情況檢查、可設定性和可測試性。 讓我們從建立新的類別庫項目開始。
在與上一篇文章中
MailKit.Client
相同的目錄中,建立名為 的新類別庫專案。dotnet new classlib -o MailKit.Client
將專案新增至方案。
dotnet sln ./MailDevResource.sln add MailKit.Client/MailKit.Client.csproj
下一個步驟是新增整合所依賴的所有 NuGet 套件。 與其讓您從 .NET CLI 逐一新增每個套件,可能更容易將下列 XML 複製並貼到 MailKit 中的Client.csproj 檔案。
<ItemGroup>
<PackageReference Include="MailKit" Version="4.11.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Resilience" Version="9.3.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.2" />
</ItemGroup>
定義整合設定
每當您建立 .NET.NET Aspire 整合時,最好了解您要對應的用戶端庫。 使用MailKit時,您必須瞭解連線到簡單郵件傳輸通訊協定 (SMTP) 伺服器所需的組態設定。 但還需了解程式庫是否支援 健康情況檢查、追蹤 和 計量。 MailKit 透過其 MailKit.Client
的檔案中,將下列程式代碼新增至 專案:
using System.Data.Common;
namespace MailKit.Client;
/// <summary>
/// Provides the client configuration settings for connecting MailKit to an SMTP server.
/// </summary>
public sealed class MailKitClientSettings
{
internal const string DefaultConfigSectionName = "MailKit:Client";
/// <summary>
/// Gets or sets the SMTP server <see cref="Uri"/>.
/// </summary>
/// <value>
/// The default value is <see langword="null"/>.
/// </value>
public Uri? Endpoint { get; set; }
/// <summary>
/// Gets or sets a boolean value that indicates whether the database health check is disabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool DisableHealthChecks { get; set; }
/// <summary>
/// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool DisableTracing { get; set; }
/// <summary>
/// Gets or sets a boolean value that indicates whether the OpenTelemetry metrics are disabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool DisableMetrics { get; set; }
internal void ParseConnectionString(string? connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException($"""
ConnectionString is missing.
It should be provided in 'ConnectionStrings:<connectionName>'
or '{DefaultConfigSectionName}:Endpoint' key.'
configuration section.
""");
}
if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
{
Endpoint = uri;
}
else
{
var builder = new DbConnectionStringBuilder
{
ConnectionString = connectionString
};
if (builder.TryGetValue("Endpoint", out var endpoint) is false)
{
throw new InvalidOperationException($"""
The 'ConnectionStrings:<connectionName>' (or 'Endpoint' key in
'{DefaultConfigSectionName}') is missing.
""");
}
if (Uri.TryCreate(endpoint.ToString(), UriKind.Absolute, out uri) is false)
{
throw new InvalidOperationException($"""
The 'ConnectionStrings:<connectionName>' (or 'Endpoint' key in
'{DefaultConfigSectionName}') isn't a valid URI.
""");
}
Endpoint = uri;
}
}
}
上述程式碼定義 MailKitClientSettings
類別,使用下列方式:
-
Endpoint
屬性,表示 SMTP 伺服器的連接字串。 -
DisableHealthChecks
屬性,用以啟用或停用健康檢查。 -
DisableTracing
屬性,決定是否啟用追蹤。 -
DisableMetrics
屬性,決定是否啟用計量。
剖析連接字串邏輯
settings 類別也包含 ParseConnectionString
方法,可將連接字串剖析為有效的 Uri
。 組態預期會以以下格式提供:
-
ConnectionStrings:<connectionName>
:SMTP 伺服器的連接字串。 -
MailKit:Client:ConnectionString
:SMTP 伺服器的連接字串。
如果這兩個值皆未提供,則會引發例外狀況。
公開用戶端功能
.NET
.NET Aspire 整合的目標是透過相依性插入向取用者公開基礎客戶端連結庫。 使用MailKit和此範例時,SmtpClient
類別就是您想要公開的內容。 您不會包裝任何功能,而是將組態設定對應至 SmtpClient
類別。 對於整合來說,公開標準及鍵控服務的註冊是很常見的。 當服務只有一個實例時,就會使用標準註冊,而當有多個服務實例時,會使用索引鍵服務註冊。 有時候,若要建立相同類型的多個註冊,可以使用工廠模式。 在名為 MailKit.Client
的檔案中,將下列程式代碼新增至 專案:
using MailKit.Net.Smtp;
namespace MailKit.Client;
/// <summary>
/// A factory for creating <see cref="ISmtpClient"/> instances
/// given a <paramref name="smtpUri"/> (and optional <paramref name="credentials"/>).
/// </summary>
/// <param name="settings">
/// The <see cref="MailKitClientSettings"/> settings for the SMTP server
/// </param>
public sealed class MailKitClientFactory(MailKitClientSettings settings) : IDisposable
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
private SmtpClient? _client;
/// <summary>
/// Gets an <see cref="ISmtpClient"/> instance in the connected state
/// (and that's been authenticated if configured).
/// </summary>
/// <param name="cancellationToken">Used to abort client creation and connection.</param>
/// <returns>A connected (and authenticated) <see cref="ISmtpClient"/> instance.</returns>
/// <remarks>
/// Since both the connection and authentication are considered expensive operations,
/// the <see cref="ISmtpClient"/> returned is intended to be used for the duration of a request
/// (registered as 'Scoped') and is automatically disposed of.
/// </remarks>
public async Task<ISmtpClient> GetSmtpClientAsync(
CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_client is null)
{
_client = new SmtpClient();
await _client.ConnectAsync(settings.Endpoint, cancellationToken)
.ConfigureAwait(false);
}
}
finally
{
_semaphore.Release();
}
return _client;
}
public void Dispose()
{
_client?.Dispose();
_semaphore.Dispose();
}
}
MailKitClientFactory
類別是一個處理站,會根據組態設定建立 ISmtpClient
實例。 它負責返回一個具有有效連接到已配置的 SMTP 伺服器的 ISmtpClient
實現。 接下來,您需要公開功能以便讓使用者能在相依注入容器中註冊此工廠。 在名為 MailKit.Client
的檔案中,將下列程式代碼新增至 專案:
using MailKit;
using MailKit.Client;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.Hosting;
/// <summary>
/// Provides extension methods for registering a <see cref="SmtpClient"/> as a
/// scoped-lifetime service in the services provided by the <see cref="IHostApplicationBuilder"/>.
/// </summary>
public static class MailKitExtensions
{
/// <summary>
/// Registers 'Scoped' <see cref="MailKitClientFactory" /> for creating
/// connected <see cref="SmtpClient"/> instance for sending emails.
/// </summary>
/// <param name="builder">
/// The <see cref="IHostApplicationBuilder" /> to read config from and add services to.
/// </param>
/// <param name="connectionName">
/// A name used to retrieve the connection string from the ConnectionStrings configuration section.
/// </param>
/// <param name="configureSettings">
/// An optional delegate that can be used for customizing options.
/// It's invoked after the settings are read from the configuration.
/// </param>
public static void AddMailKitClient(
this IHostApplicationBuilder builder,
string connectionName,
Action<MailKitClientSettings>? configureSettings = null) =>
AddMailKitClient(
builder,
MailKitClientSettings.DefaultConfigSectionName,
configureSettings,
connectionName,
serviceKey: null);
/// <summary>
/// Registers 'Scoped' <see cref="MailKitClientFactory" /> for creating
/// connected <see cref="SmtpClient"/> instance for sending emails.
/// </summary>
/// <param name="builder">
/// The <see cref="IHostApplicationBuilder" /> to read config from and add services to.
/// </param>
/// <param name="name">
/// The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the
/// service and also to retrieve the connection string from the ConnectionStrings configuration section.
/// </param>
/// <param name="configureSettings">
/// An optional method that can be used for customizing options. It's invoked after the settings are
/// read from the configuration.
/// </param>
public static void AddKeyedMailKitClient(
this IHostApplicationBuilder builder,
string name,
Action<MailKitClientSettings>? configureSettings = null)
{
ArgumentNullException.ThrowIfNull(name);
AddMailKitClient(
builder,
$"{MailKitClientSettings.DefaultConfigSectionName}:{name}",
configureSettings,
connectionName: name,
serviceKey: name);
}
private static void AddMailKitClient(
this IHostApplicationBuilder builder,
string configurationSectionName,
Action<MailKitClientSettings>? configureSettings,
string connectionName,
object? serviceKey)
{
ArgumentNullException.ThrowIfNull(builder);
var settings = new MailKitClientSettings();
builder.Configuration
.GetSection(configurationSectionName)
.Bind(settings);
if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
{
settings.ParseConnectionString(connectionString);
}
configureSettings?.Invoke(settings);
if (serviceKey is null)
{
builder.Services.AddScoped(CreateMailKitClientFactory);
}
else
{
builder.Services.AddKeyedScoped(serviceKey, (sp, key) => CreateMailKitClientFactory(sp));
}
MailKitClientFactory CreateMailKitClientFactory(IServiceProvider _)
{
return new MailKitClientFactory(settings);
}
if (settings.DisableHealthChecks is false)
{
builder.Services.AddHealthChecks()
.AddCheck<MailKitHealthCheck>(
name: serviceKey is null ? "MailKit" : $"MailKit_{connectionName}",
failureStatus: default,
tags: []);
}
if (settings.DisableTracing is false)
{
builder.Services.AddOpenTelemetry()
.WithTracing(
traceBuilder => traceBuilder.AddSource(
Telemetry.SmtpClient.ActivitySourceName));
}
if (settings.DisableMetrics is false)
{
// Required by MailKit to enable metrics
Telemetry.SmtpClient.Configure();
builder.Services.AddOpenTelemetry()
.WithMetrics(
metricsBuilder => metricsBuilder.AddMeter(
Telemetry.SmtpClient.MeterName));
}
}
}
上述程式代碼會在 IHostApplicationBuilder
類型上新增兩個擴充方法,一個用於 MailKit 的標準註冊,另一個用於 MailKit 的索引鍵註冊。
提示
.NET
.NET Aspire 整合的擴充方法應該擴充 IHostApplicationBuilder
類型,並遵循 Add<MeaningfulName>
命名慣例,其中 <MeaningfulName>
是您新增的類型或功能。 在本文中,會使用 AddMailKitClient
擴充方法來新增MailKit用戶端。 使用 AddMailKitSmtpClient
而不是 AddMailKitClient
可能更符合官方指導方針,因為這樣做只會註冊 SmtpClient
,而不是整個 MailKit 程式庫。
這兩個延伸模組最終都依賴私人 AddMailKitClient
方法,將 MailKitClientFactory
相依性插入容器註冊為 範圍服務。 將 MailKitClientFactory
註冊為範圍服務的原因是因為連線作業被視為開銷高昂,因此應該在可能的情況下,在相同範圍內重複使用。 換句話說,對於單一要求,應該使用相同的 ISmtpClient
實例。 工廠會保留它所建立的 SmtpClient
實例並加以處置。
組態系結
AddMailKitClient
方法私用實作的第一件事之一,就是將組態設定系結至 MailKitClientSettings
類別。 設定類別會被實例化,然後用組態中特定區段來呼叫 Bind
。 然後使用目前的設定呼叫可選的configureSettings
委派。 這可讓使用者進一步配置選項,確保手動程式代碼設定優先於組態設定。 接著,取決於是否提供 serviceKey
值,MailKitClientFactory
應該向相依注入容器註冊為標準或鍵值服務。
重要
在註冊服務時,特意呼叫 implementationFactory
多載。 當組態無效時,CreateMailKitClientFactory
方法會擲回。 這可確保只在需要時再延後建立 MailKitClientFactory
,同時並防止應用程式在記錄功能可用之前便發生錯誤。
以下各節將更詳細地說明健康檢查與遙測的註冊。
新增健康檢查
健康檢查 是用來監控整合狀況的方法。 使用MailKit,您可以檢查 SMTP 伺服器的連線是否狀況良好。 在名為 MailKit.Client
的檔案中,將下列程式代碼新增至 專案:
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace MailKit.Client;
internal sealed class MailKitHealthCheck(MailKitClientFactory factory) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
// The factory connects (and authenticates).
_ = await factory.GetSmtpClientAsync(cancellationToken);
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(exception: ex);
}
}
}
健康檢查的前述實施過程:
- 實作
IHealthCheck
介面。 - 接受
MailKitClientFactory
做為主要建構函式參數。 - 透過以下方式滿足
CheckHealthAsync
方法:- 嘗試從
ISmtpClient
取得factory
實例。 如果成功,則會傳回HealthCheckResult.Healthy
。 - 如果發生例外狀況,則會傳回
HealthCheckResult.Unhealthy
。
- 嘗試從
如先前在註冊MailKitClientFactory
時所述,MailKitHealthCheck
有條件地向IHeathChecksBuilder
註冊:
if (settings.DisableHealthChecks is false)
{
builder.Services.AddHealthChecks()
.AddCheck<MailKitHealthCheck>(
name: serviceKey is null ? "MailKit" : $"MailKit_{connectionName}",
failureStatus: default,
tags: []);
}
取用者可以選擇省略健康情況檢查,方法是將設定中的 DisableHealthChecks
屬性設定為 true
。 整合的常見模式是具有選擇性的功能,.NET.NET Aspire 整合強烈建議這些類型的組態。 如需健康情況檢查和包含使用者介面的工作範例詳細資訊,請參閱 .NET AspireASP.NET Core HealthChecksUI 範例。
連接遙測
最佳做法是,MailKit 用戶端程式庫會提供遙測功能。 .NET .NET Aspire 可以利用此遙測,然後將其顯示在 .NET.NET Aspire 儀錶板中。 根據是否啟用追蹤和計量,遙測將被設定如下列代碼段所示:
if (settings.DisableTracing is false)
{
builder.Services.AddOpenTelemetry()
.WithTracing(
traceBuilder => traceBuilder.AddSource(
Telemetry.SmtpClient.ActivitySourceName));
}
if (settings.DisableMetrics is false)
{
// Required by MailKit to enable metrics
Telemetry.SmtpClient.Configure();
builder.Services.AddOpenTelemetry()
.WithMetrics(
metricsBuilder => metricsBuilder.AddMeter(
Telemetry.SmtpClient.MeterName));
}
更新電子報服務
建立整合連結庫后,您現在可以更新電子報服務以使用MailKit用戶端。 第一步是添加對 MailKit.Client
項目的引用。 將 MailKit.Client.csproj 專案引用新增到 MailDevResource.NewsletterService
專案:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailKit.Client/MailKit.Client.csproj
接下來,新增 ServiceDefaults
項目的引用:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj
最後一個步驟是使用下列 C# 程式代碼取代 Program.cs 項目中現有的 MailDevResource.NewsletterService
檔案:
using System.Net.Mail;
using MailKit.Client;
using MailKit.Net.Smtp;
using MimeKit;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Add services to the container.
builder.AddMailKitClient("maildev");
var app = builder.Build();
app.MapDefaultEndpoints();
// Configure the HTTP request pipeline.
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.MapPost("/subscribe",
async (MailKitClientFactory factory, string email) =>
{
ISmtpClient client = await factory.GetSmtpClientAsync();
using var message = new MailMessage("newsletter@yourcompany.com", email)
{
Subject = "Welcome to our newsletter!",
Body = "Thank you for subscribing to our newsletter!"
};
await client.SendAsync(MimeMessage.CreateFromMailMessage(message));
});
app.MapPost("/unsubscribe",
async (MailKitClientFactory factory, string email) =>
{
ISmtpClient client = await factory.GetSmtpClientAsync();
using var message = new MailMessage("newsletter@yourcompany.com", email)
{
Subject = "You are unsubscribed from our newsletter!",
Body = "Sorry to see you go. We hope you will come back soon!"
};
await client.SendAsync(MimeMessage.CreateFromMailMessage(message));
});
app.Run();
上述程式代碼中最值得注意的變更如下:
- 已更新的
using
語句,其中包含MailKit.Client
、MailKit.Net.Smtp
和MimeKit
命名空間。 - 使用對
AddMailKitClient
擴充方法的呼叫來取代.NETSmtpClient
的官方註冊。 - 將
/subscribe
和/unsubscribe
的映射呼叫替換為插入MailKitClientFactory
,並使用ISmtpClient
實例傳送電子郵件。
執行範例
既然您已建立 MailKit 用戶端整合並更新電子報服務以使用它,您可以執行範例。 從 IDE 中,選取 F5,或從解決方案的根目錄執行 dotnet run
以啟動應用程式,您應該會看到 .NET.NET Aspire 儀錶板:
應用程式執行之後,流覽至 https://localhost:7251/swagger 的 Swagger UI,並測試 /subscribe
和 /unsubscribe
端點。 選取向下箭號以展開端點:
然後選取 [Try it out
] 按鈕。 輸入電子郵件地址,然後選取 [Execute
] 按鈕。
重複這個數次,以新增多個電子郵件位址。 您應該會看到傳送至 MailDev 收件匣的電子郵件:
多封電子郵件的
選取執行應用程式的終端機視窗中 Ctrl+C,或選取 IDE 中的停止按鈕來停止應用程式。
檢視MailKit遙測
MailKit 用戶端函式庫提供可在 .NET.NET Aspire 儀錶板中檢視的遙測功能。 若要檢視遙測,請瀏覽至位於 .NET的 .NET Aspirehttps://localhost:7251 儀錶板。 選取 newsletter
資源以在 度量 頁面上檢視遙測資料:
再次開啟 Swagger UI,並對 /subscribe
和 /unsubscribe
端點提出一些要求。 然後,流覽回 .NET.NET Aspire 儀錶板,然後選取 newsletter
資源。 選取 mailkit.net.smtp 節點底下的指標,例如 mailkit.net.smtp.client.operation.count
。 您應該會看到有關 MailKit 用戶端的遙測資料:
總結
在本文中,您已瞭解如何建立使用MailKit傳送電子郵件的 .NET.NET Aspire 整合。 您也瞭解如何將此整合整合到您先前建置的電子報應用程式中。 您已瞭解 .NET.NET Aspire 整合的核心原則,例如透過依賴注入向取用者公開基礎客戶端程式庫,以及如何將健康狀況檢查和遙測技術新增至整合。 您也瞭解如何更新電子報服務以使用MailKit用戶端。
開始建立您自己的 .NET.NET Aspire 整合。 如果您認為您在建置的整合中有足夠的社群價值,請考慮將它發佈為 NuGet 套件, 供其他人使用。 此外,請考慮將提取要求提交至 .NET AspireGitHub 存放庫,以考慮包含在官方 .NET.NET Aspire 整合中。