简体   繁体   中英

How to get an email provider into a logger using DI in ASP.NET Core?

Sorry this is a bit new to me so I don't quite 'get it'.

I already have a logging provider

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddLogging(loggingBuilder =>
            {
                var loggingSection = Configuration.GetSection("Logging");
                loggingBuilder.AddFile(loggingSection);
                loggingBuilder.AddConsole();
                loggingBuilder.AddDebug();

I am using the package NReco.Logging.File to define AddFile etc.

I want to make it so that exceptions are emailed to me too. So I followed https://learn.microsoft.com/en-us/do.net/core/extensions/custom-logging-provider to create a custom logger.

 public sealed class EmailLoggerConfiguration
    {
        public int EventId { get; set; }

        public string EmailToSendTo { get; set; }
        public IEmailSender EmailSender { get; set; }
    }
    internal class EmailLoggingProvider : ILoggerProvider
    {
        private readonly IDisposable? _onChangeToken;
        private EmailLoggerConfiguration _currentConfig;
        private readonly ConcurrentDictionary<string, EmailLogger> _loggers =
            new(StringComparer.OrdinalIgnoreCase);
        private readonly IEmailSender emailSender;

        public EmailLoggingProvider(
            IOptionsMonitor<EmailLoggerConfiguration> config)
        {
            _currentConfig = config.CurrentValue;
            _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
            
        }

        public ILogger CreateLogger(string categoryName) =>
            _loggers.GetOrAdd(categoryName, name => new EmailLogger(name, GetCurrentConfig ));

        private EmailLoggerConfiguration GetCurrentConfig() => _currentConfig;

        public void Dispose()
        {
            _loggers.Clear();
            _onChangeToken?.Dispose();
        }
    }

    internal class EmailLogger : ILogger
    {
        private readonly string categoryName;
        private Func<EmailLoggerConfiguration> getCurrentConfig;
        IEmailSender emailSender;


        public EmailLogger(string categoryName, Func<EmailLoggerConfiguration> getCurrentConfig)
        {
            this.getCurrentConfig = getCurrentConfig;
            this.categoryName = categoryName;
            
        }

        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 emailTo = getCurrentConfig().EmailToSendTo;
            //var emailServer = getCurrentConfig().EmailSender;
            if (!String.IsNullOrEmpty(emailTo) && exception != null)
            {
                emailSender.SendEmailAsync(emailTo, "Admin exception", exception.ToString());
            }
        }
    }


    public static class EmailLoggingExtensions
    {
        public static ILoggingBuilder AddEmailLogger(
            this ILoggingBuilder builder)
        {
            builder.AddConfiguration();

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EmailLoggingProvider>());

            LoggerProviderOptions.RegisterProviderOptions<EmailLoggerConfiguration, EmailLoggingProvider>(builder.Services);

            return builder;
        }

        public static ILoggingBuilder AddEmailLogger(
            this ILoggingBuilder builder,
            Action<EmailLoggerConfiguration> configure)
        {
            builder.AddEmailLogger();
            builder.Services.Configure(configure);

            return builder;
        }
    }

You can see that EmailLogger.Log requires emailSender which should be an IEmailSender but I cannot figure out how to get it there using DI.

I realise that you can chain dependencies in DI but???? I don't see how in this context.

I tried this

                loggingBuilder.AddEmailLogger(c =>
                {
                    c.EmailToSendTo = Configuration["Logging:Email:EmailToSendTo"];
                    c.EmailSender = new AuthMessageSender(????, Configuration);
                });

but that didn't help and wouldn't even be right anyway.

In fact, by default, EmailSender is the implementation method of IEmailSender , which is used to call the SendEmailAsync() method. You don't need to go and set c.EmailSender = xxx .

You can consider the following dependency injection approach:

public interface IEmailSender
{
    Task SendEmailAsync(string email, string subject, string message);
}
public class EmailSender : IEmailSender
{
    //...
    private readonly ILogger<EmailSender> logger;
    public EmailSender(ILogger<EmailSender> logger) {
        //...
        this.logger = logger;
    }

    public Task SendEmailAsync(string email, string subject, string message) {
        //...
    }
}

At this point, IEmailSender will exist as a custom interface instead of inheriting from Microsoft.AspNetCore.Identity.UI.Services .

And you need to register it as a service:

services.AddTransient<IEmailSender, EmailSender>();

Helpful links:

Add ILogger to send email service

Should I use IEmailSender?

Using IEmailSender from Configure() in my Startup.cs file

Hope this will help you better understand IEmailSender and dependency injection.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM