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));