Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: AddOptions<T>() vs. Multiple Configure<T>(…) #514

Closed
smarts opened this issue Nov 16, 2018 · 5 comments
Closed

Question: AddOptions<T>() vs. Multiple Configure<T>(…) #514

smarts opened this issue Nov 16, 2018 · 5 comments

Comments

@smarts
Copy link

smarts commented Nov 16, 2018

I've been writing code like the following in my projects in Startup.cs:

// this is from ConfigureServices, so services is an instance of IServiceCollection
// assume Configuration is an instance of IConfiguration and was injected via the constructor
services.AddOptions()
  .Configure<MyOptions>(Configuration.GetSection("mySection"))
  .Configure<MyOptions>(x => x.SomeProperty = "foo");

and i'm wondering if I should expect this to continue working, or if it is just coincidence and I should switch to the following:

services.AddOptions()
  .AddOptions<MyOptions>()
  .Bind(Configuration.GetSection("mySection"))
  .Configure(x => x.SomeProperty = "foo");

Some more context: the reason i'm asking this is because i see that in the first example both of those Configure<TOptions>(…) methods use services.AddSingleton<IConfigureOptions<TOptions>>(…) under the hood, and -- although each uses a different type for the implementation instance -- i was under the impression that the number of registrations for a service type is restricted to one, either by ServiceCollection or ServiceProvider

@poke
Copy link

poke commented Nov 17, 2018

Configure<TOptions>(Action<TOptions> configureOptions) and OptionsBuilder<TOptions>.Configure(Action<TOptions> configureOptions) will both end up doing the same thing:

services.AddSingleton<IConfigureOptions<TOptions>>(
    new ConfigureNamedOptions<TOptions>(name, configureOptions));

And OptionsBuilder<TOptions>.Bind(IConfiguration config) will actually call Configure<TOptions>(IConfiguration config) directly, so they are also equivalent.

So both APIs are interchangeable. And you can expect the services.Configure calls to continue to work. The options builder API came later to allow for a bit more control with various utility methods. But it’s not a replacement for the direct services.Configure API.

I was under the impression that the number of registrations for a service type is restricted to one

No, you can register any number of implementations for the same service type. While it is true that the normal service resolution will only give you a single instance, which will then be the latest registered dependency for that type, the options framework will resolve an IEnumerable<IConfigureOptions<TOptions>> which will make the DI container provide all registered implementations for that type. So the framework can then work through them in order and apply all registered configuration sources on the options object.

@smarts
Copy link
Author

smarts commented Dec 12, 2018

Thanks for the reply @poke . Doesn't resolving IEnumerable<T> require using [Try]AddEnumerable<T>(…) when registering a service? (And this doesn't, because it uses AddSingleton<T>(…))

@poke
Copy link

poke commented Dec 13, 2018

The only special thing TryAddEnumerable does is that it ensures that you are not accidentally adding the same implementation type multiple times. So when adding something, it checks the collection first.

But no, resolving with IEnumerable<T> will give you all implementations that were registered as T regardless of how that happened. So if you have multiple AdsSingleton<T> calls, you will be able to get all those instances by injecting the enumerable.

If you just inject T, then you will only get the implementation that was registered last.

@smarts
Copy link
Author

smarts commented Dec 13, 2018

Thanks! That explanation was very helpful. One last question: I assume the "you will only get the implementation that was registered last" behavior is dependent on the IServiceProvider implementation, yes?

@poke
Copy link

poke commented Dec 13, 2018

Hmm, I’m not too sure about other implementations and how much of that behavior is required by IServiceProvider. I think what is true for all DI containers is that the last registration wins. So when you inject T, then the last registration for T will be what you get. This is to allow overwriting registrations.

Some containers may then choose not to make multiple registrations for T available as IEnumerable<T>. I think SimpleInjector does not do this; there, you have to register the collection individually.

But since ASP.NET Core requires this behavior for some of the things to work, I would assume that you can expect this to work for all DI containers that integrate with ASP.NET Core.

@smarts smarts closed this as completed Dec 13, 2018
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants