2
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.