Introduce Yavsc.Interfaces.ISmtpClient and a recording test fake
Narrow ISmtpClient to the four operations MailSender actually uses, behind a Yavsc.Interfaces.ISmtpClientFactory. Production wires MailKitSmtpClient (SmtpClientFactory); tests wire a recording fake (RecordingSmtpClientFactory). The fake is pre-registered in WebServerFixture so SMTP calls are short-circuited; the EMailling test now asserts the Connect -> Authenticate -> Send -> Disconnect sequence. Yavsc.Abstract stays free of MailKit/MimeKit.
This commit is contained in:
parent
16508e9fb2
commit
2dec799d71
13 changed files with 264 additions and 10 deletions
|
|
@ -19,6 +19,6 @@ internal static class PlatformBootstrap
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Platform.DefaultRedirectUri = Settings.DefaultLoopbackRedirectUri;
|
Platform.DefaultRedirectUri = Settings.DefaultLoopbackRedirectUri;
|
||||||
Platform.CreateBrowser = () => new LoopbackBrowser();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,10 +14,12 @@ using Anthropic.SDK;
|
||||||
using IdentityModel;
|
using IdentityModel;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Abstract.Interfaces;
|
using Yavsc.Abstract.Interfaces;
|
||||||
using Yavsc.Helpers;
|
using Yavsc.Helpers;
|
||||||
using Yavsc.Interface;
|
using Yavsc.Interface;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
|
|
@ -84,6 +86,8 @@ internal class Program
|
||||||
services.AddTransient<ITrueEmailSender, MailSender>()
|
services.AddTransient<ITrueEmailSender, MailSender>()
|
||||||
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender,
|
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender,
|
||||||
MailSender>()
|
MailSender>()
|
||||||
|
.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
|
||||||
|
services
|
||||||
.AddTransient<IBillingService, BillingService>()
|
.AddTransient<IBillingService, BillingService>()
|
||||||
.AddTransient<ICalendarManager, CalendarManager>();
|
.AddTransient<ICalendarManager, CalendarManager>();
|
||||||
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
|
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ using IdentityModel;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Yavsc;
|
||||||
using Yavsc.Interface;
|
using Yavsc.Interface;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
|
@ -48,6 +51,8 @@ internal class Program
|
||||||
services
|
services
|
||||||
.AddTransient<ITrueEmailSender, MailSender>()
|
.AddTransient<ITrueEmailSender, MailSender>()
|
||||||
.AddTransient<IEmailSender<ApplicationUser>, MailSender>()
|
.AddTransient<IEmailSender<ApplicationUser>, MailSender>()
|
||||||
|
.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
|
||||||
|
services
|
||||||
.AddTransient<IBillingService, BillingService>()
|
.AddTransient<IBillingService, BillingService>()
|
||||||
.AddTransient<ICalendarManager, CalendarManager>()
|
.AddTransient<ICalendarManager, CalendarManager>()
|
||||||
.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>()
|
.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>()
|
||||||
|
|
|
||||||
71
src/Yavsc.Org.Tests/Fakes/RecordingSmtpClient.cs
Normal file
71
src/Yavsc.Org.Tests/Fakes/RecordingSmtpClient.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using MailKit.Security;
|
||||||
|
using MimeKit;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
|
|
||||||
|
namespace Yavsc.Org.Tests.Fakes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// In-memory test double for <see cref="ISmtpClient"/>. Records
|
||||||
|
/// every call for assertion and short-circuits the SMTP
|
||||||
|
/// roundtrip.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RecordingSmtpClient : ISmtpClient
|
||||||
|
{
|
||||||
|
public List<RecordingSmtpCall> Calls { get; } = new();
|
||||||
|
|
||||||
|
public int Timeout { get; set; }
|
||||||
|
public string? LastConnectedHost { get; private set; }
|
||||||
|
public int LastConnectedPort { get; private set; }
|
||||||
|
public SecureSocketOptions LastConnectedOptions { get; private set; }
|
||||||
|
public string? LastAuthenticatedUser { get; private set; }
|
||||||
|
public MimeMessage? LastSentMessage { get; private set; }
|
||||||
|
public bool WasDisconnected { get; private set; }
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
public void Connect(string host, int port, SecureSocketOptions options)
|
||||||
|
{
|
||||||
|
LastConnectedHost = host;
|
||||||
|
LastConnectedPort = port;
|
||||||
|
LastConnectedOptions = options;
|
||||||
|
Calls.Add(new RecordingSmtpCall(RecordingSmtpCallKind.Connect, host, port, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Authenticate(string userName, string password)
|
||||||
|
{
|
||||||
|
LastAuthenticatedUser = userName;
|
||||||
|
Calls.Add(new RecordingSmtpCall(RecordingSmtpCallKind.Authenticate, userName, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
LastSentMessage = message;
|
||||||
|
Calls.Add(new RecordingSmtpCall(RecordingSmtpCallKind.Send, message.Subject, null, null));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect(bool quit)
|
||||||
|
{
|
||||||
|
WasDisconnected = quit;
|
||||||
|
Calls.Add(new RecordingSmtpCall(RecordingSmtpCallKind.Disconnect, null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RecordingSmtpCallKind
|
||||||
|
{
|
||||||
|
Connect,
|
||||||
|
Authenticate,
|
||||||
|
Send,
|
||||||
|
Disconnect,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct RecordingSmtpCall(
|
||||||
|
RecordingSmtpCallKind Kind,
|
||||||
|
string? HostOrSubjectOrUser,
|
||||||
|
int? Port,
|
||||||
|
SecureSocketOptions? Options);
|
||||||
|
}
|
||||||
21
src/Yavsc.Org.Tests/Fakes/RecordingSmtpClientFactory.cs
Normal file
21
src/Yavsc.Org.Tests/Fakes/RecordingSmtpClientFactory.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Yavsc.Interfaces;
|
||||||
|
|
||||||
|
namespace Yavsc.Org.Tests.Fakes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test double for <see cref="ISmtpClientFactory"/>. Hands out
|
||||||
|
/// <see cref="RecordingSmtpClient"/> instances and tracks them so
|
||||||
|
/// tests can assert on every SMTP interaction.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RecordingSmtpClientFactory : ISmtpClientFactory
|
||||||
|
{
|
||||||
|
public List<RecordingSmtpClient> Created { get; } = new();
|
||||||
|
|
||||||
|
public ISmtpClient CreateClient()
|
||||||
|
{
|
||||||
|
var client = new RecordingSmtpClient();
|
||||||
|
Created.Add(client);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Yavsc.Interface;
|
using Yavsc.Interface;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
|
using Yavsc.Org.Tests.Fakes;
|
||||||
|
|
||||||
namespace Yavsc.Org.Tests
|
namespace Yavsc.Org.Tests
|
||||||
{
|
{
|
||||||
|
|
@ -26,6 +28,8 @@ namespace Yavsc.Org.Tests
|
||||||
|
|
||||||
using IServiceScope scope = _serverFixture.Services.CreateScope();
|
using IServiceScope scope = _serverFixture.Services.CreateScope();
|
||||||
ITrueEmailSender mailSender = scope.ServiceProvider.GetRequiredService<ITrueEmailSender>();
|
ITrueEmailSender mailSender = scope.ServiceProvider.GetRequiredService<ITrueEmailSender>();
|
||||||
|
var factory = Assert.IsType<RecordingSmtpClientFactory>(
|
||||||
|
scope.ServiceProvider.GetRequiredService<ISmtpClientFactory>());
|
||||||
|
|
||||||
output.WriteLine("SendEMailSynchrone ...");
|
output.WriteLine("SendEMailSynchrone ...");
|
||||||
mailSender.SendEmailAsync
|
mailSender.SendEmailAsync
|
||||||
|
|
@ -35,6 +39,21 @@ namespace Yavsc.Org.Tests
|
||||||
$"monthly email",
|
$"monthly email",
|
||||||
"test boby monthly email").Wait();
|
"test boby monthly email").Wait();
|
||||||
|
|
||||||
|
// Assert the SMTP roundtrip was short-circuited by the
|
||||||
|
// recording fake installed in WebServerFixture: exactly
|
||||||
|
// one client was created and it saw the expected sequence
|
||||||
|
// of Connect → Authenticate → Send → Disconnect.
|
||||||
|
var client = Assert.Single(factory.Created);
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
RecordingSmtpCallKind.Connect,
|
||||||
|
RecordingSmtpCallKind.Authenticate,
|
||||||
|
RecordingSmtpCallKind.Send,
|
||||||
|
RecordingSmtpCallKind.Disconnect,
|
||||||
|
},
|
||||||
|
client.Calls.Select(c => c.Kind).ToArray());
|
||||||
|
Assert.Equal(_serverFixture.SiteSettings.Owner.EMail, client.LastSentMessage?.To.Mailboxes.First().Address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,11 @@ using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Extensions;
|
using Yavsc.Extensions;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Client = IdentityServer8.EntityFramework.Entities.Client;
|
using Client = IdentityServer8.EntityFramework.Entities.Client;
|
||||||
|
using Yavsc.Org.Tests.Fakes;
|
||||||
|
|
||||||
|
|
||||||
namespace Yavsc.Org.Tests
|
namespace Yavsc.Org.Tests
|
||||||
|
|
@ -40,6 +42,7 @@ namespace Yavsc.Org.Tests
|
||||||
private static string? _sharedTestingUserName;
|
private static string? _sharedTestingUserName;
|
||||||
private static string? _sharedTestingUserPassword;
|
private static string? _sharedTestingUserPassword;
|
||||||
private static string? _sharedTestingUserEmail;
|
private static string? _sharedTestingUserEmail;
|
||||||
|
private static RecordingSmtpClientFactory? _sharedSmtpClientFactory;
|
||||||
private static IServiceProvider? _sharedServices;
|
private static IServiceProvider? _sharedServices;
|
||||||
private static IConfiguration? _sharedConfiguration;
|
private static IConfiguration? _sharedConfiguration;
|
||||||
private static SiteSettings? _sharedSiteSettings;
|
private static SiteSettings? _sharedSiteSettings;
|
||||||
|
|
@ -64,6 +67,7 @@ namespace Yavsc.Org.Tests
|
||||||
public SiteSettings? SiteSettings { get => siteSettings; set => siteSettings = value; }
|
public SiteSettings? SiteSettings { get => siteSettings; set => siteSettings = value; }
|
||||||
public string? TestClientSecret { get; set; }
|
public string? TestClientSecret { get; set; }
|
||||||
public string? TestingUserEmail { get; set; }
|
public string? TestingUserEmail { get; set; }
|
||||||
|
public RecordingSmtpClientFactory? SmtpClientFactory { get; private set; }
|
||||||
public WebServerFixture()
|
public WebServerFixture()
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
|
|
@ -100,6 +104,7 @@ namespace Yavsc.Org.Tests
|
||||||
_sharedTestingUserName = null;
|
_sharedTestingUserName = null;
|
||||||
_sharedTestingUserPassword = null;
|
_sharedTestingUserPassword = null;
|
||||||
_sharedTestingUserEmail = null;
|
_sharedTestingUserEmail = null;
|
||||||
|
_sharedSmtpClientFactory = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +121,7 @@ namespace Yavsc.Org.Tests
|
||||||
TestingUserName = _sharedTestingUserName;
|
TestingUserName = _sharedTestingUserName;
|
||||||
TestingUserPassword = _sharedTestingUserPassword;
|
TestingUserPassword = _sharedTestingUserPassword;
|
||||||
TestingUserEmail = _sharedTestingUserEmail;
|
TestingUserEmail = _sharedTestingUserEmail;
|
||||||
|
SmtpClientFactory = _sharedSmtpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetupHost()
|
public async Task SetupHost()
|
||||||
|
|
@ -156,8 +162,23 @@ namespace Yavsc.Org.Tests
|
||||||
// IServiceCollection.AddSingleton.
|
// IServiceCollection.AddSingleton.
|
||||||
builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthPolicyProvider>();
|
builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthPolicyProvider>();
|
||||||
|
|
||||||
|
// Replace the production ISmtpClientFactory (added later
|
||||||
|
// by ConfigureWebAppServices via TryAddSingleton) with a
|
||||||
|
// recording fake. By pre-registering here, the prod
|
||||||
|
// TryAdd becomes a no-op and tests get a single shared
|
||||||
|
// fake they can assert against.
|
||||||
|
var smtpFactory = new RecordingSmtpClientFactory();
|
||||||
|
builder.Services.AddSingleton<ISmtpClientFactory>(smtpFactory);
|
||||||
|
_sharedSmtpClientFactory = smtpFactory;
|
||||||
|
|
||||||
_app = builder.ConfigureWebAppServices();
|
_app = builder.ConfigureWebAppServices();
|
||||||
|
|
||||||
|
// Note: a recording ISmtpClientFactory is registered
|
||||||
|
// BEFORE ConfigureWebAppServices() above (further up in
|
||||||
|
// this method) so the production TryAddSingleton inside
|
||||||
|
// ConfigureWebAppServices becomes a no-op.
|
||||||
|
// _sharedSmtpClientFactory is captured there.
|
||||||
|
|
||||||
// The MSBuild target CopyYavscOrgStaticAssets in
|
// The MSBuild target CopyYavscOrgStaticAssets in
|
||||||
// Yavsc.Org.Tests.csproj mirrors the Yavsc.Org static
|
// Yavsc.Org.Tests.csproj mirrors the Yavsc.Org static
|
||||||
// assets manifest into the test bin directory. Call
|
// assets manifest into the test bin directory. Call
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -102,6 +103,8 @@ public static class HostingExtensions
|
||||||
services.AddTransient<ITrueEmailSender, MailSender>()
|
services.AddTransient<ITrueEmailSender, MailSender>()
|
||||||
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>();
|
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>();
|
||||||
|
|
||||||
|
services.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
|
||||||
|
|
||||||
|
|
||||||
services.AddTransient<IYavscMessageSender, YavscMessageSender>()
|
services.AddTransient<IYavscMessageSender, YavscMessageSender>()
|
||||||
.AddTransient<IBillingService, BillingService>()
|
.AddTransient<IBillingService, BillingService>()
|
||||||
|
|
|
||||||
25
src/Yavsc.Server/Interfaces/ISmtpClient.cs
Normal file
25
src/Yavsc.Server/Interfaces/ISmtpClient.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MimeKit;
|
||||||
|
|
||||||
|
namespace Yavsc.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Yavsc's own SMTP client surface. Narrower than
|
||||||
|
/// <c>MailKit.Net.Smtp.ISmtpClient</c>: only the calls that
|
||||||
|
/// <c>MailSender</c> actually makes. Production wires
|
||||||
|
/// <c>MailKitSmtpClient</c>; tests wire a recording fake.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISmtpClient : IDisposable
|
||||||
|
{
|
||||||
|
int Timeout { get; set; }
|
||||||
|
|
||||||
|
void Connect(string host, int port, MailKit.Security.SecureSocketOptions options);
|
||||||
|
|
||||||
|
void Authenticate(string userName, string password);
|
||||||
|
|
||||||
|
Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
void Disconnect(bool quit);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Yavsc.Server/Interfaces/ISmtpClientFactory.cs
Normal file
13
src/Yavsc.Server/Interfaces/ISmtpClientFactory.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Yavsc.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Builds fresh <see cref="ISmtpClient"/> instances on demand.
|
||||||
|
/// Per-call construction is required because <c>MailSender</c>
|
||||||
|
/// owns its client with a <c>using</c> block: a singleton or
|
||||||
|
/// scoped client would race across concurrent sends.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISmtpClientFactory
|
||||||
|
{
|
||||||
|
ISmtpClient CreateClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/Yavsc.Server/Services/MailKitSmtpClient.cs
Normal file
53
src/Yavsc.Server/Services/MailKitSmtpClient.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using MailKit.Security;
|
||||||
|
using MimeKit;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
|
|
||||||
|
namespace Yavsc.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter from <see cref="Yavsc.Interfaces.ISmtpClient"/>
|
||||||
|
/// to MailKit's <c>SmtpClient</c>. Stateless; the underlying
|
||||||
|
/// MailKit client is created per <c>Connect</c> cycle by the
|
||||||
|
/// factory and disposed by <c>MailSender</c>'s <c>using</c>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MailKitSmtpClient : ISmtpClient
|
||||||
|
{
|
||||||
|
private readonly MailKit.Net.Smtp.SmtpClient _client;
|
||||||
|
|
||||||
|
public MailKitSmtpClient()
|
||||||
|
{
|
||||||
|
_client = new MailKit.Net.Smtp.SmtpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Timeout
|
||||||
|
{
|
||||||
|
get => _client.Timeout;
|
||||||
|
set => _client.Timeout = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(string host, int port, SecureSocketOptions options)
|
||||||
|
{
|
||||||
|
_client.Connect(host, port, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Authenticate(string userName, string password)
|
||||||
|
{
|
||||||
|
_client.Authenticate(userName, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return _client.SendAsync(message, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect(bool quit)
|
||||||
|
{
|
||||||
|
_client.Disconnect(quit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using MailKit.Net.Smtp;
|
|
||||||
using MailKit.Security;
|
using MailKit.Security;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Yavsc.Interface;
|
using Yavsc.Interface;
|
||||||
|
using Yavsc.Interfaces;
|
||||||
using Yavsc.Settings;
|
using Yavsc.Settings;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
|
@ -16,21 +16,26 @@ namespace Yavsc.Services
|
||||||
{
|
{
|
||||||
public class MailSender : IEmailSender<ApplicationUser>, IEmailSender, ITrueEmailSender
|
public class MailSender : IEmailSender<ApplicationUser>, IEmailSender, ITrueEmailSender
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly IStringLocalizer<MailSender> localizer;
|
private readonly IStringLocalizer<MailSender> localizer;
|
||||||
readonly SiteSettings siteSettings;
|
readonly SiteSettings siteSettings;
|
||||||
readonly SmtpSettings smtpSettings;
|
readonly SmtpSettings smtpSettings;
|
||||||
private readonly ILogger logger;
|
private readonly ILogger logger;
|
||||||
|
private readonly ISmtpClientFactory _smtpClientFactory;
|
||||||
|
|
||||||
public MailSender(
|
public MailSender(
|
||||||
IOptions<SiteSettings> sitesOptions,
|
IOptions<SiteSettings> sitesOptions,
|
||||||
IOptions<SmtpSettings> smtpOptions,
|
IOptions<SmtpSettings> smtpOptions,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStringLocalizer<MailSender> localizer
|
IStringLocalizer<MailSender> localizer,
|
||||||
|
ISmtpClientFactory smtpClientFactory
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.localizer = localizer;
|
this.localizer = localizer;
|
||||||
siteSettings = sitesOptions.Value;
|
siteSettings = sitesOptions.Value;
|
||||||
smtpSettings = smtpOptions.Value;
|
smtpSettings = smtpOptions.Value;
|
||||||
logger = loggerFactory.CreateLogger<MailSender>();
|
logger = loggerFactory.CreateLogger<MailSender>();
|
||||||
|
_smtpClientFactory = smtpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink)
|
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink)
|
||||||
|
|
@ -69,7 +74,7 @@ namespace Yavsc.Services
|
||||||
msg.MessageId = MimeKit.Utils.MimeUtils.GenerateMessageId(
|
msg.MessageId = MimeKit.Utils.MimeUtils.GenerateMessageId(
|
||||||
siteSettings.Authority
|
siteSettings.Authority
|
||||||
);
|
);
|
||||||
using (SmtpClient sc = new())
|
using ISmtpClient sc = _smtpClientFactory.CreateClient();
|
||||||
{
|
{
|
||||||
sc.Timeout = 30000;
|
sc.Timeout = 30000;
|
||||||
sc.Connect(
|
sc.Connect(
|
||||||
|
|
|
||||||
14
src/Yavsc.Server/Services/SmtpClientFactory.cs
Normal file
14
src/Yavsc.Server/Services/SmtpClientFactory.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Yavsc.Interfaces;
|
||||||
|
|
||||||
|
namespace Yavsc.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Production <see cref="ISmtpClientFactory"/>: hands out a
|
||||||
|
/// fresh <see cref="MailKitSmtpClient"/> per call. Stateless;
|
||||||
|
/// safe to register as a singleton.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SmtpClientFactory : ISmtpClientFactory
|
||||||
|
{
|
||||||
|
public ISmtpClient CreateClient() => new MailKitSmtpClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue