如何在ASP.NET Core中通过DI将IEmailSender注入自定义日志记录器?
解决自定义日志记录器中注入IEmailSender的问题
你的核心问题是错误地把服务实例(IEmailSender)放到了配置类里,而且日志提供者和日志器的构造函数没有正确接收依赖注入的服务。下面是一步步的修正方案:
1. 调整日志配置类
配置类只应该存放纯配置参数,不要放服务实例,所以把IEmailSender从EmailLoggerConfiguration中移除:
public sealed class EmailLoggerConfiguration { public int EventId { get; set; } public string EmailToSendTo { get; set; } // 移除IEmailSender字段 }
2. 修改日志提供者(EmailLoggingProvider)
在提供者的构造函数中直接注入IEmailSender,这样创建日志器时可以把它传递下去:
internal class EmailLoggingProvider : ILoggerProvider { private readonly IDisposable? _onChangeToken; private EmailLoggerConfiguration _currentConfig; private readonly ConcurrentDictionary<string, EmailLogger> _loggers = new(StringComparer.OrdinalIgnoreCase); // 注入IEmailSender private readonly IEmailSender _emailSender; public EmailLoggingProvider( IOptionsMonitor<EmailLoggerConfiguration> config, IEmailSender emailSender) // 添加这个参数 { _currentConfig = config.CurrentValue; _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig); _emailSender = emailSender; } public ILogger CreateLogger(string categoryName) => // 创建EmailLogger时传入_emailSender _loggers.GetOrAdd(categoryName, name => new EmailLogger(name, GetCurrentConfig, _emailSender)); private EmailLoggerConfiguration GetCurrentConfig() => _currentConfig; public void Dispose() { _loggers.Clear(); _onChangeToken?.Dispose(); } }
3. 修正日志器(EmailLogger)
更新构造函数接收IEmailSender,并在Log方法中使用:
internal class EmailLogger : ILogger { private readonly string _categoryName; private readonly Func<EmailLoggerConfiguration> _getCurrentConfig; private readonly IEmailSender _emailSender; // 添加IEmailSender参数 public EmailLogger(string categoryName, Func<EmailLoggerConfiguration> getCurrentConfig, IEmailSender emailSender) { _categoryName = categoryName; _getCurrentConfig = getCurrentConfig; _emailSender = emailSender; } public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!; public bool IsEnabled(LogLevel logLevel) => !string.IsNullOrEmpty(_getCurrentConfig().EmailToSendTo); public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { var config = _getCurrentConfig(); if (string.IsNullOrEmpty(config.EmailToSendTo) || exception == null) return; // 注意:Log是同步方法,异步邮件发送需要同步等待完成 _emailSender.SendEmailAsync(config.EmailToSendTo, "Admin exception", exception.ToString()) .GetAwaiter() .GetResult(); } }
4. 注册依赖并配置日志
在ConfigureServices中,先注册IEmailSender的实现,再配置EmailLogger:
public void ConfigureServices(IServiceCollection services) { // 1. 先注册IEmailSender的实现(比如你的AuthMessageSender) services.AddSingleton<IEmailSender, AuthMessageSender>(sp => new AuthMessageSender(/* 这里传入你的构造参数,比如从Configuration里取 */)); // 2. 配置日志 services.AddLogging(loggingBuilder => { var loggingSection = Configuration.GetSection("Logging"); loggingBuilder.AddFile(loggingSection); loggingBuilder.AddConsole(); loggingBuilder.AddDebug(); // 配置EmailLogger loggingBuilder.AddEmailLogger(c => { c.EmailToSendTo = Configuration["Logging:Email:EmailToSendTo"]; c.EventId = 100; // 根据需要设置 }); }); // 其他服务注册... }
关键注意点
- 不要把服务实例放到配置类中:配置类的作用是存储可配置的参数,服务应该通过依赖注入获取。
- 处理异步方法的同步调用:日志的
Log方法是同步的,所以调用异步的SendEmailAsync时需要用GetAwaiter().GetResult()来等待完成(如果担心阻塞,可以考虑用后台任务队列,但简单场景下同步等待足够)。 - 服务生命周期匹配:
EmailLoggingProvider是注册为Singleton的,所以注入的IEmailSender最好也是Singleton生命周期,避免生命周期不匹配的问题。
内容的提问来源于stack exchange,提问作者Speed
相关产品推荐
相关产品推荐

