问题
I use IoC (DI) approach and usually have parameters, which are being read from configuration settings (i.e. connection strings, static values etc) by the lowest layer (DB layer etc). What is the best way to do it?
Read directly in this the lowest layer, i.e.:
string sendGridApiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
It works, but need to add also this key to config file of unit test project. Also, assembly depends on configuration file
- Read it in the highest layer (i.e. web application) and throw as parameter from the all layers? It will work, but all middle layers will get parameters, which are not used (so, they will be depended on things, which are not used).
Also there is a problem when different implementations of the the lowest layers can require different parameters. I.e. SendMail1 can require SMTP/login/password, but SendMail2 can require only ApiKey, but SendMail1 and SendMail2 should implement the same interface. So, it creates difficulties to use approach #2
回答1:
option 1 starts out as a simpler solution, but pretty soon ends up being difficult for testing, needing references, breaking a pattern around flowing values from highest to lowest layers etc.
The recommended pattern is #2, where the highest layer sends down all dependencies and their values to the lower layers.
Even though you have to pass it down across all layers, your DI engine should help you here in terms of automatic chained resolution.
e.g.
If your controller needs to instantiate a Business Layer class, which needs to instantiate a Repository class, which needs a Connection class, which needs the setting value, you don't need to manually do it at 3 places.
You can define registrations of BL class, Repository class and Connection class separately in the DI engine, and it'll take care of instantiating a controller for you.
It may look tedious, but normally has great benefits in the long run. (in terms of clear contract definition, unit testing, no anti-pattern, isolated concerns etc.)
And if you are really worried about passing it 3 places, there are various options in terms of Factories and Aggregate services. Each have their pros/cons and depend on the DI engine you are using. Let us know if option 2, is absolutely unacceptable.
e.g. Autofac allows you to wrap a lot of constructor parameters into a Single Aggregate service interface, so that Autofac can inject that for you.
回答2:
Neither approach you've outlined works well - first (read configuration in services) prevents unit testing as you've mentioned, second (pass config from top level) requires knowledge of all possible implementations of each service by top layer.
I like approaches that rely on DI container's knowledge of both configuration storage and types of objects registered for each interface:
pass configuration during registration time - i.e. if container supports registering factory methods such factory method can read configuration and than invoke particular constructor of concrete service
// constructor: publc ConcreteServiceX(int setting1, string setting2)... container.RegisterFactory<IServiceX>( container => return new ConcreteServiceX(42, ReadSetting("X"));
register configuration for each of the service in container as class/interface
// constructor: publc ConcreteServiceX(IConcreteServiceXSettings settings)... container.RegisterType<IService,ConcreteServiceX>(); container.RegisterInstance<IConcreteServiceXSettings>( new ConcreteServiceXSettings(42, ReadSetting("X"));
Both approaches localize knowledge of configuration system to one place (container configuration) and allow easier unit testing of each service (no dependency on type of configuration storage) as well as higher level objects (no need to know any settings for services).
Note: sample use Unity-like syntax, adopt to container of your choice
来源:https://stackoverflow.com/questions/35530725/configuration-settings-and-ioc