I have a .NET application with a .config file that has content like this:
<appSettings>
<add key="SmtpConfiguration" value="Db" />
</appSettings>
Inside the application's code I define an ISmtpConfiguration
interface and several different implementations. DbSmtpConfiguration
gets configuration data from a database, AppSettingsSmtpConfiguration
reads from some other entries in the .config, and DefaultSmtpConfiguration
has values written directly in the code.
In my service provider configuration, I have this:
switch (ConfigurationManager.AppSettings["SmtpConfiguration"])
{
case "Db":
services.AddSingleton<ISmtpConfiguration, DbSmtpConfiguration>();
break;
case "AppSettings":
services.AddSingleton<ISmtpConfiguration, AppSettingsSmtpConfiguration>();
break;
default:
services.AddSingleton<ISmtpConfiguration, DefaultSmtpConfiguration>();
}
I'm not thrilled with that, so I have created a factory:
public class SmtpConfigurationFactory
{
public static ISmtpConfiguration GetSmtpConfiguration()
{
var type = Type.GetType(ConfigurationManager.AppSettings["SmtpConfiguration"] + "SmtpConfiguration");
if (type == null)
{
type = typeof(DefaultSmtpConfiguration);
}
return (ISmtpConfiguration) Activator.CreateInstance(type);
}
}
I then use the factory like this:
services.AddSingleton(SmtpConfigurationFactory.GetSmtpConfiguration());
I'm not terribly thrilled with that either. My final thought was to change consumers of ISmtpConfiguration
to accept SmtpConfigurationFactory
instead, make the method not static, and then add the factory to my services:
services.AddSingleton<SmtpConfigurationFactory>();
Then the constructor for the consumer looks like:
public SmtpEmailService(SmtpConfigurationFactory configurationFactory)
{
Configuration = configurationFactory.GetSmtpConfiguration();
}
I like that the best, but is there a better solution? The factory introduces the issue that implementations of ISmtpConfiguration
cannot require dependencies of their own. DbSmtpConfiguration
for example couldn't receive an IDbConfiguration
telling it which database to connect to.
1 Answer 1
Since the value that determines what type to use doesn't change I personally would prefer your first solution of a switch statement in the registration.
The current implementation of the factory pattern would lose any type safety. If a parameter gets added to the constructor then then the Activator.CreateInstance would fail. For example the database one might take in a connection string or sql object while the default one wouldn't. Plus a naming convention would need to be understood and knowledge passed down in maintenance mode. As the saying goes juice not worth the squeeze.
If the value to return could change based on a parameter being passed in then I would agree factory pattern could be used but as it stands I think the switch statement in the container registration is the simplest and meets the requirement.
There is still ways to create the factory pattern that would give type safety and DI but it ends up looking a lot like the switch statement.
Explore related questions
See similar questions with these tags.