Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här artikeln är en fortsättning på artikeln Skapa anpassade Aspire värdintegreringar . Den vägleder dig genom att skapa en Aspire klientintegrering 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 klientintegrering 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 med bör du ha en nyhetsbrevsapp från stegen i artikeln Skapa anpassad Aspire värdintegrering .
Tips
Den här artikeln är inspirerad av befintliga Aspire integreringar och bygger 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 Aspire integreringskrav.
Skapa bibliotek för integrering
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. Aspire integreringspaket är avsedda att omsluta ett klientbibliotek, 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.Clienti 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.14.1" />
  <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
  <PackageReference Include="Microsoft.Extensions.Resilience" Version="9.10.0" />
  <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.10" />
  <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.10" />
  <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.13.1" />
</ItemGroup>
Definiera integreringsinställningar
När du skapar en Aspire integrering är det bäst att förstå klientbiblioteket som du mappar till. Med MailKit måste du förstå de konfigurationsinställningar som krävs för att ansluta till en 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:
- 
              Endpointegenskap som representerar anslutningssträngen till SMTP-servern.
- 
              DisableHealthChecksegenskap som avgör om hälsokontroller är aktiverade.
- 
              DisableTracingegenskap som avgör om spårning är aktiverat.
- 
              DisableMetricsegenskap 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-servern.
- 
              MailKit:Client:ConnectionString: Anslutningssträngen till SMTP-servern.
Om inget av dessa värden anges genereras ett undantag.
Exponera klientfunktioner
Målet med Aspire integreringar är att exponera det underliggande klientbiblioteket för konsumenter via beroendeinmatning. 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 standard- och nyckeltjänstregistreringar 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
{
    /// <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)
    {
        var client = new SmtpClient();
        try
        {
            if (settings.Endpoint is not null)
            {
                await client.ConnectAsync(settings.Endpoint, cancellationToken)
                             .ConfigureAwait(false);
            }
            return client;
        }
        catch
        {
            await client.DisconnectAsync(true, cancellationToken)
            client.Dispose();
            throw;
        }       
    }
}
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 Aspire integreringar bör utöka IHostApplicationBuilder typen och följa Add<MeaningfulName> namngivningskonventionen 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-klienten. 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 behåller instansen av SmtpClient som den skapar och kasserar 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 overload 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-servern ä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 MailKitClientFactorysom en primär konstruktorparameter.
- Uppfyller metoden CheckHealthAsyncgenom att:- Försöker hämta en ISmtpClientinstans 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 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 AspireASP.NET Core HealthChecksUI-exempel.
Anslut telemetri
Bästa praxis är att MailKit-klientbiblioteket exponerar telemetri. Aspire kan dra nytta av den här telemetrin och visa den på 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-klienten. 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.SmtpochMimeKit.
- Ersättning av registreringen för den officiella .NETSmtpClientmed att anropa tilläggsmetodenAddMailKitClient.
- Ersättningen av både /subscribeoch/unsubscribemappostanrop genom att istället mata inMailKitClientFactoryoch användaISmtpClient-instansen för att skicka e-postmeddelandet.
Kör exemplet
Nu när du har skapat MailKit-klientintegrering och uppdaterat nyhetsbrevstjänsten så att den används kan du köra exemplet. Från 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 Aspire instrumentpanelen:
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
MailKit-klientbiblioteket exponerar telemetri som kan visas på Aspire instrumentpanelen. Om du vill visa telemetrin navigerar du till Aspire instrumentpanelen 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 Aspire instrumentpanelen 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-klienten:
Sammanfattning
I den här artikeln har du lärt dig hur du skapar en 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 huvudprinciperna för Aspire integreringar, såsom att exponera det underliggande klientbiblioteket för konsumenter via beroendeinjektion, och hur man lägger till hälsokontroller och telemetri i integreringarna. Du har också lärt dig hur du uppdaterar nyhetsbrevstjänsten för att använda MailKit-klienten.
Gå vidare och skapa dina egna 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-förfrågan till lagringsplatsenAspireGitHub för att bli övervägd för inkludering i de officiella Aspire integrationerna.
 
              
               
              
               
              
               
              
              