Создать пользовательские интеграции .NET Aspireclient
Эта статья является продолжением статьи Создание пользовательских .NET.NET Aspire интеграций хостинга. Он поможет вам создать интеграцию .NET Aspireclient, которая использует MailKit для отправки сообщений электронной почты. Затем эта интеграция добавляется в приложение бюллетеня, которое вы ранее создали. В предыдущем примере опущено создание интеграции client и вместо этого использовался существующий .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 Aspire предназначены для упаковки библиотеки client, например 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.9.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.10.0" />
</ItemGroup>
Определение параметров интеграции
Каждый раз, когда вы создаете интеграцию .NET Aspire, в первую очередь важно понять библиотеку client, к которой вы сопоставляете. С помощью MailKit необходимо понять параметры конфигурации, необходимые для подключения к простому протоколу передачи почты (SMTP) server. Но также важно понимать, поддерживает ли библиотека поддержку проверок работоспособности, трассировки и метрик. MailKit поддерживает трассировки 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 server. -
DisableHealthChecks
свойство, определяющее, включены ли проверки состояния. -
DisableTracing
свойство, определяющее, включена ли трассировка. -
DisableMetrics
свойство, определяющее, включены ли метрики.
Логика разбора строки подключения
Класс параметров также содержит метод ParseConnectionString
, который анализирует строку подключения в валидный Uri
. Ожидается, что конфигурация будет предоставлена в следующем формате:
-
ConnectionStrings:<connectionName>
: строка подключения к SMTP-server. -
MailKit:Client:ConnectionString
: строка подключения к SMTP-server.
Если ни ни из этих значений не указано, создается исключение.
Раскрыть функциональность client
Целью интеграции .NET Aspire является предоставление базовой библиотеки client потребителям через внедрение зависимостей. С помощью 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 server. Затем необходимо предоставить функциональность для потребителей, чтобы зарегистрировать эту фабрику в контейнере внедрения зависимостей. Добавьте следующий код в проект 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 client. Скорее всего, использование AddMailKitSmtpClient
вместо AddMailKitClient
будет больше соответствовать официальным рекомендациям, поскольку это регистрирует только SmtpClient
, а не всю библиотеку MailKit.
В итоге оба расширения полагаются на частный метод AddMailKitClient
, чтобы зарегистрировать MailKitClientFactory
в контейнере внедрения зависимостей как службу с областью . Причина регистрации MailKitClientFactory
в качестве ограниченной службы заключается в том, что операции подключения считаются дорогостоящими и следует повторно использовать в той же области, где это возможно. Другими словами, для одного запроса следует использовать тот же ISmtpClient
экземпляр. Фабрика удерживает экземпляр SmtpClient
, который она создает, и удаляет его.
Привязка конфигурации
Одной из первых вещей, которые выполняет частная реализация методов AddMailKitClient
, является привязка параметров конфигурации к классу MailKitClientSettings
. Класс параметров создаётся, и затем функция Bind
вызывается с определённым разделом конфигурации. Затем вызывается необязательный configureSettings
делегат с текущими параметрами. Это позволяет потребителю дополнительно настроить параметры, гарантируя, что параметры кода вручную имеют приоритет над параметрами конфигурации. После этого в зависимости от того, было ли предоставлено значение serviceKey
, MailKitClientFactory
следует зарегистрировать в контейнере внедрения зависимостей в качестве стандартной или ключевой службы.
Важный
Специально предусмотрено, что при регистрации служб вызывается перегрузка implementationFactory
. Метод CreateMailKitClientFactory
вызывается, если конфигурация недопустима. Это гарантирует, что создание MailKitClientFactory
откладывается до тех пор, пока в нем не возникнет необходимость, и предотвращает сбой приложения до того, как ведение журнала станет доступным.
Регистрация проверок работоспособности, а также телеметрии, более подробно описана в следующих разделах.
Добавить проверки работоспособности
проверки состояния здоровья — это способ мониторинга состояния здоровья интеграций. С помощью MailKit можно проверить работоспособность подключения к SMTP-server. Добавьте следующий код в проект 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 client предоставляет данные телеметрии. .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 client. Первым шагом является добавление ссылки на проект 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
для отправки электронной почты.
Выполнить пример
Теперь, когда вы создали интеграцию MailKit client и обновили службу бюллетеня, чтобы использовать ее, можно запустить пример. В интегрированной среде разработки выберите F5 или запустите dotnet run
из корневого каталога решения, чтобы запустить приложение — вы должны увидеть панель мониторинга .NET.NET Aspire.
После запуска приложения перейдите к интерфейсу Swagger по адресу https://localhost:7251/swagger и протестируйте конечные точки /subscribe
и /unsubscribe
. Щелкните стрелку вниз, чтобы развернуть конечную точку:
Затем нажмите кнопку Try it out
. Введите адрес электронной почты и нажмите кнопку Execute
.
Повторите это несколько раз, чтобы добавить несколько адресов электронной почты. Вы увидите сообщение электронной почты, отправленное в папку "Входящие" MailDev:
Остановите приложение, выбрав CTRL+C в окне терминала, где запущено приложение, или нажав кнопку остановки в интегрированной среде разработки.
Просмотр телеметрии MailKit
Библиотека MailKit client предоставляет данные телеметрии, которые можно просмотреть на панели мониторинга .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 client:
Сводка
В этой статье вы узнали, как создать интеграцию .NET.NET Aspire, которая использует MailKit для отправки сообщений электронной почты. Вы также узнали, как интегрировать эту интеграцию с приложением бюллетеня, которое вы ранее создали. Вы узнали об основных принципах интеграции .NET Aspire, таких как предоставление базовой библиотеки client клиентам с помощью внедрения зависимостей и добавление проверок состояния и телеметрии в рамках интеграции. Вы также узнали, как обновить службу рассылки для использования MailKit client.
Идите вперед и создайте собственные интеграции .NET.NET Aspire. Если вы считаете, что в построенной интеграции достаточно ценности сообщества, рассмотрите возможность публикации его в качестве пакета NuGet, для других пользователей. Кроме того, рассмотрите возможность отправить пулл-реквест в репозиторий .NET AspireGitHub для рассмотрения и включения в официальные интеграции .NET.NET Aspire.
Дальнейшие действия
.NET Aspire