.NET Core - what is the best practice of implementing an interface due to conditions at runtime

93 Views Asked by At

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

1

There are 1 best solutions below

0
Gerald Chifanzwa On

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(...);
    }
}