Skapa anpassade .NET Aspireclient-integreringar
Den här artikeln är en fortsättning på artikeln Skapa anpassade .NET.NET Aspire som är värd för integreringar. Den vägleder dig genom att skapa en .NET Aspireclient integrering som använder MailKit för att skicka e-postmeddelanden. Den här integreringen läggs sedan till i appen Nyhetsbrev som du skapade tidigare. I föregående exempel utelämnades skapandet av en client-integrering och förlitade sig i stället på den befintliga .NETSmtpClient
. Det är bäst att använda MailKits SmtpClient
över den officiella .NETSmtpClient
för att skicka e-postmeddelanden, eftersom det är modernare och stöder fler funktioner/protokoll. Mer information finns i .NET SmtpClient: Kommentarer.
Förutsättningar
Om du följer instruktionerna bör du ha en nyhetsbrev-app från stegen i artikeln Skapa anpassad .NET.NET Aspire hosting-integration.
Tips
Den här artikeln är inspirerad av befintliga .NET.NET Aspire integreringar och baseras på teamets officiella vägledning. Det finns platser där den nämnda vägledningen varierar, och det är viktigt att förstå resonemanget bakom skillnaderna. Mer information finns i .NET.NET Aspire integrationskrav.
Skapa bibliotek för integrering
.NET .NET Aspire integreringar levereras som NuGet-paket, men i det här exemplet ligger det utanför omfånget för den här artikeln att publicera ett NuGet-paket. I stället skapar du ett klassbiblioteksprojekt som innehåller integreringen och refererar till det som ett projekt. .NET Aspire integreringspaket är avsedda att omsluta ett client-bibliotek, till exempel MailKit, och tillhandahålla produktionsklar telemetri, hälsokontroller, konfigurerbarhet och testbarhet. Vi börjar med att skapa ett nytt klassbiblioteksprojekt.
Skapa ett nytt klassbiblioteksprojekt med namnet
MailKit.Client
i samma katalog som MailDevResource.sln från föregående artikel.dotnet new classlib -o MailKit.Client
Lägg till projektet i lösningen.
dotnet sln ./MailDevResource.sln add MailKit.Client/MailKit.Client.csproj
Nästa steg är att lägga till alla NuGet-paket som integreringen förlitar sig på. I stället för att lägga till varje paket en i taget från .NET CLI är det förmodligen enklare att kopiera och klistra in följande XML i MailKit.Client.csproj fil.
<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>
Definiera integreringsinställningar
När du skapar en .NET Aspire integrering är det bäst att förstå det client bibliotek som du mappar till. Med MailKit måste du förstå de konfigurationsinställningar som krävs för att ansluta till ett SMTP-server(Simple Mail Transfer Protocol). Men det är också viktigt att förstå om biblioteket har stöd för hälsokontroller, spårning och mått. MailKit stöder spårning och metrik, genom sin Telemetry.SmtpClient
klass. När du lägger till hälsokontrollerbör du använda alla etablerade eller befintliga hälsokontroller om möjligt. Annars kan du överväga att implementera din egen i integrationen. Lägg till följande kod i projektet MailKit.Client
i en fil med namnet 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;
}
}
}
Koden ovan definierar klassen MailKitClientSettings
med:
-
Endpoint
egenskap som representerar anslutningssträngen till SMTP-server. -
DisableHealthChecks
egenskap som avgör om hälsokontroller är aktiverade. -
DisableTracing
egenskap som avgör om spårning är aktiverat. -
DisableMetrics
egenskap som avgör om mått är aktiverade.
Parsa anslutningssträngslogik
Inställningsklassen innehåller också en ParseConnectionString
metod som parsar anslutningssträngen till en giltig Uri
. Konfigurationen förväntas anges i följande format:
-
ConnectionStrings:<connectionName>
: Anslutningssträngen till SMTP-server. -
MailKit:Client:ConnectionString
: Anslutningssträngen till SMTP-server.
Om inget av dessa värden anges genereras ett undantag.
Exponera client funktioner
Målet med .NET Aspire integrationer är att exponera det underliggande client biblioteket för konsumenter via beroendeinjektion. Med MailKit och i det här exemplet är klassen SmtpClient
det du vill exponera. Du omsluter inte några funktioner, utan mappar snarare konfigurationsinställningar till en SmtpClient
-klass. Det är vanligt att exponera både standardregistreringar och registreringar för "keyed-service" vid integrationer. Standardregistreringar används när det bara finns en instans av en tjänst och registreringar av nyckelade tjänster används när det finns flera instanser av en tjänst. Ibland använder du ett fabriksmönster för att uppnå flera registreringar av samma typ. Lägg till följande kod i projektet MailKit.Client
i en fil med namnet 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();
}
}
Klassen MailKitClientFactory
är en fabrik som skapar en ISmtpClient
-instans baserat på konfigurationsinställningarna. Den ansvarar för att returnera en ISmtpClient
implementering som har en aktiv anslutning till en konfigurerad SMTP-server. Därefter måste du exponera funktionaliteten så att konsumenterna kan registrera den här fabriken med beroendeinjektionskontainern. Lägg till följande kod i projektet MailKit.Client
i en fil med namnet 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));
}
}
}
Föregående kod lägger till två tilläggsmetoder på den IHostApplicationBuilder
typen, en för standardregistreringen av MailKit och en annan för nyckelregistrering av MailKit.
Tips
Tilläggsmetoder för .NET.NET Aspire integreringar bör utöka IHostApplicationBuilder
typ och följa Add<MeaningfulName>
namngivningskonvention där <MeaningfulName>
är den typ eller funktion som du lägger till. I den här artikeln används AddMailKitClient
-tilläggsmetoden för att lägga till MailKit-client. Det är förmodligen mer i linje med den officiella vägledningen för att använda AddMailKitSmtpClient
i stället för AddMailKitClient
, eftersom detta bara registrerar SmtpClient
och inte hela MailKit-biblioteket.
Båda tilläggen förlitar sig slutligen på den privata AddMailKitClient
-metoden för att registrera MailKitClientFactory
med containern för beroendeinmatning som en begränsad tjänst. Anledningen till att registrera MailKitClientFactory
som en begränsad tjänst är att anslutningsåtgärderna anses vara dyra och bör återanvändas inom samma omfång där det är möjligt. Med andra ord bör samma ISmtpClient
instans användas för en enskild begäran. Fabriken håller fast vid instansen av SmtpClient
som den skapar och tar bort den.
Konfigurationsbindning
En av de första sakerna som den privata implementeringen av AddMailKitClient
metoder gör är att binda konfigurationsinställningarna till klassen MailKitClientSettings
. Inställningsklassen instansieras och sedan anropas Bind
med det specifika avsnittet i konfigurationen. Sedan anropas den valfria configureSettings
-delegeringen med de aktuella inställningarna. Detta gör det möjligt för konsumenten att ytterligare konfigurera inställningarna, vilket säkerställer att manuella kodinställningar respekteras jämfört med konfigurationsinställningarna. Därefter, beroende på om serviceKey
värdet angavs, bör MailKitClientFactory
registreras med containern för beroendeinmatning som antingen en standardtjänst eller en nyckelbaserad tjänst.
Viktig
Det är avsiktligt att implementationFactory
överlagring anropas när tjänster registreras. Metoden CreateMailKitClientFactory
utlöser när konfigurationen är ogiltig. Detta säkerställer att skapandet av MailKitClientFactory
skjuts upp tills det behövs, och det förhindrar att appen kraschar innan loggning är tillgänglig.
Registreringen av hälsokontroller och telemetri beskrivs lite mer detaljerat i följande avsnitt.
Lägga till hälsokontroller
Hälsokontroller är ett sätt att övervaka hälsotillståndet för en integrering. Med MailKit kan du kontrollera om anslutningen till SMTP-server är felfri. Lägg till följande kod i projektet MailKit.Client
i en fil med namnet 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);
}
}
}
Föregående implementering av hälsokontroll:
- Implementerar
IHealthCheck
-gränssnittet. - Accepterar
MailKitClientFactory
som en primär konstruktorparameter. - Uppfyller metoden
CheckHealthAsync
genom att:- Försöker hämta en
ISmtpClient
instans frånfactory
. Om det lyckas returnerasHealthCheckResult.Healthy
. - Om ett undantag utlöses returneras
HealthCheckResult.Unhealthy
.
- Försöker hämta en
Som tidigare delats vid registreringen av MailKitClientFactory
är MailKitHealthCheck
villkorligt registrerat med IHeathChecksBuilder
:
if (settings.DisableHealthChecks is false)
{
builder.Services.AddHealthChecks()
.AddCheck<MailKitHealthCheck>(
name: serviceKey is null ? "MailKit" : $"MailKit_{connectionName}",
failureStatus: default,
tags: []);
}
Konsumenten kan välja att utelämna hälsokontroller genom att ange egenskapen DisableHealthChecks
till true
i konfigurationen. Ett vanligt mönster för integreringar är att ha valfria funktioner och .NET.NET Aspire integreringar uppmuntrar starkt dessa typer av konfigurationer. Mer information om hälsokontroller och ett arbetsexempel som innehåller ett användargränssnitt finns i .NET AspireASP.NET Core HealthChecksUI-exempel.
Anslut telemetri
Bästa praxis är att biblioteket MailKit client exponerar telemetri. .NET .NET Aspire kan dra nytta av den här telemetrin och visa den på .NET.NET Aspire instrumentpanelen. Beroende på om spårning och mått är aktiverade eller inte är telemetrin kopplad enligt följande kodfragment:
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));
}
Uppdatera nyhetsbrevstjänsten
När integrationsbiblioteket har skapats kan du nu uppdatera nyhetsbrevstjänsten för att använda MailKit-client. Det första steget är att lägga till en referens till MailKit.Client
-projektet. Lägg till MailKit.Client.csproj projektreferens till MailDevResource.NewsletterService
-projektet:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailKit.Client/MailKit.Client.csproj
Lägg sedan till en referens till ServiceDefaults
-projektet:
dotnet add ./MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj reference MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj
Det sista steget är att ersätta den befintliga Program.cs-filen i MailDevResource.NewsletterService
-projektet med följande C#-kod:
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();
De viktigaste ändringarna i föregående kod är:
- De uppdaterade
using
-instruktionerna som innehåller namnrymdernaMailKit.Client
,MailKit.Net.Smtp
ochMimeKit
. - Ersättning av registreringen för den officiella .NET
SmtpClient
med anropet tillAddMailKitClient
tilläggsmetod. - Ersättningen av både
/subscribe
och/unsubscribe
mappa postanrop för att i stället mata inMailKitClientFactory
och använda instansenISmtpClient
för att skicka e-postmeddelandet.
Kör exemplet
Nu när du har skapat MailKit-client integrering och uppdaterat nyhetsbrevstjänsten så att den används kan du köra exemplet. I din IDE väljer du F5 eller kör dotnet run
från rotkatalogen för lösningen för att starta programmet. Du bör se .NET.NET Aspire instrumentpanel:
När programmet har körts går du till Swagger-användargränssnittet vid https://localhost:7251/swagger och testar /subscribe
- och /unsubscribe
-slutpunkterna. Välj nedåtpilen för att expandera slutpunkten:
Välj sedan knappen Try it out
. Ange en e-postadress och välj sedan knappen Execute
.
Upprepa detta flera gånger för att lägga till flera e-postadresser. Du bör se e-postmeddelandet som skickas till MailDev inkorgen:
Stoppa programmet genom att välja Ctrl+C i terminalfönstret där programmet körs, eller genom att välja stoppknappen i din IDE.
Visa Telemetri för MailKit
Biblioteket MailKit client visar telemetri som kan visas på instrumentpanelen .NET Aspire. Om du vill visa telemetrin navigerar du till instrumentpanelen .NET.NET Aspire på https://localhost:7251. Välj resursen newsletter
för att visa telemetrin på sidan Mått.
Öppna Swagger-användargränssnittet igen och gör några begäranden till /subscribe
och /unsubscribe
slutpunkter. Gå sedan tillbaka till instrumentpanelen .NET.NET Aspire och välj resursen newsletter
. Välj ett mått under noden mailkit.net.smtp, till exempel mailkit.net.smtp.client.operation.count
. Du bör se telemetrin för MailKit-client:
Sammanfattning
I den här artikeln har du lärt dig hur du skapar en .NET.NET Aspire integrering som använder MailKit för att skicka e-postmeddelanden. Du har också lärt dig hur du integrerar den här integreringen i appen Nyhetsbrev som du skapade tidigare. Du har lärt dig om grundprinciperna för .NET Aspire-integrationer, till exempel att exponera det underliggande client-biblioteket för konsumenter via beroendeinjektion och hur du lägger till hälsokontroller och telemetri i integrationen. Du har också lärt dig hur du uppdaterar nyhetsbrevstjänsten för att använda MailKit-client.
Gå vidare och skapa egna .NET.NET Aspire integreringar. Om du tror att det finns tillräckligt med community-värde i den integrering som du skapar kan du publicera det som ett NuGet-paket för andra att använda. Överväg dessutom att skicka en pull request till förrådet .NET AspireGitHub för övervägande att inkluderas i de officiella .NET.NET Aspire integrationerna.
Nästa steg
.NET Aspire