How to, using dependency injection, get configurat

2019-02-06 21:00发布

I'm using Simple Injector, but maybe what I need is more of a conceptual answer.

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, one would usually have a class which implements IApplicationSettings, getting each field from a specified source, for instance:

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

HOWEVER! Let's say I want to get EnableLogging from app.config, CopyLocal from database, and ServerName from another implementation which gets the current computer name. I want to be able to mix-match my app configuration without having to create 9 implementations, one for each combination.

I'm assuming that I can't pass any parameters because the interfaces are resolved by the injector (container).

I thought of this, 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, with this I have one main problem: How do I know how to create an instance of TEnableLogging (which is a IGetValue<bool>)? Oh, assume that IGetValue<bool> is an interface which has a Value property, which will be implemented by the concrete class. But the concrete class may need some specifics (like what's the name of the key in app.config) or not (I may simply want to return always true).

I'm relatively new to dependency injection, so maybe I'm thinking in a wrong way. Does anyone have any ideas on how to accomplish this?

(You may answer using another DI library, I won't mind. I think I just need to grab the concept of it.)

1条回答
贼婆χ
2楼-- · 2019-02-06 21:38

You are definitely heading the wrong way here.

Some years ago I built an application that contained an interface much like your IApplicationSettings. I believe I named it IApplicationConfiguration, but it contained all application's configuration values as well.

Although it helped me make my application testable at first, after some time the design started to get in the way. A lot of implementations depended on that interface, but it kept changing a lot and with it the implementation, and the test version.

Just like you I implemented some lazy loading, but this had a terrible down side. When one of the configuration values was missing, I only found out that it did when the value was called for the first time. This resulted in a configuration that was hard to verify.

It took me a couple of iterations of refactoring to find out what the core of the problem was. Big interfaces are a problem. My IApplicationConfiguration class was violating the Interface Segregation Principle and the result was poor maintainability.

In the end I found out that this interface was completely useless. Besides violating the ISP, those configuration values described an implementation detail and instead of making an application wide abstraction, it is much better to supply each implementation directly with the configuration value they need, and only the values they need.

When you do this, the easiest thing to do is to wrap those values into a Parameter Object (even if it is just one value), and inject those configuration values into the constructor. Here's an ecample:

var enableLogging =
    Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]);

container.RegisterSingleton(new LoggerSettings(loggingEnabled: enableLogging));

In this case, LoggerSettings is a configuration object specific to Logger, which requires it as constructor argument.

When doing this, the enableLogging value is read just once from the configuration file and is done so during application startup. This makes it fast and makes it fail at application startup when the value is missing.

查看更多
登录 后发表回答