Home > Back-end >  How to get an email provider into a logger using DI in ASP.NET Core?
How to get an email provider into a logger using DI in ASP.NET Core?

Time:11-30

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/dotnet/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.

CodePudding user response:

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.

  • Related