0

I am working on email service which can send emails using IMAP, SMTP or third-party software (like SendGrid).

And I need to determine which service will handle the process of sending at runtime, because the user will specify which provider will be used (in the request there is a property called ProviderType).

So, what is the best approach of implementing this process.

I was trying to implement an interface called IEmailSender depending on the provider type, but I've failed.

I am using .Net core 6

James.mikael
  • 99
  • 1
  • 9
  • 1
    Maby share some code, of things you have allready tried so we get a more clear understanding of your problem. – Leroy Meijer Oct 26 '22 at 15:04
  • You can try having one interface `IEmailSender` and have implementations for each provider type. Then have a factory method which returns `IEmailSender` and accepts the provider type value from your request. U can have it as a class with a dependency on `IServiceProvider` to get the instances via dependency injection. – Gerald Chifanzwa Oct 27 '22 at 05:42
  • @GeraldChifanzwa I think it is the solution, please can you provide the code I need to write in the factory class? – James.mikael Oct 27 '22 at 09:01
  • @James.mikael see my posted answer with a rough code sample – Gerald Chifanzwa Oct 27 '22 at 09:39

1 Answers1

2

Here is a very crude implementation, but should get you the idea.

Declare your interface for email sender, e.g.

public interface IEmailSender
{
   Task SendAsync(string to, string body);
}

Add implementation classes for each flavour, e.g.

public class SmtpEmailSender : IEmailSender
{
    private readonly IOptions<SmtpOptions> _options;
    private readonly ISmtpClient _client;

    public SmtpEmailSender(IOptions<SmtpOptions> options, ISmtpClient client)
    {
        _options = options;
        _client = client;
    }
    public async Task SendAsync(string to, string body)
    { /* smtp logic */ }
}

public class SendGridEmailSender : IEmailSender
{
    private readonly ISendGridClient _sendGridClient;

    public SendGridEmailSender(ISendGridClient sendGridClient)
    { _sendGridClient = sendGridClient; }
    public async Task SendAsync(string to, string body) { /* smtp logic*/ }
}

public class IMapEmailSender : IEmailSender
{
    public async Task SendAsync(string to, string body) { ... }
}

Each of these can have its own dependencies as you wish.

Add a factory class (with an interface too if you want. left that out to keep it simple)

public class EmailSenderFactory
{
    private readonly IServiceProvider _serviceProvider;
    public EmailSenderFactory(IServiceProvider serviceProvider)
    { _serviceProvider = serviceProvider; }
    public IEmailSender GetEmailSender(string senderType)
    {
        return senderType switch
        {
            "smtp"     => _serviceProvider.GetRequiredService<SmtpEmailSender>(),
            "sendgrid" => _serviceProvider.GetRequiredService<SendGridEmailSender>(),
            "imap"     => _serviceProvider.GetRequiredService<IMapEmailSender>(),
            "other"    => _serviceProvider.GetRequiredService<OtherType>(),
            _          => throw new Exception("No sender configured for this") // handle case of provider not found 
        };
    }
}

Register each of the implementations, including the factory class.

builder.Services.AddScoped<SendGridEmailSender>(); // or service.Add if in Startup.cs
builder.Services.AddScoped<IMapEmailSender>();
builder.Services.AddScoped<SmtpEmailSender>();
builder.Services.AddScoped<EmailSenderFactory>();

In your usage, inject EmailSenderFactory and resolve an IEmailSender from there.

public class SomeController
{
    private readonly EmailSenderFactory _factory;

    public SomeController(EmailSenderFactory factory)
    { _factory = factory; }

    [HttpPost]
    public async Task<ActionResult> SendEmail(RequestModel requestModel)
    {
        var sender = _factory.GetEmailSender(requestModel.ProviderType);
        await sender.SendAsync(...);
    }
}
Gerald Chifanzwa
  • 1,277
  • 7
  • 14