-
Notifications
You must be signed in to change notification settings - Fork 68
Options 2.0 Iteration 3 - Breaking change edition: Takeover IOptions #179
Changes from all commits
40eb483
5fd3090
c7bca40
3cdfe99
9600087
4ba6b21
563ea44
145337c
e80c497
3d10f70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.Extensions.Options | ||
{ | ||
/// <summary> | ||
/// Implementation of IConfigureNamedOptions. | ||
/// </summary> | ||
/// <typeparam name="TOptions"></typeparam> | ||
public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions>, IConfigureOptions<TOptions> where TOptions : class | ||
{ | ||
/// <summary> | ||
/// Constructor. | ||
/// </summary> | ||
/// <param name="name">The name of the options.</param> | ||
/// <param name="action">The action to register.</param> | ||
public ConfigureNamedOptions(string name, Action<TOptions> action) | ||
{ | ||
Name = name; | ||
Action = action; | ||
} | ||
|
||
/// <summary> | ||
/// The options name. | ||
/// </summary> | ||
public string Name { get; } | ||
|
||
/// <summary> | ||
/// The configuration action. | ||
/// </summary> | ||
public Action<TOptions> Action { get; } | ||
|
||
/// <summary> | ||
/// Invokes the registered configure Action if the name matches. | ||
/// </summary> | ||
/// <param name="name"></param> | ||
/// <param name="options"></param> | ||
public virtual void Configure(string name, TOptions options) | ||
{ | ||
if (options == null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
|
||
// Null name is used to configure all named options. | ||
if (Name == null || name == Name) | ||
{ | ||
Action?.Invoke(options); | ||
} | ||
} | ||
|
||
public void Configure(TOptions options) => Configure(Options.DefaultName, options); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.Extensions.Options | ||
{ | ||
/// <summary> | ||
/// Represents something that configures the TOptions type. | ||
/// </summary> | ||
/// <typeparam name="TOptions"></typeparam> | ||
public interface IConfigureNamedOptions<in TOptions> : IConfigureOptions<TOptions> where TOptions : class | ||
{ | ||
/// <summary> | ||
/// Invoked to configure a TOptions instance. | ||
/// </summary> | ||
/// <param name="name">The name of the options instance being configured.</param> | ||
/// <param name="options">The options instance to configure.</param> | ||
void Configure(string name, TOptions options); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.Extensions.Options | ||
|
@@ -7,10 +7,10 @@ namespace Microsoft.Extensions.Options | |
/// Used to retreive configured TOptions instances. | ||
/// </summary> | ||
/// <typeparam name="TOptions">The type of options being requested.</typeparam> | ||
public interface IOptions<out TOptions> where TOptions : class, new() | ||
public interface IOptions<TOptions> where TOptions : class, new() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Have we considered having INamedOptions extend from IOptions and adds the Get method? With the removal of Add and Remove methods, that would mean we would not break current implementors of IOptions while still adding new functionality in 2.0 for those that implement INamedOptions. It also should not result in two parallel sets of APIs, like the previous PR had. And it seems like the two interfaces and their relationship are a reasonable separation of concerns that we might make even if designing this from scratch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's closer to the previous iterations where we had a new Options 2.0 interface basically. I definitely prefer not breaking ideally, since already this is a PITA fixing the options usages all over the place. So I thought the concern around potential confusion was related to the usage of the new OptionsFactory/OptionsCache, if we normalizing both IOptions + IOptionsGet to use the new implementation, that sounds good to me as well. Should we consider a more generic name than INamedOptions since we potentially are looking into adding the validation stuff back soon, I'd hate to rename it right away... My preference would be to name it something that implies additional logic is being applied on top of IOptions, i.e. IOptionsManager/IOptionsService. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @HaoK There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a problem with splitting Get into its own interface, I just really don't like INamedOptions (which I already used in a previous iteration, it did not grow on me at all...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also are we still trying to eliminate IOptionsSnapshot/IOptionsMonitor assuming there are sane workarounds which can be implemented via IOptionsFactory/Cache? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, the behavior of the new IOptions.Value implementation that is backed by the IOptionsCache is fully dependent on what's in the cache. The names stuff is more or less orthogonal, as currently There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ah, so you are actually proposing keeping the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah well that's why I was initially proposing we leave IOptions alone entirely and keep the old semantics, and introduce IOptionsManager with the new semantics and no .Value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we wanted to be crystal clear about what's old and new, we could leave Options alone entirely, and add all the new stuff in a new Options.Manager package, which would reuse only a few things like IConfigureOptions/Configure |
||
{ | ||
/// <summary> | ||
/// The configured TOptions instance. | ||
/// The default configured TOptions instance, equivalent to Get(string.Empty). | ||
/// </summary> | ||
TOptions Value { get; } | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.Extensions.Options | ||
{ | ||
/// <summary> | ||
/// Used to cache TOptions instances. | ||
/// </summary> | ||
/// <typeparam name="TOptions">The type of options being requested.</typeparam> | ||
public interface IOptionsCache<TOptions> where TOptions : class | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this public? Where would you ever use this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. People were asking for this to be exposed so they can plug in their own caching logic, i.e. using the memory cache to store options and control their own eviction policies, #167 Our default cache will at least have to support the existing reload on change behavior that will refresh any options that are bound to config. I'll be taking a look at that next. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that should be fairly straightforward, and is just hooking up the config OnReload changeTokens to call Remove on the IOptionsCache There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hope is that with this new factoring with IOptionsCache, we can obsolete/delete IOptionsMonitor/IOptionsSnapshot, to only have one IOptions to rule them all. (with IOptionsFactory + potentially in a later PR IOptionsValidator as ringwraiths) |
||
{ | ||
TOptions GetOrAdd(string name, Func<TOptions> createOptions); | ||
|
||
bool TryAdd(string name, TOptions options); | ||
|
||
bool TryRemove(string name); | ||
|
||
// Do we need a Clear all? | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.Extensions.Options | ||
{ | ||
/// <summary> | ||
/// Used to create TOptions instances. | ||
/// </summary> | ||
/// <typeparam name="TOptions">The type of options being requested.</typeparam> | ||
public interface IOptionsFactory<TOptions> where TOptions : class, new() | ||
{ | ||
/// <summary> | ||
/// Returns a configured TOptions instance with the given name. | ||
/// </summary> | ||
TOptions Create(string name); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
|
||
namespace Microsoft.Extensions.Options | ||
{ | ||
internal class LegacyOptionsCache<TOptions> where TOptions : class, new() | ||
{ | ||
private readonly Func<TOptions> _createCache; | ||
private object _cacheLock = new object(); | ||
private bool _cacheInitialized; | ||
private TOptions _options; | ||
private IEnumerable<IConfigureOptions<TOptions>> _setups; | ||
|
||
public LegacyOptionsCache(IEnumerable<IConfigureOptions<TOptions>> setups) | ||
{ | ||
_setups = setups; | ||
_createCache = CreateOptions; | ||
} | ||
|
||
private TOptions CreateOptions() | ||
{ | ||
var result = new TOptions(); | ||
if (_setups != null) | ||
{ | ||
foreach (var setup in _setups) | ||
{ | ||
setup.Configure(result); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
public virtual TOptions Value | ||
{ | ||
get | ||
{ | ||
return LazyInitializer.EnsureInitialized( | ||
ref _options, | ||
ref _cacheInitialized, | ||
ref _cacheLock, | ||
_createCache); | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine that we might have a place that requires to have not only the options value, but also the API to operate with options (Get, Add, Remove etc). I also agree that this interface is backward-compatible.
But I think that this interface violates with Interface Segregation principle. If I understand it right, methods (Get, Add, Remove) are required for Auth stuff. But for just retrieving options values isn't required.
Maybe create separated interface
IOptionsService
with methods (Get, Add, Remove). For basic use-cases inject it inIOptions
implementation. For Auth - inject the service itself and operate with options.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well before, the reason Add was on the main interface was because it needed to do validation using the validator before inserting into the cache. Now that validation is no longer part of this PR, consumers can directly insert what they want into the cache (using the options factory to create it), so for now, its probably fine dropping Add/Remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think that having
Get(name)
method in end-user interface is fine?Should options consumer decide which options is needed? Or DI engine should have an ability and all required information for choosing the right named options ? (and consumer will just consume)