Создание пользовательских интеграций клиентов .NET.NET Aspire
Эта статья является продолжением статьи Создание пользовательских .NET.NET Aspire интеграций хостинга. Он поможет вам создать интеграцию клиента .NET.NET Aspire, которая использует MailKit для отправки сообщений электронной почты. Затем эта интеграция добавляется в приложение бюллетеня, которое вы ранее создали. В предыдущем примере было опущено создание клиентской интеграции, и вместо этого положились на существующий .NETSmtpClient
. Лучше всего использовать SmtpClient
MailKit по сравнению с официальным .NETSmtpClient
для отправки сообщений электронной почты, так как это более современно и поддерживает больше функций и протоколов. Дополнительные сведения см. в статье .NET SmtpClient: примечания.
Необходимые условия
Если вы следуете инструкциям, то у вас должно быть приложение для рассылки, полученное в результате выполнения шагов, описанных в статье Create custom .NET.NET Aspire hosting integration.
Совет
Эта статья вдохновлена существующими интеграциями .NET.NET Aspire и основана на официальном руководстве команды. Есть места, где указанные рекомендации различаются, и важно понять причину различий. Дополнительные сведения см. в .NET.NET Aspire требованиях к интеграции.
Создание библиотеки для интеграции
.NET .NET Aspire интеграции предоставляются в виде пакетов NuGet, но в этом примере публикация пакета NuGet выходит за рамки обсуждаемой в статье темы. Вместо этого создается проект библиотеки классов, содержащий интеграцию и ссылающийся на него как проект. .NET .NET Aspire пакеты интеграции предназначены для упаковки клиентской библиотеки, такой как MailKit, и предоставления готовых к работе телеметрии, проверок работоспособности, настраиваемости и возможности тестирования. Начнем с создания проекта библиотеки классов.
Создайте проект библиотеки классов с именем
MailKit.Client
в том же каталоге, что и MailDevResource.sln из предыдущей статьи.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 поддерживает прослеживание и метрики с помощью своего Telemetry.SmtpClient
класса. При добавлении проверки работоспособностиследует по возможности использовать любые установленные или существующие проверки работоспособности. В противном случае можно рассмотреть возможность разработки собственного решения для интеграции. Добавьте следующий код в проект MailKit.Client
в файл с именем MailKitClientSettings.cs:
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
свойство, определяющее, включены ли метрики.
Логика разбора строки подключения
Класс параметров также содержит метод ParseConnectionString
, который анализирует строку подключения в валидный Uri
. Ожидается, что конфигурация будет предоставлена в следующем формате:
-
ConnectionStrings:<connectionName>
: строка подключения к SMTP-серверу. -
MailKit:Client:ConnectionString
: строка подключения к SMTP-серверу.
Если ни ни из этих значений не указано, создается исключение.
Предоставление функциональных возможностей клиента
Целью интеграции .NET.NET Aspire является предоставление базовой клиентской библиотеки потребителям путем внедрения зависимостей. С помощью MailKit и для этого примера класс SmtpClient
— это тот, который вы хотите сделать доступным. Вы не оборачиваете какую-либо функциональность, а сопоставляете настройки конфигурации с классом SmtpClient
. Обычно для интеграции можно предоставлять как стандартные, так и ключевые регистрации служб. Стандартные регистрации используются при наличии только одного экземпляра службы, а регистрация служб с ключами используется при наличии нескольких экземпляров службы. Иногда для получения нескольких регистраций одного типа используют шаблон фабрики. Добавьте следующий код в проект MailKit.Client
в файл с именем MailKitClientFactory.cs:
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
на основе параметров конфигурации. Он отвечает за возврат реализации ISmtpClient
, которая имеет активное подключение к настроенному SMTP-серверу. Затем необходимо предоставить возможность потребителям зарегистрировать эту фабрику в контейнере внедрения зависимостей. Добавьте следующий код в проект MailKit.Client
в файл с именем MailKitExtensions.cs:
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
в файл с именем MailKitHealthCheck.cs:
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
. Добавьте ссылку на проект MailKitClient.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
Последний шаг — заменить существующий файл Program.cs в проекте MailDevResource.NewsletterService
следующим кодом 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();
Наиболее заметными изменениями в предыдущем коде являются:
- Обновленные инструкции
using
, которые включают пространства именMailKit.Client
,MailKit.Net.Smtp
иMimeKit
. - Замена регистрации для официального .NET
SmtpClient
вызовом метода расширенияAddMailKitClient
. - Замена вызовов для карт
/subscribe
и/unsubscribe
на внедрениеMailKitClientFactory
и использование экземпляраISmtpClient
для отправки email.
Выполнить пример
Теперь, когда вы создали клиентскую интеграцию MailKit и обновили службу бюллетеня, чтобы использовать ее, можно запустить пример. В интегрированной среде разработки выберите F5 или запустите dotnet run
из корневого каталога решения, чтобы запустить приложение — вы должны увидеть панель мониторинга .NET.NET Aspire.
После запуска приложения перейдите к интерфейсу Swagger по адресу https://localhost:7251/swagger и протестируйте конечные точки /subscribe
и /unsubscribe
. Щелкните стрелку вниз, чтобы развернуть конечную точку:
Затем нажмите кнопку Try it out
. Введите адрес электронной почты и нажмите кнопку Execute
.
Повторите это несколько раз, чтобы добавить несколько адресов электронной почты. Вы увидите сообщение электронной почты, отправленное в папку "Входящие" MailDev:
Остановите приложение, выбрав CTRL+C в окне терминала, где запущено приложение, или нажав кнопку остановки в интегрированной среде разработки.
Просмотр телеметрии MailKit
Клиентская библиотека MailKit предоставляет данные телеметрии, которые можно просмотреть на панели мониторинга .NET.NET Aspire. Чтобы просмотреть данные телеметрии, перейдите на панель мониторинга .NET.NET Aspirehttps://localhost:7251. Выберите ресурс newsletter
, чтобы просмотреть данные телеметрии на странице метрик .
панель мониторинга: телеметрия MailKit.
Снова откройте пользовательский интерфейс Swagger и выполните некоторые запросы к конечным точкам /subscribe
и /unsubscribe
. Затем вернитесь к панели мониторинга .NET.NET Aspire и выберите ресурс newsletter
. Выберите метрику в узле mailkit.net.smtp, например mailkit.net.smtp.client.operation.count
. Вы должны увидеть данные телеметрии для клиента MailKit:
Сводка
В этой статье вы узнали, как создать интеграцию .NET.NET Aspire, которая использует MailKit для отправки сообщений электронной почты. Вы также узнали, как интегрировать эту интеграцию с приложением бюллетеня, которое вы ранее создали. Вы узнали о основных принципах интеграций .NETи.NET Aspire, например, обеспечение доступа к базовой клиентской библиотеке потребителям путем инъекции зависимостей, а также добавление мониторинга состояния и сбора данных телеметрии в интеграцию. Вы также узнали, как обновить службу бюллетеня для использования клиента MailKit.
Идите вперед и создайте собственные интеграции .NET.NET Aspire. Если вы считаете, что в построенной интеграции достаточно ценности сообщества, рассмотрите возможность публикации его в качестве пакета NuGet, для других пользователей. Кроме того, подумайте о том, чтобы отправить пулл-реквест в репозиторий .NET AspireGitHub, чтобы его могли рассмотреть для включения в официальные интеграции .NET.NET Aspire.
Дальнейшие действия
.NET Aspire