Vytvoření vlastních integrací klientů .NET.NET Aspire
Tento článek je pokračováním článku Vytvořte vlastní .NET.NET Aspire hostingových integrací. Provede vás vytvořením .NET.NET Aspire klientské integrace, která používá MailKit k odesílání e-mailů. Tato integrace se pak přidá do aplikace Bulletin, kterou jste vytvořili dříve. Předchozí příklad vynechal vytvoření integrace klienta a místo toho se spoléhal na existující .NETSmtpClient
. Nejlepší je používat SmtpClient
MailKitu přes oficiální .NETSmtpClient
pro odesílání e-mailů, protože je modernější a podporuje více funkcí a protokolů. Další informace naleznete v tématu .NET SmtpClient: Poznámky.
Požadavky
Pokud jste postupovali podle pokynů v článku o vytvoření vlastní integrace .NET.NET Aspire hostingu, měli byste mít aplikaci Newsletter.
Spropitné
Tento článek je inspirovaný stávajícími integracemi .NET.NET Aspire a na základě oficiálních pokynů týmu. Existují místa, kde se zmíněné pokyny liší a je důležité pochopit důvody rozdílů. Další informace najdete v tématu .NET.NET Aspire požadavky na integraci.
Vytvoření knihovny pro integraci
.NET .NET Aspire integrace se doručují jako balíčky NuGet, ale v tomto příkladu je zveřejnění balíčku NuGet nad rámec tohoto článku. Místo toho vytvoříte projekt knihovny tříd, který obsahuje integraci, a odkazujete na něj jako na projekt. .NET .NET Aspire integrační balíčky jsou určeny k zabalení klientské knihovny, jako je MailKit, a zajištění telemetrie připravené pro produkční prostředí, kontroly stavu, konfigurovatelnost a testovatelnost. Začněme vytvořením nového projektu knihovny tříd.
Vytvořte nový projekt knihovny tříd s názvem
MailKit.Client
ve stejném adresáři jako MailDevResource.sln z předchozího článku.dotnet new classlib -o MailKit.Client
Přidejte projekt do řešení.
dotnet sln ./MailDevResource.sln add MailKit.Client/MailKit.Client.csproj
Dalším krokem je přidání všech balíčků NuGet, na které integrace spoléhá. Místo toho, abyste jednotlivé balíčky přidávali z příkazového řádku
<ItemGroup>
<PackageReference Include="MailKit" Version="4.10.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Resilience" Version="9.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.1" />
</ItemGroup>
Definování nastavení integrace
Pokaždé, když vytváříte integraci .NET.NET Aspire, je nejlepší pochopit klientskou knihovnu, na kterou mapujete. Pomocí Sady MailKit musíte pochopit nastavení konfigurace, která jsou potřebná pro připojení k serveru SMTP (Simple Mail Transfer Protocol). Je také důležité pochopit, jestli knihovna podporuje kontroly stavu , trasování a metriky . MailKit podporuje trasování a metrikprostřednictvím třídy Telemetry.SmtpClient
. Při přidávání kontroly stavubyste měli použít všechny zavedené nebo existující kontroly stavu, pokud je to možné. Jinak můžete zvážit implementovat vlastní řešení v rámci integrace. Do projektu MailKit.Client
do souboru s názvem MailKitClientSettings.cspřidejte následující kód:
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;
}
}
}
Předchozí kód definuje třídu MailKitClientSettings
pomocí:
-
Endpoint
vlastnost, která představuje připojovací řetězec k serveru SMTP. -
DisableHealthChecks
vlastnost, která určuje, zda jsou povoleny zdravotní kontroly. -
DisableTracing
vlastnost, která určuje, zda je povoleno trasování. -
DisableMetrics
vlastnost, která určuje, jestli jsou metriky povolené.
Analýza logiky připojovacího řetězce
Třída nastavení také obsahuje ParseConnectionString
metodu, která parsuje připojovací řetězec do platného Uri
. Očekává se, že konfigurace bude poskytována v následujícím formátu:
-
ConnectionStrings:<connectionName>
: Připojovací řetězec k serveru SMTP. -
MailKit:Client:ConnectionString
: Připojovací řetězec k serveru SMTP.
Pokud není k dispozici žádná z těchto hodnot, vyvolá se výjimka.
Zveřejnění funkcí klienta
Cílem integrace .NET.NET Aspire je zveřejnit základní klientskou knihovnu příjemcům prostřednictvím injektáže závislostí. S MailKitem a v tomto příkladu je třída SmtpClient
to, co chcete zveřejnit. Nezaobalujete žádnou funkcionalitu, ale namapujete nastavení konfigurace na třídu SmtpClient
. Běžné je zpřístupnit standardní i klíčové registrace služeb pro integrace. Standardní registrace se používají v případě, že existuje pouze jedna instance služby, a registrace klíč-služba se používají, když existuje více instancí služby. Někdy se stává, že pokud chcete dosáhnout více registrací stejného typu, použijete vzor továrny. Do projektu MailKit.Client
do souboru s názvem MailKitClientFactory.cspřidejte následující kód:
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();
}
}
Třída MailKitClientFactory
je továrna, která vytvoří instanci ISmtpClient
na základě konfiguračních nastavení. Zodpovídá za vrácení implementace ISmtpClient
, která má aktivní připojení k nakonfigurovanému serveru SMTP. Dále je potřeba zpřístupnit funkcionalitu, aby bylo možné tuto továrnu zaregistrovat v kontejneru pro injektování závislostí. Do projektu MailKit.Client
do souboru s názvem MailKitExtensions.cspřidejte následující kód:
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));
}
}
}
Předchozí kód přidá dvě rozšiřující metody na typ IHostApplicationBuilder
, jednu pro standardní registraci MailKitu a druhou pro klíčovou registraci MailKitu.
Spropitné
Rozšiřující metody pro integrace .NET.NET Aspire by měly rozšířit typ IHostApplicationBuilder
a dodržovat pravidlo pojmenování Add<MeaningfulName>
, kde <MeaningfulName>
je typ nebo funkce, které přidáváte. V tomto článku se metoda rozšíření AddMailKitClient
používá k přidání klienta MailKit. Pravděpodobněji je v souladu s oficiálními pokyny k používání AddMailKitSmtpClient
místo AddMailKitClient
, protože to zaregistruje jenom SmtpClient
a ne celou knihovnu MailKit.
Obě rozšíření nakonec spoléhají na privátní metodu AddMailKitClient
pro registraci MailKitClientFactory
v kontejneru pro injektování závislostí jako vymezovaná služba. Důvodem registrace MailKitClientFactory
jako služby s vymezeným oborem je to, že operace připojení jsou považovány za nákladné a pokud je to možné, měly by se znovu použít ve stejném rozsahu. Jinými slovy, pro jeden požadavek by se měla použít stejná instance ISmtpClient
. Továrna drží vytvořenou instanci SmtpClient
a nakládá s ní.
Vazba konfigurace
Jednou z prvních věcí, kterou provádí privátní implementace AddMailKitClient
metod, je svázat nastavení konfigurace s MailKitClientSettings
třídy. Třída nastavení je instanciována a poté je zavolán Bind
s konkrétní částí konfigurace. Potom se vyvolá volitelný configureSettings
delegát s aktuálními nastaveními. To umožňuje spotřebiteli dále nakonfigurovat nastavení a zajistí, že nastavení ručního kódu bude upřednostněno před nastavením konfigurace. Potom, v závislosti na tom, zda byla zadána hodnota serviceKey
, by měla být MailKitClientFactory
zaregistrována v kontejneru pro injektování závislostí jako standardní nebo klíčovaná služba.
Důležitý
Je úmyslné, že při registraci služeb se volá přetížení implementationFactory
. Metoda CreateMailKitClientFactory
vyvolá, když je konfigurace neplatná. Tím se zajistí, že se vytvoření MailKitClientFactory
odloží, dokud nebude potřeba, a zabrání tomu, aby aplikace před zpřístupněním protokolování vyvolala chybu.
Registrace kontrol stavu a telemetrie jsou podrobněji popsány v následujících částech.
Přidejte kontrolu stavu
Kontroly zdravotního stavu jsou způsobem, jak sledovat zdraví integrace. Pomocí MailKitu můžete zkontrolovat, jestli je připojení k serveru SMTP v pořádku. Do projektu MailKit.Client
do souboru s názvem MailKitHealthCheck.cspřidejte následující kód:
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);
}
}
}
Předchozí implementace kontroly zdravotního stavu:
- Implementuje rozhraní
IHealthCheck
. - Přijímá
MailKitClientFactory
jako parametr primárního konstruktoru. - Splňuje metodu
CheckHealthAsync
pomocí:- Pokus o získání instance
ISmtpClient
zfactory
. Pokud je úspěšné, vrátíHealthCheckResult.Healthy
. - Pokud je vyvolána výjimka, vrátí
HealthCheckResult.Unhealthy
.
- Pokus o získání instance
Jak bylo dříve sdíleno v registraci MailKitClientFactory
, MailKitHealthCheck
je podmíněně registrován v IHeathChecksBuilder
:
if (settings.DisableHealthChecks is false)
{
builder.Services.AddHealthChecks()
.AddCheck<MailKitHealthCheck>(
name: serviceKey is null ? "MailKit" : $"MailKit_{connectionName}",
failureStatus: default,
tags: []);
}
Příjemce by se mohl rozhodnout vynechat kontroly stavu nastavením vlastnosti DisableHealthChecks
na true
v konfiguraci. Běžným vzorem integrace je mít volitelné funkce a integrace .NET.NET Aspire důrazně podporují tyto typy konfigurací. Další informace o kontrolách stavu a funkční ukázce, která obsahuje uživatelské rozhraní, najdete v .NET AspireASP.NET Core ukázky HealthChecksUI.
Připojení telemetrie
Osvědčeným postupem je, že klientská knihovna MailKit zveřejňuje telemetrii. .NET .NET Aspire může využít tuto telemetrii a zobrazit ji na řídicím panelu .NET.NET Aspire. V závislosti na tom, jestli je povolené trasování a metriky, je telemetrie připojená, jak je znázorněno v následujícím fragmentu kódu:
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));
}
Aktualizace služby Bulletin
S vytvořenou knihovnou integrace teď můžete aktualizovat službu Bulletin tak, aby používala klienta MailKit. Prvním krokem je přidání odkazu na projekt MailKit.Client
. Přidejte odkaz na projekt MailKit.Client.csproj do projektu MailDevResource.NewsletterService
:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailKit.Client/MailKit.Client.csproj
Dále přidejte odkaz na projekt ServiceDefaults
:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj
Posledním krokem je nahrazení existujícího souboru Program.cs v projektu MailDevResource.NewsletterService
následujícím kódem jazyka C#:
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();
Nejdůležitějšími změnami v předchozím kódu jsou:
- Aktualizované
using
příkazy, které zahrnují obory názvůMailKit.Client
,MailKit.Net.Smtp
aMimeKit
. - Nahrazení registrace oficiálního .NET
SmtpClient
voláním rozšiřující metodyAddMailKitClient
. - Nahrazení map volání u
/subscribe
i/unsubscribe
za vloženíMailKitClientFactory
a použití instanceISmtpClient
k odeslání e-mailu.
Spusťte ukázku
Teď, když jste vytvořili integraci klienta MailKit a aktualizovali jste službu Bulletin tak, aby ji používala, můžete ukázku spustit. V integrovaném vývojovém prostředí vyberte F5 nebo spusťte dotnet run
z kořenového adresáře řešení pro spuštění aplikace—měli byste vidět ovládací panel .NET.NET Aspire:
Po spuštění aplikace přejděte do uživatelského rozhraní Swagger v https://localhost:7251/swagger a otestujte koncové body /subscribe
a /unsubscribe
. Výběrem šipky dolů rozbalte koncový bod:
Pak vyberte tlačítko Try it out
. Zadejte e-mailovou adresu a pak vyberte tlačítko Execute
.
Tento postup opakujte několikrát, pokud chcete přidat více e-mailových adres. Měli byste vidět e-mail odeslaný do schránky MailDev.
Ukončete aplikaci tak, že v okně terminálu, kde je aplikace spuštěná, vyberete Ctrl+C nebo v integrovaném vývojovém prostředí vyberete tlačítko zarážky.
Zobrazení telemetrie MailKitu
Klientská knihovna MailKit zveřejňuje telemetrii, kterou lze zobrazit na řídicím panelu .NET.NET Aspire. Pokud chcete zobrazit telemetrii, přejděte na řídicí panel .NET.NET Aspire na https://localhost:7251. Výběrem prostředku newsletter
zobrazte telemetrii na stránce Metriky:
Znovu otevřete uživatelské rozhraní Swagger a odešlete požadavky na koncové body /subscribe
a /unsubscribe
. Pak přejděte zpět na řídicí panel .NET.NET Aspire a vyberte prostředek newsletter
. Vyberte metriku v uzlu mailkit.net.smtp, například mailkit.net.smtp.client.operation.count
. Měla by se zobrazit telemetrie klienta MailKit:
Shrnutí
V tomto článku jste se dozvěděli, jak vytvořit integraci .NET.NET Aspire, která k odesílání e-mailů používá Sadu MailKit. Dozvěděli jste se také, jak tuto integraci integrovat do aplikace Bulletin, kterou jste vytvořili dříve. Dozvěděli jste se o základních principech integrace .NET.NET Aspire, jako je zveřejnění základní klientské knihovny příjemcům prostřednictvím injektáže závislostí a přidání kontrol stavu a telemetrie do integrace. Také jste se dozvěděli, jak aktualizovat službu Bulletin tak, aby používala klienta MailKit.
Pokračujte a sestavte si vlastní integrace .NET.NET Aspire. Pokud se domníváte, že integrace, kterou vytváříte, má dostatek hodnot komunity, zvažte její publikování jako balíček NuGet , aby ho mohli používat ostatní. Zvažte rovněž podání žádosti o přijetí změn do úložiště .NET AspireGitHub, aby bylo zváženo pro zařazení do oficiálních integrací .NET.NET Aspire.