... Or a different way of implementing this:
I have an interface called ICommunicationProvider
which is then extended by
ISMSCommunicationProvider
, IEmailCommunicationProvider
, IWhatsAppCommunicationProvider
, etc.
Each of those interfaces is a blueprint for implementation of different messaging providers, for SMS I might use Twilio, for Email I might want to use MailChimp of SMTP, etc.
I created a class, implementation of ISMSCommunicationProvider
called TwilioSMSCommunicationProvider
which will be used to create instances of Twilio Communication Providers in the system. There could be multiple of Twilio Communication Providers, each having different API key, SMS number etc - but all of them will have the same CommunicationProviderCode
that links them to TwilioSMSCommunicationProvider
implementation and also SettingsKeys
dictionary that defines the amount of fields for settings, lets say 4.
Now, I also want to create TextLocalSMSCommunicationProvider
which will also be an implementation of ISMSCommunicationProvider
, will also have CommunicationProviderCode
field, different than Twilio implementation, and SettingsKeys
dictionary but with different amount of fields for settings.
How can I make sure that systemCode
and settings
are always implemented in every class that extends ISMSCommunicationProvider
if those fields MUST be static?
They must be static because I am using reflection to populate dropdowns with those implementations, when instances of Communication Providers are being added to the System/DB. I can't instantiate those implementations because all I need is the CommunicationProviderCode
and settingsKeys
dictionary to know what settings fields I need to create.
Example below shows the Implementation with some static fields at the top that I am currently using to do what I need to do, but it's ugly - because I will have to REMEMBER to add them to every implementation of ISMSCommunicationProvider
rather than being able to use the Interface to force their presence. If they were properties - I wouldn't be able to fetch them via reflection, as I'd have to create instances...
Is there a cleaner way of achieving what I need to achieve?
Example:
ICommunicationProvider
public interface ICommunicationProvider
{
/// <summary>
/// Communication Provider.
/// </summary>
CommunicationProvider CommunicationProvider { get; }
}
ISMSCommunicationProvider
public interface ISMSCommunicationProvider : ICommunicationProvider
{
void Send(ISMS sms);
Task SendAsync(ISMS sms, bool isExceptionReport = false);
Task<bool> ReportException(Exception ex, bool isReceive);
}
TwilioSMSCommunicationProvider
public class TwilioSMSCommunicationProvider : ISMSCommunicationProvider
{
public static readonly string[] SettingsKeys = { "AccountSID", "AuthToken", "SMSNumber"};
public static readonly string CommunicationProviderCode = "TwilioSMSProvider";
public static readonly string CommunicationProviderName = "Twilio SMS Provider";
public static readonly CommunicationType CommunicationProviderType = CommunicationType.SMS;
private readonly Dictionary<string, string> communicationProviderSettings;
private readonly ICommunicationProviderService communicationProviderService;
/// <summary>
/// Constructor
/// </summary>
/// <param name="communicationProvider"></param>
/// <param name="communicationProviderService"></param>
public TwilioSMSCommunicationProvider(CommunicationProvider communicationProvider, ICommunicationProviderService communicationProviderService)
{
this.CommunicationProvider = communicationProvider;
this.communicationProviderService = communicationProviderService;
this.communicationProviderSettings = communicationProviderService.GetSettingsForCommunicationProvider(SettingsKeys);
TwilioClient.Init(this.communicationProviderSettings["AccountSID"], this.communicationProviderSettings["AuthToken"]);
}
public CommunicationProvider CommunicationProvider { get; }
/// <summary>
/// Send SMS
/// </summary>
/// <param name="sms"></param>
public void Send(ISMS sms)
{
var message = MessageResource.Create(
body: sms.BodyText,
from: new Twilio.Types.PhoneNumber(this.communicationProviderSettings["SMSNumber"]),
to: new Twilio.Types.PhoneNumber(sms.To)
);
Console.WriteLine(message.Sid);
}
To add some extra reference as to what I need to do; I first choose the Provider Type:
Once a Provider Type box is populated, the second select box fetches all the implementations of ICommunicationProvider
via reflection that have CommunicationType.SMS
enum in its static field, uses another two static fields CommunicationProviderName
and CommunicationProviderCode
and populates the dropdown:
Now, After filling the other details and saving, a CommunicationProvider
is created in the DB along with empty settings that have also been declared in a static field in TwilioSMSCommunicationProvider
called SettingsKeys
, creates those settings in the DB to be filled in with values by the user:
The general idea of the end result is - Creating an implementation of either SMS or Email or WhatsApp communication provider interface in code, will be the only thing I will need to do to create brand new CommunicationProviders that utilise this implementation. The implementation dictates what settings I need for it and has all the details I need for it to appear in the select boxes etc. All I need to do is create new class, fill in the details in that class, implement methods - and it just works.
As you can see, it needs a lot of static fields in the implementations, which essentially should be present in each implementation of ISMSCommunicationProvider
, they will just have different values. If factory pattern can achieve this in a much cleaner way - please can you point me in the right direction?