diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsServiceCollectionExtensions.cs index 0dd03a5ba31..cdf4b8cbdbd 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsServiceCollectionExtensions.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options.Contextual; +using Microsoft.Extensions.Options.Contextual.Internal; +using Microsoft.Extensions.Options.Contextual.Provider; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.DependencyInjection; @@ -25,8 +27,8 @@ public static IServiceCollection AddContextualOptions(this IServiceCollection se _ = Throw.IfNull(services).AddOptions(); services.TryAdd(ServiceDescriptor.Singleton(typeof(IContextualOptionsFactory<>), typeof(ContextualOptionsFactory<>))); - services.TryAdd(ServiceDescriptor.Singleton(typeof(IContextualOptions<>), typeof(ContextualOptions<>))); - services.TryAdd(ServiceDescriptor.Singleton(typeof(INamedContextualOptions<>), typeof(ContextualOptions<>))); + services.TryAdd(ServiceDescriptor.Singleton(typeof(IContextualOptions<,>), typeof(ContextualOptions<,>))); + services.TryAdd(ServiceDescriptor.Singleton(typeof(INamedContextualOptions<,>), typeof(ContextualOptions<,>))); return services; } @@ -54,14 +56,14 @@ public static IServiceCollection Configure( /// The value of . public static IServiceCollection Configure( this IServiceCollection services, - string name, + string? name, Func>> loadOptions) where TOptions : class => services .AddContextualOptions() .AddSingleton>( new LoadContextualOptions( - Throw.IfNull(name), + name, Throw.IfNull(loadOptions))); /// @@ -69,11 +71,13 @@ public static IServiceCollection Configure( /// /// The options type to be configured. /// The to add the services to. - /// The action used to configure the options. + /// The action used to configure the options. /// The value of . - public static IServiceCollection Configure(this IServiceCollection services, Action configureOptions) +#pragma warning disable S3872 // Parameter names should not duplicate the names of their methods + public static IServiceCollection Configure(this IServiceCollection services, Action configure) +#pragma warning restore S3872 // Parameter names should not duplicate the names of their methods where TOptions : class - => services.Configure(Options.Options.DefaultName, Throw.IfNull(configureOptions)); + => services.Configure(Options.Options.DefaultName, Throw.IfNull(configure)); /// /// Registers an action used to configure a particular type of options. @@ -81,81 +85,42 @@ public static IServiceCollection Configure(this IServiceCollection ser /// The options type to be configured. /// The to add the services to. /// The name of the options to configure. - /// The action used to configure the options. + /// The action used to configure the options. /// The value of . - public static IServiceCollection Configure(this IServiceCollection services, string name, Action configureOptions) +#pragma warning disable S3872 // Parameter names should not duplicate the names of their methods + public static IServiceCollection Configure(this IServiceCollection services, string? name, Action configure) +#pragma warning restore S3872 // Parameter names should not duplicate the names of their methods where TOptions : class { return services.AddContextualOptions().AddSingleton>( new LoadContextualOptions( - Throw.IfNull(name), + name, (context, _) => new ValueTask>( - new ConfigureContextualOptions(Throw.IfNull(configureOptions), Throw.IfNull(context))))); + new ConfigureContextualOptions(Throw.IfNull(configure), Throw.IfNull(context))))); } /// - /// Registers an action used to initialize all instances of a particular type of options. + /// Registers an action used to configure all instances of a particular type of options. /// /// The options type to be configured. /// The to add the services to. - /// The action used to configure the options. - /// The value of . - public static IServiceCollection PostConfigureAll(this IServiceCollection services, Action configureOptions) - where TOptions : class - => services.PostConfigure(null, Throw.IfNull(configureOptions)); - - /// - /// Registers an action used to initialize a particular type of options. - /// - /// The options type to be configured. - /// The to add the services to. - /// The action used to configure the options. + /// The action used to configure the options. /// The value of . - public static IServiceCollection PostConfigure(this IServiceCollection services, Action configureOptions) + public static IServiceCollection ConfigureAll( + this IServiceCollection services, + Func>> loadOptions) where TOptions : class - => services.PostConfigure(Options.Options.DefaultName, Throw.IfNull(configureOptions)); + => services.Configure(name: null, Throw.IfNull(loadOptions)); /// - /// Registers an action used to initialize a particular type of options. + /// Registers an action used to configure all instances of a particular type of options. /// /// The options type to be configured. /// The to add the services to. - /// The name of the options instance. - /// The action used to configure the options. - /// The value of . - public static IServiceCollection PostConfigure(this IServiceCollection services, string? name, Action configureOptions) - where TOptions : class - => services - .AddContextualOptions() - .AddSingleton>( - new PostConfigureContextualOptions(name, Throw.IfNull(configureOptions))); - - /// - /// Register a validation action for an options type. - /// - /// The options type to be validated. - /// The to add the services to. - /// The validation function. - /// The failure message to use when validation fails. + /// The action used to configure the options. /// The value of . - public static IServiceCollection ValidateContextualOptions(this IServiceCollection services, Func validate, string failureMessage) + public static IServiceCollection ConfigureAll(this IServiceCollection services, Action configure) where TOptions : class - => services.ValidateContextualOptions(Options.Options.DefaultName, Throw.IfNull(validate), Throw.IfNull(failureMessage)); - - /// - /// Register a validation action for an options type. - /// - /// The options type to be validated. - /// The to add the services to. - /// The name of the options instance. - /// The validation function. - /// The failure message to use when validation fails. - /// The value of . - public static IServiceCollection ValidateContextualOptions(this IServiceCollection services, string name, Func validate, string failureMessage) - where TOptions : class - => services - .AddContextualOptions() - .AddSingleton>( - new ValidateContextualOptions(Throw.IfNull(name), Throw.IfNull(validate), Throw.IfNull(failureMessage))); + => services.Configure(name: null, Throw.IfNull(configure)); } diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptions.cs index 92e5073a065..66d9bb750a9 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptions.cs @@ -10,16 +10,16 @@ namespace Microsoft.Extensions.Options.Contextual; /// Used to retrieve configured instances. /// /// The type of options being requested. -public interface IContextualOptions +/// A type defining the context for this request. +public interface IContextualOptions where TOptions : class + where TContext : IOptionsContext { /// /// Gets the configured instance. /// - /// A type defining the context for this request. /// The context that will be used to create the options. /// The token to monitor for cancellation requests. /// A configured instance of . - ValueTask GetAsync(in TContext context, CancellationToken cancellationToken) - where TContext : IOptionsContext; + ValueTask GetAsync(in TContext context, CancellationToken cancellationToken); } diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/INamedContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/INamedContextualOptions.cs index e60774ca8b0..72f0a427859 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/INamedContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/INamedContextualOptions.cs @@ -10,17 +10,17 @@ namespace Microsoft.Extensions.Options.Contextual; /// Used to retrieve named configured instances. /// /// The type of options being requested. -public interface INamedContextualOptions : IContextualOptions +/// A type defining the context for this request. +public interface INamedContextualOptions : IContextualOptions where TOptions : class + where TContext : IOptionsContext { /// /// Gets the named configured instance. /// - /// A type defining the context for this request. /// The name of the options to get. /// The context that will be used to create the options. /// The token to monitor for cancellation requests. /// A configured instance of . - ValueTask GetAsync(string name, in TContext context, CancellationToken cancellationToken) - where TContext : IOptionsContext; + ValueTask GetAsync(string name, in TContext context, CancellationToken cancellationToken); } diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContext.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContext.cs index 4a55aad24cb..11a7a721415 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContext.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContext.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.Options.Contextual.Provider; + namespace Microsoft.Extensions.Options.Contextual; /// diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IPostConfigureContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/IPostConfigureContextualOptions.cs deleted file mode 100644 index 77688c4ed10..00000000000 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IPostConfigureContextualOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Extensions.Options.Contextual; - -/// -/// Represents something that configures the type. -/// -/// Options type being configured. -public interface IPostConfigureContextualOptions - where TOptions : class -{ - /// - /// Invoked to configure a instance. - /// - /// Options type being configured. - /// The name of the options instance being configured. - /// The context that will be used to configure the options. - /// The options instance to configured. - void PostConfigure(string name, in TContext context, TOptions options) - where TContext : IOptionsContext; -} diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IValidateContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/IValidateContextualOptions.cs deleted file mode 100644 index 21ff8899ec9..00000000000 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IValidateContextualOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Options; - -namespace Microsoft.Extensions.Options.Contextual; - -/// -/// Interface used to validate options. -/// -/// The options type to validate. -public interface IValidateContextualOptions - where TOptions : class -{ - /// - /// Validates a specific named options instance (or all when name is null). - /// - /// The name of the options instance being validated. - /// The options instance. - /// The result. - ValidateOptionsResult Validate(string? name, TOptions options); -} diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/ConfigureContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ConfigureContextualOptions.cs similarity index 92% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/ConfigureContextualOptions.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ConfigureContextualOptions.cs index 35faff4002c..71c837496c4 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/ConfigureContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ConfigureContextualOptions.cs @@ -2,9 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Extensions.Options.Contextual.Provider; using Microsoft.Shared.Diagnostics; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Internal; /// /// Configures the type. diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ContextualOptions.cs similarity index 60% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptions.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ContextualOptions.cs index 7a09dc90462..bab1a9f9058 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ContextualOptions.cs @@ -5,19 +5,21 @@ using System.Threading.Tasks; using Microsoft.Shared.Diagnostics; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Internal; /// /// Used to retrieve configured TOptions instances based on a context. /// /// The type of options being requested. -internal sealed class ContextualOptions : INamedContextualOptions +/// A type defining the context for this request. +internal sealed class ContextualOptions : INamedContextualOptions where TOptions : class + where TContext : notnull, IOptionsContext { private readonly IContextualOptionsFactory _factory; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The factory to create instances of with. public ContextualOptions(IContextualOptionsFactory factory) @@ -26,12 +28,10 @@ public ContextualOptions(IContextualOptionsFactory factory) } /// - public ValueTask GetAsync(in TContext context, CancellationToken cancellationToken) - where TContext : notnull, IOptionsContext - => GetAsync(Microsoft.Extensions.Options.Options.DefaultName, context, cancellationToken); + public ValueTask GetAsync(in TContext context, CancellationToken cancellationToken) + => GetAsync(Options.DefaultName, context, cancellationToken); /// - public ValueTask GetAsync(string name, in TContext context, CancellationToken cancellationToken) - where TContext : notnull, IOptionsContext + public ValueTask GetAsync(string name, in TContext context, CancellationToken cancellationToken) => _factory.CreateAsync(Throw.IfNull(name), context, cancellationToken); } diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsFactory.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ContextualOptionsFactory.cs similarity index 66% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsFactory.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ContextualOptionsFactory.cs index f044d9eedd2..a13cb0e32a4 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/ContextualOptionsFactory.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/ContextualOptionsFactory.cs @@ -9,9 +9,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Contextual.Provider; using Microsoft.Shared.Diagnostics; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Internal; /// /// Implementation of . @@ -20,28 +21,29 @@ namespace Microsoft.Extensions.Options.Contextual; internal sealed class ContextualOptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TOptions> : IContextualOptionsFactory where TOptions : class { - private readonly IOptionsFactory _baseFactory; private readonly ILoadContextualOptions[] _loaders; - private readonly IPostConfigureContextualOptions[] _postConfigures; - private readonly IValidateContextualOptions[] _validations; + + private readonly IConfigureOptions[] _setups; + private readonly IPostConfigureOptions[] _postConfigures; + private readonly IValidateOptions[] _validations; /// /// Initializes a new instance of the class. /// - /// The factory to create instances of with. /// The configuration loaders to run. + /// The configuration actions to run. /// The initialization actions to run. /// The validations to run. public ContextualOptionsFactory( - IOptionsFactory baseFactory, IEnumerable> loaders, - IEnumerable> postConfigures, - IEnumerable> validations) + IEnumerable> setups, + IEnumerable> postConfigures, + IEnumerable> validations) { - _baseFactory = baseFactory; _loaders = loaders.ToArray(); - _postConfigures = postConfigures.ToArray(); - _validations = validations.ToArray(); + _setups = setups as IConfigureOptions[] ?? setups.ToArray(); + _postConfigures = postConfigures as IPostConfigureOptions[] ?? postConfigures.ToArray(); + _validations = validations as IValidateOptions[] ?? validations.ToArray(); } /// @@ -54,7 +56,21 @@ public ValueTask CreateAsync(string name, in TContext contex _ = Throw.IfNull(context); cancellationToken.ThrowIfCancellationRequested(); - var options = _baseFactory.Create(name); + TOptions options = Activator.CreateInstance(); + + // Set the default setup. + foreach (IConfigureOptions setup in _setups) + { + if (setup is IConfigureNamedOptions namedSetup) + { + namedSetup.Configure(name, options); + } + else if (name == Options.DefaultName) + { + setup.Configure(options); + } + } + return ConfigureOptions(context); async ValueTask ConfigureOptions(TContext context) @@ -108,25 +124,27 @@ async ValueTask ConfigureOptions(TContext context) cancellationToken.ThrowIfCancellationRequested(); - foreach (var post in _postConfigures) + foreach (IPostConfigureOptions post in _postConfigures) { - post.PostConfigure(name, context, options); + post.PostConfigure(name, options); } - List? failures = default; - foreach (var validate in _validations) + if (_validations.Length > 0) { - var result = validate.Validate(name, options); - if (result.Failed) + var failures = new List(); + foreach (IValidateOptions validate in _validations) { - failures ??= []; - failures.AddRange(result.Failures); + ValidateOptionsResult result = validate.Validate(name, options); + if (result is not null && result.Failed) + { + failures.AddRange(result.Failures); + } } - } - if (failures is not null) - { - throw new OptionsValidationException(name, typeof(TOptions), failures); + if (failures.Count > 0) + { + throw new OptionsValidationException(name, typeof(TOptions), failures); + } } return options; diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptionsFactory.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/IContextualOptionsFactory.cs similarity index 90% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptionsFactory.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/IContextualOptionsFactory.cs index 9438803b17b..73dcf5d953e 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IContextualOptionsFactory.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/IContextualOptionsFactory.cs @@ -4,13 +4,13 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Internal; /// /// A factory to create instances of . /// /// The type of options being created. -public interface IContextualOptionsFactory +internal interface IContextualOptionsFactory where TOptions : class { /// diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/LoadContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/LoadContextualOptions.cs similarity index 94% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/LoadContextualOptions.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/LoadContextualOptions.cs index 3c7b1d2af38..e71317ca491 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/LoadContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Internal/LoadContextualOptions.cs @@ -4,9 +4,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options.Contextual.Provider; using Microsoft.Shared.Diagnostics; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Internal; /// /// Used to retrieve named configuration data from a contextual options provider implementation. diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/PostConfigureContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/PostConfigureContextualOptions.cs deleted file mode 100644 index 7bb75f7188a..00000000000 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/PostConfigureContextualOptions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Extensions.Options.Contextual; - -/// -/// Implementation of . -/// -/// Options type being configured. -internal sealed class PostConfigureContextualOptions : IPostConfigureContextualOptions - where TOptions : class -{ - /// - /// Initializes a new instance of the class. - /// - /// The name of the options. - /// The action to register. - public PostConfigureContextualOptions(string? name, Action action) - { - Name = name; - Action = action; - } - - /// - /// Gets the options name. - /// - public string? Name { get; } - - /// - /// Gets the initialization action. - /// - public Action Action { get; } - - /// - public void PostConfigure(string name, in TContext context, TOptions options) - where TContext : notnull, IOptionsContext - { - _ = Throw.IfNull(name); - _ = Throw.IfNull(context); - _ = Throw.IfNull(options); - - if (Name == null || name == Name) - { - Action(context, options); - } - } -} diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IConfigureContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/IConfigureContextualOptions.cs similarity index 91% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/IConfigureContextualOptions.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/IConfigureContextualOptions.cs index 7e488ada72c..1e8f0c367d5 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IConfigureContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/IConfigureContextualOptions.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Provider; /// /// Represents something that configures the type. diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/ILoadContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/ILoadContextualOptions.cs similarity index 95% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/ILoadContextualOptions.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/ILoadContextualOptions.cs index 14e4d18002f..391b1645157 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/ILoadContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/ILoadContextualOptions.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Provider; /// /// Used to retrieve named configuration data from a contextual options provider implementation. diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContextReceiver.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/IOptionsContextReceiver.cs similarity index 91% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContextReceiver.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/IOptionsContextReceiver.cs index f3342f0d687..a9a78471270 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/IOptionsContextReceiver.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/IOptionsContextReceiver.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Provider; /// /// Used by contextual options providers to collect context data. diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/NullConfigureContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/NullConfigureContextualOptions.cs similarity index 93% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/NullConfigureContextualOptions.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/NullConfigureContextualOptions.cs index caaa969ee55..324bbaf1702 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/NullConfigureContextualOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/NullConfigureContextualOptions.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Provider; /// /// Helper class. diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/NullConfigureContextualOptions_1.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/NullConfigureContextualOptions_1.cs similarity index 93% rename from src/Libraries/Microsoft.Extensions.Options.Contextual/NullConfigureContextualOptions_1.cs rename to src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/NullConfigureContextualOptions_1.cs index 61fa72fe68a..8ec2922e110 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/NullConfigureContextualOptions_1.cs +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Provider/NullConfigureContextualOptions_1.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Extensions.Options.Contextual; +namespace Microsoft.Extensions.Options.Contextual.Provider; #pragma warning disable SA1649 // File name should match first type name diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/ValidateContextualOptions.cs b/src/Libraries/Microsoft.Extensions.Options.Contextual/ValidateContextualOptions.cs deleted file mode 100644 index 1b8dd972656..00000000000 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/ValidateContextualOptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Extensions.Options; - -namespace Microsoft.Extensions.Options.Contextual; - -/// -/// Implementation of . -/// -/// The options type to validate. -internal sealed class ValidateContextualOptions : ValidateOptions, IValidateContextualOptions - where TOptions : class -{ - /// - /// Initializes a new instance of the class. - /// - /// Options name. - /// Validation function. - /// Validation failure message. - public ValidateContextualOptions(string? name, Func validation, string failureMessage) - : base(name, validation, failureMessage) - { - } -} diff --git a/test/Generators/Microsoft.Gen.ContextualOptions/Generated/ContextualOptionsTests.cs b/test/Generators/Microsoft.Gen.ContextualOptions/Generated/ContextualOptionsTests.cs index 5dea0143d3f..1bf6eb354e2 100644 --- a/test/Generators/Microsoft.Gen.ContextualOptions/Generated/ContextualOptionsTests.cs +++ b/test/Generators/Microsoft.Gen.ContextualOptions/Generated/ContextualOptionsTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Options.Contextual; +using Microsoft.Extensions.Options.Contextual.Provider; using TestClasses; using Xunit; diff --git a/test/Generators/Microsoft.Gen.ContextualOptions/Unit/ContextualOptionsTests.cs b/test/Generators/Microsoft.Gen.ContextualOptions/Unit/ContextualOptionsTests.cs index 71bfa6797e0..1c0215f3975 100644 --- a/test/Generators/Microsoft.Gen.ContextualOptions/Unit/ContextualOptionsTests.cs +++ b/test/Generators/Microsoft.Gen.ContextualOptions/Unit/ContextualOptionsTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Options.Contextual; +using Microsoft.Extensions.Options.Contextual.Provider; using TestClasses; using Xunit; diff --git a/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/AcceptanceTests.cs b/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/AcceptanceTests.cs index bdb9cc1b366..684affd32a1 100644 --- a/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/AcceptanceTests.cs +++ b/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/AcceptanceTests.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Testing; +using Microsoft.Extensions.Options.Contextual.Provider; using Xunit; namespace Microsoft.Extensions.Options.Contextual.Test; @@ -43,10 +44,10 @@ public void Receive(string key, T value) internal class WeatherForecastService : IWeatherForecastService { - private readonly IContextualOptions _contextualOptions; + private readonly IContextualOptions _contextualOptions; private readonly Random _rng = new(0); - public WeatherForecastService(IContextualOptions contextualOptions) + public WeatherForecastService(IContextualOptions contextualOptions) { _contextualOptions = contextualOptions; } diff --git a/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsFactoryTests.cs b/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsFactoryTests.cs index 9fae5a50bd7..3a92693993a 100644 --- a/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsFactoryTests.cs +++ b/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsFactoryTests.cs @@ -8,6 +8,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Contextual.Internal; +using Microsoft.Extensions.Options.Contextual.Provider; using Moq; using Xunit; @@ -19,12 +21,12 @@ public class ContextualOptionsFactoryTests public async Task ContextualOptionsFactoryDoesNothingWithNoOptionalDependenciesProvided() { var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), Enumerable.Empty>>(), - Enumerable.Empty>>(), - Enumerable.Empty>>()); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + Enumerable.Empty>>()); - var result = await new ContextualOptions>(sut).GetAsync(Mock.Of(), default); + var result = await new ContextualOptions, IOptionsContext>(sut).GetAsync(Mock.Of(), default); Assert.Empty(result); } @@ -33,10 +35,10 @@ public async Task ContextualOptionsFactoryDoesNothingWithNoOptionalDependenciesP public async Task DefaultValidatorsFailValidationForAnyInstanceName() { var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), Enumerable.Empty>>(), - Enumerable.Empty>>(), - new[] { new ValidateContextualOptions>(null, _ => false, "epic fail") }); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + new[] { new ValidateOptions>(null, _ => false, "epic fail") }); await Assert.ThrowsAsync(async () => await sut.CreateAsync(string.Empty, Mock.Of(), default)); await Assert.ThrowsAsync(async () => await sut.CreateAsync("A Name", Mock.Of(), default)); @@ -46,10 +48,10 @@ public async Task DefaultValidatorsFailValidationForAnyInstanceName() public async Task NamedValidatorsFailValidationOnlyForNamedInstance() { var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), Enumerable.Empty>>(), - Enumerable.Empty>>(), - new[] { new ValidateContextualOptions>("Foo", _ => false, "epic fail") }); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + new[] { new ValidateOptions>("Foo", _ => false, "epic fail") }); await Assert.ThrowsAsync(async () => await sut.CreateAsync("Foo", Mock.Of(), default)); Assert.Empty(await sut.CreateAsync("Bar", Mock.Of(), default)); @@ -66,10 +68,10 @@ public async Task PostConfigureRunsAfterLoad() }; var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), loaders, - new[] { new PostConfigureContextualOptions>(string.Empty, (_, list) => list.Add("post configure")) }, - Enumerable.Empty>>()); + Enumerable.Empty>>(), + new[] { new PostConfigureOptions>(string.Empty, (list) => list.Add("post configure")) }, + Enumerable.Empty>>()); var result = await sut.CreateAsync(string.Empty, Mock.Of(), default); @@ -86,11 +88,11 @@ public async Task NamedLoadersLoadOnlyNamedOptions() (context, _) => new ValueTask>>(new ConfigureContextualOptions>((_, list) => list.Add("configure"), context))) }; - var sut = new ContextualOptions>(new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), + var sut = new ContextualOptions, IOptionsContext>(new ContextualOptionsFactory>( loaders, - Enumerable.Empty>>(), - Enumerable.Empty>>())); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + Enumerable.Empty>>())); Assert.Equal(new[] { "configure" }, await sut.GetAsync("Foo", Mock.Of(), default)); Assert.Empty(await sut.GetAsync("Bar", Mock.Of(), default)); @@ -107,10 +109,10 @@ public async Task DefaultLoadersLoadAllOptions() }; var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), loaders, - Enumerable.Empty>>(), - Enumerable.Empty>>()); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + Enumerable.Empty>>()); Assert.Equal(new[] { "configure" }, await sut.CreateAsync("Foo", Mock.Of(), default)); Assert.Equal(new[] { "configure" }, await sut.CreateAsync("Bar", Mock.Of(), default)); @@ -120,10 +122,10 @@ public async Task DefaultLoadersLoadAllOptions() public async Task DefaultPostConfigureConfiguresAllOptions() { var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), Enumerable.Empty>>(), - new[] { new PostConfigureContextualOptions>(null, (_, list) => list.Add("post configure")) }, - Enumerable.Empty>>()); + Enumerable.Empty>>(), + new[] { new PostConfigureOptions>(null, (list) => list.Add("post configure")) }, + Enumerable.Empty>>()); Assert.Equal(new[] { "post configure" }, await sut.CreateAsync("Foo", Mock.Of(), default)); Assert.Equal(new[] { "post configure" }, await sut.CreateAsync("Bar", Mock.Of(), default)); @@ -133,15 +135,29 @@ public async Task DefaultPostConfigureConfiguresAllOptions() public async Task NamePostConfigureConfiguresOnlyNamedOptions() { var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), Enumerable.Empty>>(), - new[] { new PostConfigureContextualOptions>("Foo", (_, list) => list.Add("post configure")) }, - Enumerable.Empty>>()); + Enumerable.Empty>>(), + new[] { new PostConfigureOptions>("Foo", (list) => list.Add("post configure")) }, + Enumerable.Empty>>()); Assert.Equal(new[] { "post configure" }, await sut.CreateAsync("Foo", Mock.Of(), default)); Assert.Empty(await sut.CreateAsync("Bar", Mock.Of(), default)); } + [Fact] + public async Task ConfigureConfiguresOnlyOptions() + { + var sut = new ContextualOptionsFactory>( + Enumerable.Empty>>(), + new[] { new ConfigureOptions>((list) => list.Add("pre configure")) }, + Enumerable.Empty>>(), + Enumerable.Empty>>()); + + Assert.Empty(await sut.CreateAsync("Foo", Mock.Of(), default)); + Assert.Equal(new[] { "pre configure" }, await sut.CreateAsync(Options.DefaultName, Mock.Of(), default)); + Assert.Empty(await sut.CreateAsync("Bar", Mock.Of(), default)); + } + [Fact] [SuppressMessage( "Minor Code Smell", @@ -176,10 +192,10 @@ public async Task LoadsRunConcurrentlyWhileConfiguresRunSequentially() }; var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), loaders, - Enumerable.Empty>>(), - Enumerable.Empty>>()); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + Enumerable.Empty>>()); Assert.Equal(new[] { "1", "2", "3", "4" }, await sut.CreateAsync(string.Empty, Mock.Of(), default)); } @@ -198,10 +214,10 @@ public async Task CreateAsyncAggregatesAllExceptions() }; var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), loaders, - Enumerable.Empty>>(), - Enumerable.Empty>>()); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + Enumerable.Empty>>()); var exception = await Assert.ThrowsAsync(async () => await sut.CreateAsync(string.Empty, Mock.Of(), default)); Assert.Equal(2, exception.InnerExceptions.Count); @@ -223,10 +239,10 @@ public async Task CreateAsyncCallsDisposeEvenAfterExceptions() }; var sut = new ContextualOptionsFactory>( - new OptionsFactory>(Enumerable.Empty>>(), Enumerable.Empty>>()), loaders, - Enumerable.Empty>>(), - Enumerable.Empty>>()); + Enumerable.Empty>>(), + Enumerable.Empty>>(), + Enumerable.Empty>>()); var exception = await Assert.ThrowsAsync(async () => await sut.CreateAsync(string.Empty, Mock.Of(), default)); Assert.Equal(2, exception.InnerExceptions.Count); diff --git a/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsServiceCollectionExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsServiceCollectionExtensionsTests.cs index 118831b4586..bd490dda445 100644 --- a/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsServiceCollectionExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Options.ContextualOptions.Tests/ContextualOptionsServiceCollectionExtensionsTests.cs @@ -5,6 +5,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options.Contextual.Internal; +using Microsoft.Extensions.Options.Contextual.Provider; using Moq; using Xunit; @@ -19,8 +21,8 @@ public void AddContextualOptionsTest() using var provider = new ServiceCollection().AddContextualOptions().BuildServiceProvider(); - Assert.IsType>(provider.GetRequiredService>()); - Assert.IsType>(provider.GetRequiredService>()); + Assert.IsType>(provider.GetRequiredService>()); + Assert.IsType>(provider.GetRequiredService>()); Assert.IsType>(provider.GetRequiredService>()); } @@ -47,57 +49,24 @@ public async Task ConfigureDirectTest() } [Fact] - public void PostConfigureAllTest() + public void ConfigureAllWithLoadTest() { - Action configureOptions = (_, _) => { }; - using var provider = new ServiceCollection().PostConfigureAll(configureOptions).BuildServiceProvider(); - var postConfigure = (PostConfigureContextualOptions)provider.GetRequiredService>(); - - Assert.Equal(configureOptions, postConfigure.Action); - Assert.Null(postConfigure.Name); - } - - [Fact] - public void PostConfigureDefaultTest() - { - Action configureOptions = (_, _) => { }; - using var provider = new ServiceCollection().PostConfigure(configureOptions).BuildServiceProvider(); - var postConfigure = (PostConfigureContextualOptions)provider.GetRequiredService>(); + Func>> loadOptions = + (_, _) => new ValueTask>(NullConfigureContextualOptions.GetInstance()); - Assert.Equal(configureOptions, postConfigure.Action); - Assert.Equal(string.Empty, postConfigure.Name); + using var provider = new ServiceCollection().ConfigureAll(loadOptions).BuildServiceProvider(); + var loader = (LoadContextualOptions)provider.GetRequiredService>(); + Assert.Equal(loadOptions, loader.LoadAction); + Assert.Null(loader.Name); } [Fact] - public void PostConfigureNamedTest() + public async Task ConfigureAllDirectTest() { Action configureOptions = (_, _) => { }; - using var provider = new ServiceCollection().PostConfigure("Foo", configureOptions).BuildServiceProvider(); - var postConfigure = (PostConfigureContextualOptions)provider.GetRequiredService>(); - - Assert.Equal(configureOptions, postConfigure.Action); - Assert.Equal("Foo", postConfigure.Name); - } - - [Fact] - public void ValidateDefaultTest() - { - Func validate = _ => true; - using var provider = new ServiceCollection().ValidateContextualOptions(validate, "epic fail").BuildServiceProvider(); - var validateOptions = (ValidateContextualOptions)provider.GetRequiredService>(); - - Assert.Equal(validate, validateOptions.Validation); - Assert.Equal(string.Empty, validateOptions.Name); - } - - [Fact] - public void ValidateNamedTest() - { - Func validate = _ => true; - using var provider = new ServiceCollection().ValidateContextualOptions("Foo", validate, "epic fail").BuildServiceProvider(); - var validateOptions = (ValidateContextualOptions)provider.GetRequiredService>(); - - Assert.Equal(validate, validateOptions.Validation); - Assert.Equal("Foo", validateOptions.Name); + using var provider = new ServiceCollection().ConfigureAll(configureOptions).BuildServiceProvider(); + var loader = (LoadContextualOptions)provider.GetRequiredService>(); + Assert.Equal(configureOptions, ((ConfigureContextualOptions)await loader.LoadAction(Mock.Of(), default)).ConfigureOptions); + Assert.Null(loader.Name); } }