Creación de integraciones de .NET Aspireclient personalizadas
Este artículo es una continuación del artículo Creación de integraciones de hospedaje personalizadas de .NET.NET Aspire. Le guía a través de la creación de una integración de .NET Aspireclient que usa MailKit para enviar emails. A continuación, esta integración se agrega a la aplicación Newsletter que creó anteriormente. En el ejemplo anterior se omitió la creación de una integración de client y, en su lugar, se basaba en el .NETSmtpClient
existente. Es mejor usar el SmtpClient
de MailKit sobre el .NETSmtpClient
oficial para enviar correos electrónicos, ya que es más moderno y admite más características o protocolos. Para obtener más información, vea .NET SmtpClient: Comentarios.
Prerrequisitos
Si estás siguiendo, deberías tener una aplicación Newsletter de los pasos descritos en el artículo Creación de .NET.NET Aspire hospedaje integrado personalizado.
Propina
Este artículo está inspirado en las integraciones de .NET.NET Aspire existentes y en función de las instrucciones oficiales del equipo. Hay lugares en los que dicha guía varía y es importante comprender el razonamiento detrás de las diferencias. Para obtener más información, consulte .NET.NET Aspire requisitos de integración.
Creación de una biblioteca para la integración
.NET .NET Aspire integraciones se entregan como paquetes NuGet, pero en este ejemplo, publicar un paquete NuGet escapa al alcance de este artículo. En su lugar, se crea un proyecto de biblioteca de clases que contiene la integración y se hace referencia a él como un proyecto. .NET Aspire paquetes de integración están diseñados para encapsular una biblioteca de client, como MailKit, y proporcionar telemetría preparada para producción, verificaciones de estado, configurabilidad y testabilidad. Comencemos creando un nuevo proyecto de biblioteca de clases.
Cree un nuevo proyecto de biblioteca de clases denominado
MailKit.Client
en el mismo directorio que el MailDevResource.sln del artículo anterior.dotnet new classlib -o MailKit.Client
Agregue el proyecto a la solución.
dotnet sln ./MailDevResource.sln add MailKit.Client/MailKit.Client.csproj
El siguiente paso consiste en agregar todos los paquetes NuGet en los que se basa la integración. En lugar de agregar cada paquete uno a uno desde la CLI de .NET, es probable que sea más fácil copiar y pegar el siguiente XML en el MailKit.Clientarchivo de .csproj.
<ItemGroup>
<PackageReference Include="MailKit" Version="4.9.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Resilience" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
</ItemGroup>
Definición de la configuración de integración
Cuando crea una integración de .NET Aspire, es mejor comprender la biblioteca de client a la que está mapeando. Con MailKit, debe comprender las opciones de configuración necesarias para conectarse a un protocolo simple de transferencia de correo (SMTP) server. Pero también es importante comprender si la biblioteca admite comprobaciones de estado , seguimiento , y métricas . MailKit admite trazas y métricas , a través de su clase Telemetry.SmtpClient
. Al agregar comprobaciones de estado, debe usar las comprobaciones de estado establecidas o existentes siempre que sea posible. De lo contrario, podrías considerar implementar la tuya en la integración. Agregue el código siguiente al proyecto de MailKit.Client
en un archivo denominado 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;
}
}
}
El código anterior define la clase MailKitClientSettings
con:
-
Endpoint
propiedad que representa la cadena de conexión al serverSMTP. -
DisableHealthChecks
propiedad que determina si las verificaciones de salud están habilitadas. -
DisableTracing
propiedad que determina si el seguimiento está habilitado. -
DisableMetrics
propiedad que determina si las métricas están habilitadas.
Análisis de la lógica de la cadena de conexión
La clase settings también contiene un método ParseConnectionString
que analiza la cadena de conexión en un Uri
válido. Se espera que la configuración se proporcione en el formato siguiente:
-
ConnectionStrings:<connectionName>
: la cadena de conexión al SMTP server. -
MailKit:Client:ConnectionString
: la cadena de conexión al servidor serverSMTP.
Si no se proporciona ninguno de estos valores, se produce una excepción.
Exponer client funcionalidad
El objetivo de las integraciones de .NET Aspire es exponer la biblioteca subyacente de client a los usuarios a través de la inyección de dependencias. Con MailKit y para este ejemplo, la clase SmtpClient
es lo que desea exponer. No estás envolviendo ninguna funcionalidad, sino que estás asignando configuraciones a una clase SmtpClient
. Es habitual exponer registros estándar y de servicio con claves para integraciones. Los registros estándar se usan cuando solo hay una instancia de un servicio y los registros de keyed-service se usan cuando hay varias instancias de un servicio. A veces, para lograr varios registros del mismo tipo, use un patrón de fábrica. Agregue el código siguiente al proyecto de MailKit.Client
en un archivo denominado 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();
}
}
La clase MailKitClientFactory
es un generador que crea una instancia de ISmtpClient
en función de los valores de configuración. Es responsable de devolver una implementación de ISmtpClient
que tiene una conexión activa a un serverSMTP configurado. A continuación, debe poner a disposición la funcionalidad para que los consumidores registren esta fábrica en el contenedor de inyección de dependencias. Agregue el código siguiente al proyecto de MailKit.Client
en un archivo denominado 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));
}
}
}
El código anterior agrega dos métodos de extensión en el tipo IHostApplicationBuilder
, uno para el registro estándar de MailKit y otro para el registro con clave de MailKit.
Propina
Los métodos de extensión para las integraciones de .NET.NET Aspire deben extender el tipo de IHostApplicationBuilder
y seguir la convención de nomenclatura de Add<MeaningfulName>
donde el <MeaningfulName>
es el tipo o la funcionalidad que se está agregando. En este artículo, el método de extensión AddMailKitClient
se usa para agregar MailKit client. Es probable que esté más en línea con las instrucciones oficiales para usar AddMailKitSmtpClient
en lugar de AddMailKitClient
, ya que esto solo registra el SmtpClient
y no toda la biblioteca mailKit.
En última instancia, ambas extensiones se basan en el método MailKitClientFactory
como servicio con ámbito es porque las operaciones de conexión se consideran costosas y deben reutilizarse dentro del mismo ámbito siempre que sea posible. En otras palabras, para una sola solicitud, se debe usar la misma instancia de ISmtpClient
. La fábrica retiene la instancia de la SmtpClient
que crea y se deshace de ella.
Vinculación de configuración
Una de las primeras cosas que hace la implementación privada de los métodos de AddMailKitClient
es enlazar las opciones de configuración a la clase MailKitClientSettings
. Se crea una instancia de la clase settings y, a continuación, se llama a Bind
con la sección específica de configuración. A continuación, se invoca el delegado configureSettings
opcional con la configuración actual. Esto permite al consumidor configurar aún más las opciones, asegurándose de que se respetan las opciones de código manual sobre las opciones de configuración. Después, dependiendo de si se proporcionó el valor serviceKey
, el MailKitClientFactory
debe registrarse con el contenedor de inserción de dependencias como un servicio estándar o con claves.
Importante
Es intencional que la sobrecarga implementationFactory
se llame al registrar servicios. El método CreateMailKitClientFactory
lanza una excepción cuando la configuración no es válida. Esto garantiza que la creación del MailKitClientFactory
se aplaza hasta que sea necesaria y evita que la aplicación produzca errores antes de que el registro esté disponible.
El registro de comprobaciones de estado y la telemetría se describen con más detalle en las secciones siguientes.
Añade comprobaciones de salud
Las verificaciones de salud son una manera de supervisar la salud de una integración. Con MailKit, puede comprobar si la conexión con el server SMTP es correcta. Agregue el código siguiente al proyecto de MailKit.Client
en un archivo denominado 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);
}
}
}
La implementación anterior de la verificación del estado de salud:
- Implementa la interfaz
IHealthCheck
. - Acepta el
MailKitClientFactory
como parámetro de constructor principal. - Satisface el método
CheckHealthAsync
mediante:- Intentando obtener una instancia de
ISmtpClient
de lafactory
. Si se ejecuta correctamente, devuelveHealthCheckResult.Healthy
. - Si se produce una excepción, devuelve
HealthCheckResult.Unhealthy
.
- Intentando obtener una instancia de
Como se ha compartido anteriormente en el registro de la MailKitClientFactory
, el MailKitHealthCheck
se registra condicionalmente con el IHeathChecksBuilder
:
if (settings.DisableHealthChecks is false)
{
builder.Services.AddHealthChecks()
.AddCheck<MailKitHealthCheck>(
name: serviceKey is null ? "MailKit" : $"MailKit_{connectionName}",
failureStatus: default,
tags: []);
}
El consumidor podría optar por omitir las comprobaciones de estado estableciendo la propiedad DisableHealthChecks
en true
en la configuración. Un patrón común para las integraciones es tener características opcionales y las integraciones .NET.NET Aspire fomentan enérgicamente este tipo de configuraciones. Para obtener más información sobre las comprobaciones de estado y un ejemplo de trabajo que incluye una interfaz de usuario, consulte .NET AspireASP.NET Core ejemplo healthChecksUI.
Configurar la telemetría
Como procedimiento recomendado, la biblioteca mailKit de client expone datos de telemetría. .NET .NET Aspire puede aprovechar esta telemetría y mostrarla en el panel de .NET.NET Aspire. En función de si el seguimiento y las métricas están habilitados, la telemetría se conecta como se muestra en el siguiente fragmento de código:
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));
}
Actualizar el servicio newsletter
Con la biblioteca de integración creada, ahora puede actualizar el servicio Newsletter para usar mailKit client. El primer paso es agregar una referencia al proyecto de MailKit.Client
. Agregue el MailKit.Clientreferencia de proyecto de .csproj al proyecto de MailDevResource.NewsletterService
:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailKit.Client/MailKit.Client.csproj
A continuación, agregue una referencia al proyecto de ServiceDefaults
:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj
El último paso consiste en reemplazar el archivo de Program.cs existente en el proyecto de MailDevResource.NewsletterService
por el siguiente código de 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();
Los cambios más importantes en el código anterior son:
- Las instrucciones
using
actualizadas que incluyen los espacios de nombresMailKit.Client
,MailKit.Net.Smtp
yMimeKit
. - Reemplazo del registro del .NET
SmtpClient
oficial por la llamada al método de extensiónAddMailKitClient
. - El reemplazo de
/subscribe
y/unsubscribe
llamadas de publicación de mapa para insertar en su lugar elMailKitClientFactory
y usar la instancia deISmtpClient
para enviar el correo electrónico.
Ejecución del ejemplo
Ahora que ha creado la integración de MailKit client y ha actualizado el servicio Newsletter para usarlo, puede ejecutar el ejemplo. En el IDE, seleccione F5 o ejecute dotnet run
desde el directorio raíz de la solución para iniciar la aplicación; debería ver el panel de .NET.NET Aspire:
Una vez que se ejecuta la aplicación, vaya a la interfaz de usuario de Swagger en https://localhost:7251/swagger y pruebe los puntos de conexión de /subscribe
y /unsubscribe
. Seleccione la flecha hacia abajo para expandir el punto de conexión:
A continuación, seleccione el botón Try it out
. Escriba una dirección de correo electrónico y, a continuación, seleccione el botón Execute
.
Repita esto varias veces para agregar varias direcciones de correo electrónico. Debería ver el correo electrónico enviado a la bandeja de entrada de MailDev:
Detenga la aplicación seleccionando Ctrl+C en la ventana de terminal donde se ejecuta la aplicación o seleccionando el botón detener en el IDE.
Visualización de la telemetría de MailKit
La biblioteca MailKit client expone la telemetría que se puede ver en el panel de control .NET Aspire. Para ver la telemetría, vaya al panel de .NET.NET Aspire en https://localhost:7251. Seleccione el recurso newsletter
para ver la telemetría en la página Métricas:
Vuelva a abrir la interfaz de usuario de Swagger y realice algunas solicitudes a los puntos de conexión /subscribe
y /unsubscribe
. A continuación, vuelva al panel de .NET.NET Aspire y seleccione el recurso newsletter
. Seleccione una métrica en el nodo mailkit.net.smtp, como mailkit.net.smtp.client.operation.count
. Debería ver la telemetría de MailKit client:
Resumen
En este artículo, aprendiste a crear una integración .NETy.NET Aspire que usa MailKit para enviar correos electrónicos. También ha aprendido a integrar esta integración en la aplicación Newsletter que ha creado anteriormente. Has aprendido sobre los principios básicos de las integraciones de .NET Aspire, como exponer la biblioteca subyacente de client a los usuarios mediante la inyección de dependencias, y cómo agregar verificaciones de estado y telemetría a la integración. También ha aprendido a actualizar el servicio Newsletter para usar MailKit client.
Continúe y cree sus propias integraciones de .NET.NET Aspire. Si cree que hay suficiente valor de comunidad en la integración que está creando, considere la posibilidad de publicarlo como un paquete NuGet para que otros usuarios lo usen. Además, considere enviar una solicitud de incorporación de cambios al repositorio .NET AspireGitHub para su consideración en las integraciones oficiales de .NET.NET Aspire.