You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core依赖注入:每次请求是否生成新SmtpClient实例?

问题拆解与解决方案

首先直接回答你的核心疑问:

  • 用AddTransient注册IMailer<BaseModel>时,每次从DI容器获取IMailer实例(包括同一个请求内多次获取)都会生成全新的Mailer,同时也会新建一个SmtpClient——因为你在注册的工厂方法里直接new了SmtpClient,每次创建Mailer都会执行这段逻辑。
  • 改成AddScoped后,同一个请求内只会有一个Mailer实例,对应的SmtpClient也是同一个;但不同请求之间会各自创建新的实例。

连续发邮件出错的原因

你的Mailer<T>实现了IDisposable,并且在Dispose里销毁了_smtpClient。如果在同一个请求中,你多次调用SendEmail,或者DI容器提前触发了Mailer的Dispose(比如某些框架的生命周期钩子),后续再用这个已被释放的SmtpClient发邮件就会报错。

另外,旧的System.Net.Mail.SmtpClient本身有复用缺陷:如果SMTP服务器主动关闭了连接,复用同一个实例再次发送时,客户端不会自动重建连接,直接抛出错误。

修复方案

这里给你几个可行的解决思路:

方案1:每次发送都用新的SmtpClient

修改Mailer的设计,不在构造函数中持有SmtpClient,而是每次发送时从DI容器获取新实例,用完自动释放:

public sealed class Mailer<T> : IMailer<T> where T : BaseModel 
{ 
    private readonly IServiceProvider _serviceProvider;
    private readonly string _contentRootPath; 
    private readonly bool _forceSandbox; 

    public Mailer(IServiceProvider serviceProvider, string contentRootPath, bool isLive) 
    { 
        _serviceProvider = serviceProvider;
        _contentRootPath = contentRootPath; 
        _forceSandbox = !isLive; 
    } 

    public async Task<bool> SendEmail(T model, string template) 
    { 
        using var smtpClient = _serviceProvider.GetRequiredService<SmtpClient>();
        Email.DefaultSender = new SmtpSender(smtpClient); 
        Email.DefaultRenderer = new RazorRenderer(); 

        // 以下保持原发送逻辑不变
        if (template.Contains(".cshtml")) template = template.Replace(".cshtml", "");
        var email = Email 
            .From(model.From.Email, model.From.Name.NullOrEmptyTo(model.From.Email)) 
            .To(EmailAddressHelper.GetAddressesFromModel(model.Recipients, _forceSandbox)) 
            .Subject(model.Subject) 
            .UsingTemplateFromFile($@"{_contentRootPath}\views\{template}.cshtml", model) 
            .Attach(EmailAttachmentHelper.GetAttachmentsFromModel(model.Attachments)); 

        if (model.CcRecipients != null && model.CcRecipients.Any()) 
            email.CC(EmailAddressHelper.GetAddressesFromModel(model.CcRecipients, _forceSandbox)); 
        if (model.BccRecipients != null && model.BccRecipients.Any()) 
            email.BCC(EmailAddressHelper.GetAddressesFromModel(model.BccRecipients, _forceSandbox)); 

        email.Data.Headers = EmailHeaderHelper.GetHeaders(model.UniqueID, model.Headers); 
        var result = await email.SendAsync(); 
        return result.Successful; 
    }
}

同时修改注册代码,单独把SmtpClient注册为Transient:

public static void AddSmtpMailer(this IServiceCollection services, IHostingEnvironment environment) 
{ 
    // 单独注册SmtpClient
    services.AddTransient<SmtpClient>(_ => 
        environment.IsDevelopment() 
            ? new SmtpClient { PickupDirectoryLocation = @"c:\temp", DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory } 
            : new SmtpClient { Host = "mail.someplace.com", Port = 25, EnableSsl = true, Credentials = new NetworkCredential("user", "password") }
    );
    // 注册Mailer,让DI自动注入依赖
    services.AddTransient<IMailer<BaseModel>, Mailer<BaseModel>>();
}

这样每次发送都会用全新的SmtpClient,用完通过using自动释放,彻底避免复用导致的问题。

方案2:安全复用SmtpClient(需注意线程安全)

如果想复用SmtpClient提升性能,可以把它注册为Singleton,同时修改Mailer不要在Dispose中销毁它:

// 注册SmtpClient为单例
services.AddSingleton<SmtpClient>(_ => 
    environment.IsDevelopment() 
        ? new SmtpClient { PickupDirectoryLocation = @"c:\temp", DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory } 
        : new SmtpClient { Host = "mail.someplace.com", Port = 25, EnableSsl = true, Credentials = new NetworkCredential("user", "password") }
);

// 修改Mailer的Dispose方法
public void Dispose() 
{ 
    // 不要销毁_smtpClient,由DI容器管理其生命周期
}

⚠️ 注意:System.Net.Mail.SmtpClient的实例方法不是线程安全的,多个请求同时发邮件时必须加锁,否则会出现并发错误:

// 在Mailer中加锁
private static readonly object _sendLock = new object();

public async Task<bool> SendEmail(T model, string template) 
{ 
    lock(_sendLock)
    {
        // 发送逻辑放在锁内
        Email.DefaultSender = new SmtpSender(_smtpClient); 
        // ... 其余代码不变
        var result = await email.SendAsync(); 
        return result.Successful; 
    }
}

方案3:切换到MailKit(推荐)

System.Net.Mail.SmtpClient已经被微软标记为过时,官方推荐使用MailKit库。它的SmtpClient设计更健壮,支持自动重连、连接池,能完美解决连续发送的问题,而且API更友好。

总结

你的问题本质是:

  • AddScoped模式下,同一个请求复用的SmtpClient被Mailer的Dispose提前销毁,导致后续发送失败;
  • 旧SmtpClient的连接管理缺陷放大了这个问题。

选上面任意一个方案,都能解决你连续发送邮件出错的问题。

内容的提问来源于stack exchange,提问作者Simon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 08:02:30