using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Xunit;
}
[Fact]
+ public async Task ValidateOnStart_NamedOptions_ValidatesFailureOnStart_AddOptionsWithValidateOnStart()
+ {
+ var hostBuilder = CreateHostBuilder(services =>
+ {
+ services.AddOptions().AddSingleton(new FakeService());
+ services
+ .AddOptionsWithValidateOnStart<FakeSettings>("named")
+ .Configure<FakeService>((o, _) =>
+ {
+ o.Name = "named";
+ })
+ .Validate(o => o.Name == null, "trigger validation failure for named option!");
+ });
+
+ using (var host = hostBuilder.Build())
+ {
+ var error = await Assert.ThrowsAsync<OptionsValidationException>(async () =>
+ {
+ await host.StartAsync();
+ });
+
+ ValidateFailure<FakeSettings>(error, 1, "trigger validation failure for named option!");
+ }
+ }
+
+ [Fact]
private async Task ValidateOnStart_AddNamedOptionsMultipleTimesForSameType_BothGetTriggered()
{
bool firstOptionsBuilderTriggered = false;
}
[Fact]
+ private async Task ValidateOnStart_AddEagerValidation_DoesValidationWhenHostStartsWithNoFailure_AddOptionsWithValidateOnStart()
+ {
+ bool validateCalled = false;
+
+ var hostBuilder = CreateHostBuilder(services =>
+ {
+ // Adds eager validation using ValidateOnStart
+ services.AddOptionsWithValidateOnStart<ComplexOptions>("correct_configuration")
+ .Configure(o => o.Boolean = true)
+ .Validate(o =>
+ {
+ validateCalled = true;
+ return o.Boolean;
+ }, "correct_configuration");
+ });
+
+ using (var host = hostBuilder.Build())
+ {
+ await host.StartAsync();
+ }
+
+ Assert.True(validateCalled);
+ }
+
+ [Fact]
+ private async void CanValidateOptionsEagerly_AddOptionsWithValidateOnStart_IValidateOptions()
+ {
+ var hostBuilder = CreateHostBuilder(services =>
+ services.AddOptionsWithValidateOnStart<ComplexOptions, ComplexOptionsValidator>()
+ .Configure(o => o.Boolean = false));
+
+ using (var host = hostBuilder.Build())
+ {
+ var error = await Assert.ThrowsAsync<OptionsValidationException>(async () =>
+ {
+ await host.StartAsync();
+ });
+
+ ValidateFailure<ComplexOptions>(error, 1, "Boolean != true");
+ }
+ }
+
+ private class ComplexOptionsValidator : IValidateOptions<ComplexOptions>
+ {
+ public ValidateOptionsResult Validate(string name, ComplexOptions options)
+ {
+ if (options.Boolean == true)
+ {
+ return ValidateOptionsResult.Success;
+ }
+ return ValidateOptionsResult.Fail("Boolean != true");
+ }
+ }
+
+ [Fact]
private async Task ValidateOnStart_AddLazyValidation_SkipsValidationWhenHostStarts()
{
bool validateCalled = false;
public static partial class OptionsServiceCollectionExtensions
{
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
+ public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> AddOptionsWithValidateOnStart<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name = null) where TOptions : class { throw null; }
+ public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> AddOptionsWithValidateOnStart<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TValidateOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name = null) where TOptions : class where TValidateOptions : class, Microsoft.Extensions.Options.IValidateOptions<TOptions> { throw null; }
public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> AddOptions<TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class { throw null; }
public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> AddOptions<TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name) where TOptions : class { throw null; }
public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureAll<TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<TOptions> configureOptions) where TOptions : class { throw null; }
}
public partial interface IStartupValidator
{
- public void Validate();
+ void Validate();
}
public partial interface IValidateOptions<TOptions> where TOptions : class
{
public static Microsoft.Extensions.Options.ValidateOptionsResult Fail(System.Collections.Generic.IEnumerable<string> failures) { throw null; }
public static Microsoft.Extensions.Options.ValidateOptionsResult Fail(string failureMessage) { throw null; }
}
- public class ValidateOptionsResultBuilder
+ public partial class ValidateOptionsResultBuilder
{
public ValidateOptionsResultBuilder() { }
public void AddError(string error, string? propertyName = null) { }
}
/// <summary>
+ /// Adds services required for using options and enforces options validation check on start rather than in runtime.
+ /// </summary>
+ /// <remarks>
+ /// The <seealso cref="OptionsBuilderExtensions.ValidateOnStart{TOptions}(OptionsBuilder{TOptions})"/> extension is called by this method.
+ /// </remarks>
+ /// <typeparam name="TOptions">The options type to be configured.</typeparam>
+ /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
+ /// <param name="name">The name of the options instance.</param>
+ /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
+ public static OptionsBuilder<TOptions> AddOptionsWithValidateOnStart<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(
+ this IServiceCollection services,
+ string? name = null)
+ where TOptions : class
+ {
+ return new OptionsBuilder<TOptions>(services, name ?? Options.Options.DefaultName).ValidateOnStart();
+ }
+
+ /// <summary>
+ /// Adds services required for using options and enforces options validation check on start rather than in runtime.
+ /// </summary>
+ /// <remarks>
+ /// The <seealso cref="OptionsBuilderExtensions.ValidateOnStart{TOptions}(OptionsBuilder{TOptions})"/> extension is called by this method.
+ /// </remarks>
+ /// <typeparam name="TOptions">The options type to be configured.</typeparam>
+ /// <typeparam name="TValidateOptions">The <see cref="IValidateOptions{TOptions}"/> validator type.</typeparam>
+ /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
+ /// <param name="name">The name of the options instance.</param>
+ /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
+ public static OptionsBuilder<TOptions> AddOptionsWithValidateOnStart<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidateOptions>(
+ this IServiceCollection services,
+ string? name = null)
+ where TOptions : class
+ where TValidateOptions : class, IValidateOptions<TOptions>
+ {
+ services.AddOptions().TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<TOptions>, TValidateOptions>());
+ return new OptionsBuilder<TOptions>(services, name ?? Options.Options.DefaultName).ValidateOnStart();
+ }
+ /// <summary>
/// Registers an action used to configure a particular type of options.
/// Note: These are run before all <seealso cref="PostConfigure{TOptions}(IServiceCollection, Action{TOptions})"/>.
/// </summary>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Xunit;
namespace Microsoft.Extensions.Options.Tests
var error = Assert.Throws<NotImplementedException>(() => sp.GetRequiredService<IOptions<FakeOptions>>().Value);
}
+ private class ComplexOptionsValidator : IValidateOptions<ComplexOptions>
+ {
+ public ValidateOptionsResult Validate(string name, ComplexOptions options)
+ {
+ if (options.Boolean == true)
+ {
+ return ValidateOptionsResult.Success;
+ }
+ return ValidateOptionsResult.Fail("Boolean != true");
+ }
+ }
+
private class MultiOptionValidator : IValidateOptions<ComplexOptions>, IValidateOptions<FakeOptions>
{
private readonly string _allowed;
ValidateFailure<ComplexOptions>(error, Options.DefaultName, 3, "A validation error has occurred.", "Virtual", "Integer");
}
+ [Fact]
+ public void CanValidateOptionsEagerly_AddOptionsWithValidateOnStart()
+ {
+ var services = new ServiceCollection();
+ services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<ComplexOptions>, ComplexOptionsValidator>());
+ services
+ .AddOptionsWithValidateOnStart<ComplexOptions>()
+ .Configure(o => o.Boolean = false);
+
+ var sp = services.BuildServiceProvider();
+ // This doesn't really verify eager validation since we have no host to start.
+ var error = Assert.Throws<OptionsValidationException>(() => sp.GetRequiredService<IOptions<ComplexOptions>>().Value);
+ ValidateFailure<ComplexOptions>(error, Options.DefaultName, 1, "Boolean != true");
+ }
+
+ [Fact]
+ public void CanValidateOptionsEagerly_AddOptionsWithValidateOnStart_IValidateOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddOptionsWithValidateOnStart<ComplexOptions, ComplexOptionsValidator>()
+ .Configure(o => o.Boolean = false);
+
+ var sp = services.BuildServiceProvider();
+ // This doesn't really verify eager validation since we have no host to start.
+ var error = Assert.Throws<OptionsValidationException>(() => sp.GetRequiredService<IOptions<ComplexOptions>>().Value);
+ ValidateFailure<ComplexOptions>(error, Options.DefaultName, 1, "Boolean != true");
+ }
+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromAttribute : ValidationAttribute
{