diff --git a/src/Microsoft.Extensions.Options/ConfigureNamedOptions.cs b/src/Microsoft.Extensions.Options/ConfigureNamedOptions.cs
new file mode 100644
index 0000000..63d9f2d
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/ConfigureNamedOptions.cs
@@ -0,0 +1,54 @@
+// 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
+{
+ ///
+ /// Implementation of IConfigureNamedOptions.
+ ///
+ ///
+ public class ConfigureNamedOptions : IConfigureNamedOptions where TOptions : class
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The name of the options.
+ /// The action to register.
+ public ConfigureNamedOptions(string name, Action action)
+ {
+ Name = name;
+ Action = action;
+ }
+
+ ///
+ /// The options name.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The configuration action.
+ ///
+ public Action Action { get; }
+
+ ///
+ /// Invokes the registered configure Action if the name matches.
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/IConfigureNamedOptions.cs b/src/Microsoft.Extensions.Options/IConfigureNamedOptions.cs
new file mode 100644
index 0000000..1ec9e1a
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/IConfigureNamedOptions.cs
@@ -0,0 +1,20 @@
+// 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
+{
+
+ ///
+ /// Represents something that configures the TOptions type.
+ ///
+ ///
+ public interface IConfigureNamedOptions where TOptions : class
+ {
+ ///
+ /// Invoked to configure a TOptions instance.
+ ///
+ /// The name of the options instance being configured.
+ /// The options instance to configure.
+ void Configure(string name, TOptions options);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/IOptionsCache.cs b/src/Microsoft.Extensions.Options/IOptionsCache.cs
new file mode 100644
index 0000000..263a11f
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/IOptionsCache.cs
@@ -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
+{
+ ///
+ /// Used to cache TOptions instances.
+ ///
+ /// The type of options being requested.
+ public interface IOptionsCache where TOptions : class
+ {
+ TOptions GetOrAdd(string name, Func createOptions);
+
+ bool TryAdd(string name, TOptions options);
+
+ bool TryRemove(string name);
+
+ // Do we need a Clear all?
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/IOptionsFactory.cs b/src/Microsoft.Extensions.Options/IOptionsFactory.cs
new file mode 100644
index 0000000..f4025bd
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/IOptionsFactory.cs
@@ -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
+{
+ ///
+ /// Used to create TOptions instances.
+ ///
+ /// The type of options being requested.
+ public interface IOptionsFactory where TOptions : class, new()
+ {
+ ///
+ /// Returns a configured TOptions instance with the given name.
+ ///
+ TOptions Create(string name);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/IOptionsService.cs b/src/Microsoft.Extensions.Options/IOptionsService.cs
new file mode 100644
index 0000000..f81c5d5
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/IOptionsService.cs
@@ -0,0 +1,21 @@
+// 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
+{
+ ///
+ /// Used to retreive configured and validated TOptions instances.
+ ///
+ /// The type of options being requested.
+ public interface IOptionsService where TOptions : class, new()
+ {
+ ///
+ /// Returns a configured and validated TOptions instance with the given name.
+ ///
+ TOptions Get(string name);
+
+ void Add(string name, TOptions options);
+
+ bool Remove(string name);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/IOptionsValidator.cs b/src/Microsoft.Extensions.Options/IOptionsValidator.cs
new file mode 100644
index 0000000..36d4101
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/IOptionsValidator.cs
@@ -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
+{
+ ///
+ /// Used to validate TOptions instances.
+ ///
+ /// The type of options being requested.
+ public interface IOptionsValidator where TOptions : class, new()
+ {
+ ///
+ /// Validates the options instance.
+ ///
+ void Validate(string name, TOptions options);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/IValidateNamedOptions.cs b/src/Microsoft.Extensions.Options/IValidateNamedOptions.cs
new file mode 100644
index 0000000..b11ccea
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/IValidateNamedOptions.cs
@@ -0,0 +1,25 @@
+// 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
+{
+ ///
+ /// Represents something that validate the TOptions type.
+ ///
+ /// The type of options being requested.
+ public interface IValidateNamedOptions where TOptions : class
+ {
+ ///
+ /// The name of the options instance to validate.
+ ///
+ string Name { get; }
+
+ ///
+ /// Invoked to validate a TOptions instance.
+ ///
+ /// The name of the options instance being validated.
+ /// The options instance to validate.
+ void Validate(string name, TOptions options);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/LegacyOptionsCache.cs b/src/Microsoft.Extensions.Options/LegacyOptionsCache.cs
new file mode 100644
index 0000000..ec8b619
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/LegacyOptionsCache.cs
@@ -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 where TOptions : class, new()
+ {
+ private readonly Func _createCache;
+ private object _cacheLock = new object();
+ private bool _cacheInitialized;
+ private TOptions _options;
+ private IEnumerable> _setups;
+
+ public LegacyOptionsCache(IEnumerable> 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/Microsoft.Extensions.Options.csproj b/src/Microsoft.Extensions.Options/Microsoft.Extensions.Options.csproj
index f8b6248..e8c8fb3 100644
--- a/src/Microsoft.Extensions.Options/Microsoft.Extensions.Options.csproj
+++ b/src/Microsoft.Extensions.Options/Microsoft.Extensions.Options.csproj
@@ -4,7 +4,7 @@
Provides a strongly typed way of specifying and accessing settings using dependency injection.
- netstandard1.0
+ netstandard1.1
$(NoWarn);CS1591
true
aspnetcore;options
@@ -13,7 +13,6 @@
-
-
+
diff --git a/src/Microsoft.Extensions.Options/OptionsCache.cs b/src/Microsoft.Extensions.Options/OptionsCache.cs
index 2572ea9..e4f5dba 100644
--- a/src/Microsoft.Extensions.Options/OptionsCache.cs
+++ b/src/Microsoft.Extensions.Options/OptionsCache.cs
@@ -1,49 +1,65 @@
-// 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.
using System;
-using System.Collections.Generic;
-using System.Threading;
+using System.Collections.Concurrent;
namespace Microsoft.Extensions.Options
{
- internal class OptionsCache where TOptions : class, new()
+ ///
+ /// Used to cache TOptions instances.
+ ///
+ /// The type of options being requested.
+ public class OptionsCache : IOptionsCache where TOptions : class
{
- private readonly Func _createCache;
- private object _cacheLock = new object();
- private bool _cacheInitialized;
- private TOptions _options;
- private IEnumerable> _setups;
+ private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(StringComparer.Ordinal);
- public OptionsCache(IEnumerable> setups)
+ public virtual TOptions GetOrAdd(string name, Func createOptions)
{
- _setups = setups;
- _createCache = CreateOptions;
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (createOptions == null)
+ {
+ throw new ArgumentNullException(nameof(createOptions));
+ }
+ return _cache.GetOrAdd(name, new Lazy(createOptions)).Value;
}
- private TOptions CreateOptions()
+ ///
+ /// Tries to adds a new option to the cache, will return false if the name already exists.
+ ///
+ /// The name of the options instance.
+ /// The options instance.
+ /// Whether anything was added.
+ public virtual bool TryAdd(string name, TOptions options)
{
- var result = new TOptions();
- if (_setups != null)
+ if (name == null)
{
- foreach (var setup in _setups)
- {
- setup.Configure(result);
- }
+ throw new ArgumentNullException(nameof(name));
}
- return result;
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+ return _cache.TryAdd(name, new Lazy(() => options));
}
- public virtual TOptions Value
+ ///
+ /// Try to remove an options instance.
+ ///
+ /// The name of the options instance.
+ /// Whether anything was removed.
+ public virtual bool TryRemove(string name)
{
- get
+ if (name == null)
{
- return LazyInitializer.EnsureInitialized(
- ref _options,
- ref _cacheInitialized,
- ref _cacheLock,
- _createCache);
+ throw new ArgumentNullException(nameof(name));
}
+ return _cache.TryRemove(name, out var ignored);
}
+
+ // Do we need a Clear all?
}
}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/OptionsFactory.cs b/src/Microsoft.Extensions.Options/OptionsFactory.cs
new file mode 100644
index 0000000..70b49ee
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/OptionsFactory.cs
@@ -0,0 +1,35 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.Extensions.Options
+{
+ ///
+ /// Implementation of IOptionsFactory.
+ ///
+ /// The type of options being requested.
+ public class OptionsFactory : IOptionsFactory where TOptions : class, new()
+ {
+ private readonly IEnumerable> _setups;
+
+ ///
+ /// Initializes a new instance with the specified options configurations.
+ ///
+ /// The configuration actions to run.
+ public OptionsFactory(IEnumerable> setups)
+ {
+ _setups = setups;
+ }
+
+ public TOptions Create(string name)
+ {
+ var options = new TOptions();
+ foreach (var setup in _setups)
+ {
+ setup.Configure(name, options);
+ }
+ return options;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/OptionsManager.cs b/src/Microsoft.Extensions.Options/OptionsManager.cs
index a0ee9fb..d85bf97 100644
--- a/src/Microsoft.Extensions.Options/OptionsManager.cs
+++ b/src/Microsoft.Extensions.Options/OptionsManager.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Options
///
public class OptionsManager : IOptions, IOptionsSnapshot where TOptions : class, new()
{
- private OptionsCache _optionsCache;
+ private LegacyOptionsCache _optionsCache;
///
/// Initializes a new instance with the specified options configurations.
@@ -19,7 +19,7 @@ namespace Microsoft.Extensions.Options
/// The configuration actions to run.
public OptionsManager(IEnumerable> setups)
{
- _optionsCache = new OptionsCache(setups);
+ _optionsCache = new LegacyOptionsCache(setups);
}
///
diff --git a/src/Microsoft.Extensions.Options/OptionsMonitor.cs b/src/Microsoft.Extensions.Options/OptionsMonitor.cs
index 9f00328..a4e07f9 100644
--- a/src/Microsoft.Extensions.Options/OptionsMonitor.cs
+++ b/src/Microsoft.Extensions.Options/OptionsMonitor.cs
@@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Options
///
public class OptionsMonitor : IOptionsMonitor where TOptions : class, new()
{
- private OptionsCache _optionsCache;
+ private LegacyOptionsCache _optionsCache;
private readonly IEnumerable> _setups;
private readonly IEnumerable> _sources;
private List> _listeners = new List>();
@@ -27,7 +27,7 @@ public OptionsMonitor(IEnumerable> setups, IEnumerab
{
_sources = sources;
_setups = setups;
- _optionsCache = new OptionsCache(setups);
+ _optionsCache = new LegacyOptionsCache(setups);
foreach (var source in _sources)
{
@@ -39,7 +39,7 @@ public OptionsMonitor(IEnumerable> setups, IEnumerab
private void InvokeChanged()
{
- _optionsCache = new OptionsCache(_setups);
+ _optionsCache = new LegacyOptionsCache(_setups);
foreach (var listener in _listeners)
{
listener?.Invoke(_optionsCache.Value);
diff --git a/src/Microsoft.Extensions.Options/OptionsService.cs b/src/Microsoft.Extensions.Options/OptionsService.cs
new file mode 100644
index 0000000..7eac496
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/OptionsService.cs
@@ -0,0 +1,72 @@
+// 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.Collections.Generic;
+using System.Collections.Concurrent;
+using System;
+
+namespace Microsoft.Extensions.Options
+{
+ ///
+ /// Implementation of IOptionsFactory.
+ ///
+ /// The type of options being requested.
+ public class OptionsService : IOptionsService where TOptions : class, new()
+ {
+ private readonly IOptionsCache _cache;
+ private readonly IOptionsFactory _factory;
+ private readonly IOptionsValidator _validator;
+
+ ///
+ /// Initializes a new instance with the specified options configurations.
+ ///
+ /// The cache to use.
+ /// The factory to use to create options.
+ /// The validator used to validate options.
+ public OptionsService(IOptionsCache cache, IOptionsFactory factory, IOptionsValidator validator)
+ {
+ _cache = cache;
+ _factory = factory;
+ _validator = validator;
+ }
+
+ public virtual void Add(string name, TOptions options)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+ if (!_cache.TryAdd(name, options))
+ {
+ throw new InvalidOperationException("An option named {name} already exists.");
+ }
+ }
+
+ public virtual TOptions Get(string name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ return _cache.GetOrAdd(name, () =>
+ {
+ var options = _factory.Create(name);
+ _validator.Validate(name, options);
+ return options;
+ });
+ }
+
+ public virtual bool Remove(string name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ return _cache.TryRemove(name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/OptionsServiceCollectionExtensions.cs b/src/Microsoft.Extensions.Options/OptionsServiceCollectionExtensions.cs
index aa7343a..b447e98 100644
--- a/src/Microsoft.Extensions.Options/OptionsServiceCollectionExtensions.cs
+++ b/src/Microsoft.Extensions.Options/OptionsServiceCollectionExtensions.cs
@@ -27,6 +27,10 @@ public static IServiceCollection AddOptions(this IServiceCollection services)
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
+ services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsService<>), typeof(OptionsService<>)));
+ services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
+ services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsValidator<>), typeof(OptionsValidator<>)));
+ services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsCache<>), typeof(OptionsCache<>)));
return services;
}
@@ -53,5 +57,101 @@ public static IServiceCollection Configure(this IServiceCollection ser
services.AddSingleton>(new ConfigureOptions(configureOptions));
return services;
}
+
+ ///
+ /// Registers an action used to configure 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 so that additional calls can be chained.
+ public static IServiceCollection Configure(this IServiceCollection services, string name, Action configureOptions)
+ where TOptions : class
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (configureOptions == null)
+ {
+ throw new ArgumentNullException(nameof(configureOptions));
+ }
+
+ services.AddSingleton>(new ConfigureNamedOptions(name, configureOptions));
+ return services;
+ }
+
+ public static IServiceCollection ConfigureAll(this IServiceCollection services, Action configureOptions)
+ where TOptions : class
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (configureOptions == null)
+ {
+ throw new ArgumentNullException(nameof(configureOptions));
+ }
+
+ // REVIEW: should this ignore the non named options? ConfigureAllNamed?
+ services.Configure(configureOptions);
+ services.AddSingleton>(new ConfigureNamedOptions(name: null, action: configureOptions));
+ return services;
+ }
+
+ ///
+ /// Registers an action used to validate options with a specific name.
+ ///
+ /// The options type to be configured.
+ /// The to add the services to.
+ /// The name of the options instance.
+ /// The action used to validate the options.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection Validate(this IServiceCollection services, string name, Action validateOptions)
+ where TOptions : class
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (validateOptions == null)
+ {
+ throw new ArgumentNullException(nameof(validateOptions));
+ }
+
+ services.AddSingleton>(new ValidateNamedOptions(name, validateOptions));
+ return services;
+ }
+
+ public static IServiceCollection ValidateAll(this IServiceCollection services, Action validateOptions)
+ where TOptions : class
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (validateOptions == null)
+ {
+ throw new ArgumentNullException(nameof(validateOptions));
+ }
+
+ services.AddSingleton>(new ValidateNamedOptions(name: null, action: validateOptions));
+ return services;
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/OptionsValidator.cs b/src/Microsoft.Extensions.Options/OptionsValidator.cs
new file mode 100644
index 0000000..8c53757
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/OptionsValidator.cs
@@ -0,0 +1,33 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.Extensions.Options
+{
+ ///
+ /// Implementation of IOptionsValidator.
+ ///
+ /// The type of options being requested.
+ public class OptionsValidator : IOptionsValidator where TOptions : class, new()
+ {
+ private readonly IEnumerable> _checks;
+
+ ///
+ /// Initializes a new instance with the specified options configurations.
+ ///
+ /// The validation actions to run.
+ public OptionsValidator(IEnumerable> validations)
+ {
+ _checks = validations;
+ }
+
+ public void Validate(string name, TOptions options)
+ {
+ foreach (var check in _checks)
+ {
+ check.Validate(name, options);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Options/ValidateNamedOptions.cs b/src/Microsoft.Extensions.Options/ValidateNamedOptions.cs
new file mode 100644
index 0000000..5914ab8
--- /dev/null
+++ b/src/Microsoft.Extensions.Options/ValidateNamedOptions.cs
@@ -0,0 +1,57 @@
+// 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
+{
+
+ ///
+ /// Implementation of IValidateNamedOptions.
+ ///
+ /// The type of options being requested.
+ public class ValidateNamedOptions : IValidateNamedOptions where TOptions : class
+ {
+ public ValidateNamedOptions() { }
+
+ ///
+ /// Constructor.
+ ///
+ /// The name of the options.
+ /// The action to register.
+ public ValidateNamedOptions(string name, Action action)
+ {
+ Name = name;
+ Action = action;
+ }
+
+ ///
+ /// The options name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The configuration action.
+ ///
+ public Action Action { get; set; }
+
+ ///
+ /// Invokes the registered validate Action if the name matches.
+ ///
+ ///
+ ///
+ public virtual void Validate(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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.Extensions.Options.Test/OptionsServiceTests.cs b/test/Microsoft.Extensions.Options.Test/OptionsServiceTests.cs
new file mode 100644
index 0000000..a45df76
--- /dev/null
+++ b/test/Microsoft.Extensions.Options.Test/OptionsServiceTests.cs
@@ -0,0 +1,148 @@
+// 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 Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.Extensions.Options.Tests
+{
+ public class OptionsFactoryTest
+ {
+ [Fact]
+ public void CanResolveNamedOptions()
+ {
+ var services = new ServiceCollection().AddOptions();
+
+ services.Configure("1", options =>
+ {
+ options.Message = "one";
+ });
+ services.Configure("2", options =>
+ {
+ options.Message = "two";
+ });
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Equal("one", option.Get("1").Message);
+ Assert.Equal("two", option.Get("2").Message);
+ }
+
+ [Fact]
+ public void FactoryValidatesOptions()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.Validate("1", options =>
+ {
+ if (string.IsNullOrEmpty(options.Message))
+ {
+ throw new ArgumentNullException();
+ }
+ });
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Throws(() => option.Get("1"));
+ }
+
+ [Fact]
+ public void FactoryCanConfigureAllOptions()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.ConfigureAll(o => o.Message = "Default");
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Equal("Default", option.Get("1").Message);
+ Assert.Equal("Default", option.Get("2").Message);
+ }
+
+ [Fact]
+ public void FactoryCanValidateAllOptions()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.ValidateAll(options =>
+ {
+ if (string.IsNullOrEmpty(options.Message))
+ {
+ throw new ArgumentNullException();
+ }
+ });
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Throws(() => option.Get("1"));
+ Assert.Throws(() => option.Get("2"));
+ }
+
+ [Fact]
+ public void FactoryConfigureAndValidateAllPlayWellTogether()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.ConfigureAll(o => o.Message = "Default");
+ services.Configure("NotDefault", o => o.Message = "NotDefault");
+ services.Configure("Throws", o => o.Message = null);
+ services.Validate("NotDefault", options =>
+ {
+ if (options.Message == "Default")
+ {
+ throw new Exception();
+ }
+ });
+
+ services.ValidateAll(options =>
+ {
+ if (string.IsNullOrEmpty(options.Message))
+ {
+ throw new ArgumentNullException();
+ }
+ });
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Equal("NotDefault", option.Get("NotDefault").Message);
+ Assert.Equal("Default", option.Get("Default").Message);
+ Assert.Throws(() => option.Get("Throws"));
+ }
+
+ [Fact]
+ public void FactoryConfiguresInRegistrationOrder()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.Configure("-", o => o.Message += "-");
+ services.ConfigureAll(o => o.Message += "A");
+ services.Configure("+", o => o.Message += "+");
+ services.ConfigureAll(o => o.Message += "B");
+ services.ConfigureAll(o => o.Message += "C");
+ services.Configure("+", o => o.Message += "+");
+ services.Configure("-", o => o.Message += "-");
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Equal("ABC", option.Get("1").Message);
+ Assert.Equal("A+BC+", option.Get("+").Message);
+ Assert.Equal("-ABC-", option.Get("-").Message);
+ }
+
+ [Fact]
+ public void FactoryValidatesInRegistrationOrder()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.Validate("-", o => o.Message += "-");
+ services.ValidateAll(o => o.Message += "A");
+ services.Validate("+", o => o.Message += "+");
+ services.ValidateAll(o => o.Message += "B");
+ services.ValidateAll(o => o.Message += "C");
+ services.Validate("+", o => o.Message += "+");
+ services.Validate("-", o => o.Message += "-");
+
+ var sp = services.BuildServiceProvider();
+ var option = sp.GetRequiredService>();
+ Assert.Equal("ABC", option.Get("1").Message);
+ Assert.Equal("A+BC+", option.Get("+").Message);
+ Assert.Equal("-ABC-", option.Get("-").Message);
+ }
+
+ }
+}
\ No newline at end of file