Dependency Injection and AppSettings

前端 未结 2 578
感情败类
感情败类 2020-12-16 00:38

Let\'s say I am defining a browser implementation class for my application:

class InternetExplorerBrowser : IBrowser {
    private readonly string executable         


        
相关标签:
2条回答
  • 2020-12-16 01:13

    The individual classes should be as free from infrastructure as possible - constructs like IAppSettings, IMyClassXAppSettings, and [AppSetting] bleed composition details to classes which, at their simplest, really only depend on raw values such as executablePath. The art of Dependency Injection is in the factoring of concerns.

    I have implemented this exact pattern using Autofac, which has modules similar to Ninject and should result in similar code (I realize the question doesn't mention Ninject, but the OP does in a comment).

    Modules organize applications by subsystem. A module exposes a subsystem's configurable elements:

    public class BrowserModule : Module
    {
        private readonly string _executablePath;
    
        public BrowserModule(string executablePath)
        {
            _executablePath = executablePath;
        }
    
        public override void Load(ContainerBuilder builder)
        {
            builder
                .Register(c => new InternetExplorerBrowser(_executablePath))
                .As<IBrowser>()
                .InstancePerDependency();
        }
    }
    

    This leaves the composition root with the same problem: it must supply the value of executablePath. To avoid the configuration soup, we can write a self-contained module which reads configuration settings and passes them to BrowserModule:

    public class ConfiguredBrowserModule : Module
    {
        public override void Load(ContainerBuilder builder)
        {
            var executablePath = ConfigurationManager.AppSettings["ExecutablePath"];
    
            builder.RegisterModule(new BrowserModule(executablePath));
        }
    }
    

    You could consider using a custom configuration section instead of AppSettings; the changes would be localized to the module:

    public class BrowserSection : ConfigurationSection
    {
        [ConfigurationProperty("executablePath")]
        public string ExecutablePath
        {
            get { return (string) this["executablePath"]; }
            set { this["executablePath"] = value; }
        }
    }
    
    public class ConfiguredBrowserModule : Module
    {
        public override void Load(ContainerBuilder builder)
        {
            var section = (BrowserSection) ConfigurationManager.GetSection("myApp.browser");
    
            if(section == null)
            {
                section = new BrowserSection();
            }
    
            builder.RegisterModule(new BrowserModule(section.ExecutablePath));
        }
    }
    

    This is a nice pattern because each subsystem has an independent configuration which gets read in a single place. The only benefit here is a more obvious intent. For non-string values or complex schemas, though, we can let System.Configuration do the heavy lifting.

    0 讨论(0)
  • 2020-12-16 01:35

    I'd go with the last option - pass in an object that complies with the IAppSettings interface. In fact, I recently performed that refactor at work in order to sort out some unit tests and it worked nicely. However, there were few classes dependent on the settings in that project.

    I'd go with creating a single instance of the settings class, and pass that in to anything that's dependant upon it. I can't see any fundamental problem with that.

    However, I think you've already thought about this and seen how it can be a pain if you have lots of classes dependent on the settings.

    If this is a problem for you, you can work around it by using a dependency injection framework such as ninject (sorry if you're already aware of projects like ninject - this might sound a bit patronizing - if you're unfamiliar, the why use ninject sections on github are a good place to learn).

    Using ninject, for your main project you can declare that you want any class with a dependency on IAppSettings to use a singleton instance of your AppSettings based class without having to explicitly pass it in to constructors everywhere.

    You can then setup your system differently for your unit tests by stating that you want to use an instance of MockAppSettings wherever IAppSettings is used, or by simply explicitly passing in your mock objects directly.

    I hope I've got the gist of your question right and that I've helped - you already sound like you know what you're doing :)

    0 讨论(0)
提交回复
热议问题