How, using dependency injection, get configuration from multiple sources?

I use Simple Injector, but maybe I need a conceptual answer more.

Here's the deal, suppose I have an interface with my application settings:

public interface IApplicationSettings { bool EnableLogging { get; } bool CopyLocal { get; } string ServerName { get; } } 

Then there will usually be a class that implements IApplicationSettings, retrieving each field from the specified source, for example:

 public class AppConfigSettings : IApplicationSettings { private bool? enableLogging; public bool EnableLogging { get { if (enableLogging == null) { enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]; } return enableLogging; } } ... } 

BUT! Let's say I want to get EnableLogging from app.config, CopyLocal from the database, and ServerName from another implementation that gets the current computer name. I want to be able to match the configuration of my application without the need to create 9 implementations, one for each combination.

I assume that I can not pass any parameters, because the interfaces are allowed by the injector (container).

I thought about it, initially:

 public interface IApplicationSettings<TEnableLogging,TCopyLocal,TServerName> where TEnableLogging : IGetValue<bool> where TCopyLocal : IGetValue<bool> where TServerName : IGetValue<string> { TEnableLogging EnableLog{get;} TCopyLocal CopyLocal{get;} TServerName ServerName{get;} } public class ApplicationSettings<TEnableLogging,TCopyLocal,TServerName> { private bool? enableLogging; public bool EnableLogging { get { if (enableLogging == null) { enableLogging = Container.GetInstance<TEnableLogging>().Value } return enableLogging; } } } 

However, I have one main problem: how do I know how to create an instance of TEnableLogging (which is IGetValue<bool> )? Suppose IGetValue<bool> is an interface that has a Value property that will be implemented by a particular class. But a particular class may need some features (for example, that the key name is in app.config) or not (I may just want to always return true).

I'm relatively new to dependency injection, so maybe I'm wrong. Does anyone have any ideas on how to do this?

(You can answer using another DI library, I will not mind. I think I just need to understand its concept.)

+6
source share
1 answer

You are definitely heading the wrong way here.

A few years ago, I created an application containing an interface similar to your IApplicationSettings . I believe that I called it IApplicationConfiguration , but it also contained all the application configuration values.

Despite the fact that it helped me check the application first, after a while the design started to get in the way. Many implementations depended on this interface, but it was constantly changing, and with it the implementation and the test version.

Like you, I implemented lazy loading, but it was awful. When one of the configuration values ​​was missing, I only found out that this happened when the value was called for the first time. This led to a complex configuration check .

It took me several iterations of refactoring, what is the essence of the problem. Large interfaces are a problem. My IApplicationConfiguration class violated the principle of Interface Segregation , and the result was poor maintainability.

In the end, I found out that this interface is completely useless. In addition to breaking the ISP, these configuration values ​​described implementation details, and instead of creating a broad abstraction of the application, it was much better to provide each implementation directly with the necessary configuration value.

When you do this, it is easiest to pass this configuration value as a property value. With Simple Injector, you can use the RegisterInitializer method to do this:

 var enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]); container.RegisterInitializer<Logger>(logger => { logger.EnableLogging = enableLogging; }); 

In this case, the enableLogging value enableLogging read only once from the configuration file and is executed at application startup time. This speeds up the work and crashes when the application starts when there is no value.

If for some reason you need to delay reading (for example, from a database), you can use Lazy<T> :

 Lazy<bool> copyLocal = new Lazy<bool>(() => container.GetInstance<IDatabaseManager>().RunQuery(CopyLocalQuery)); container.RegisterInitializer<FileCopier>(copier => { copier.CopyLocal = copyLocal.Value; }); 

Instead of passing values ​​using properties, you can also use constructor arguments, but it's a little more complicated. Take a look at this article for some ideas.

If you find that you are registering the same configuration value for many registrations, you probably lack abstraction. Take a look at this:

 container.RegisterInitializer<UserRepository>(rep => { rep.ConnectionString = connectionString; }); container.RegisterInitializer<OrderRepository>(rep => { rep.ConnectionString = connectionString; }); container.RegisterInitializer<CustomerRepository>(rep => { rep.ConnectionString = connectionString; }); container.RegisterInitializer<DocumentRepository>(rep => { rep.ConnectionString = connectionString; }); 

In this case, you probably are missing the IDatabaseFactory or IDatabaseManager , and you should do something like this:

 container.RegisterSingle<IDatabaseFactory>(new SqlDatabaseFactory(connectionString)); 
+11
source

All Articles