I am lifting some discussion that was briefly started by @brockallen in #31 into its own issue.
I have been using the IOptions
API for a while now in the context of application settings and it just feels convoluted for no reason. I have the impression that I repeat meaningless, verbose, boilerplate code because... well because the framework wants me to.
Basically, anywhere a component needs to adjust its behaviour based on a setting (typically stored in config.json
), you see a pattern similar to:
class FrobController
{
private readonly AppSettings settings;
public FrobController(IOptions<AppSettings> options)
{
this.settings = options.Options; // or options.Value in beta-8 I think
}
public void FrobMe()
{
for (int i = 0; i < settings.FrobbingTimes; i++)
FrobOnce();
}
}
And if you use settings in many places or have split your settings in several classes by domain, you repeat that ctor code quite a lot. What bothers me?
- What the hell is this
IOptions<>
? Vague naming is vague. And it's not the options since they are in AppSettings
, actually.
- What is it about? Why should I use it? What does it buy me? In this use-case: nothing!
- Given its generic name and its non-existent added value, it's hard to discover. Trust me, the first time you encounter this you try to DI
AppSettings
. And it fails. Then you Google.
- The dance around
.Options
or .Value
is superfluous.
- The naming is awkward. What should I call the parameter
IOptions
? Any name I could come up with is either long for what it's worth (optionsAccessor
) or misleading/clashing with the actual options object (AppSettings
).
In #31 @lodejard said:
it is very similar, but the accessor service has advantages in that the creation of the singleton is deferred to when it's first used.
Deferred creation is something that needs a general solution, not a domain-specific one. And the solution is injecting Lazy<T>
and registering a factory for T
. And given the smallish nature of application settings in general and the fact that they are guaranteed to be used, the lazy initialization is probably more overhead than benefit.
Plus it enables the pattern where multiple IConfigureOptions can be added as services to alter any given TOptions.
Maybe for middleware configs or other advanced stuff. I don't see how that would be a use case for application settings. The use-case here is really to parse config into a strongly-typed object and make it available to the whole app.
With a singleton MyOptions added to DI it must be fully baked before it is added, and the things doing the options configuration all need to do their work before IOC is available.
Not necessarily. Alternative design can surely be considered, like having the "things" mutate the options after the fact, or registering a factory that applies the "things" just in time when creating the actual instance. Anyway I don't see how this is pertinent for app settings.
My point is that probably those things are important for other scenarios (middleware configuration?) but I think the typed application settings use case should be simplified. What feels right is injecting the typed class directly.