Skip to content

Commit

Permalink
remove service discovery as a core default for now (#2388)
Browse files Browse the repository at this point in the history
  • Loading branch information
david-driscoll authored Dec 2, 2024
1 parent a1359c2 commit 8b09739
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 139 deletions.
26 changes: 14 additions & 12 deletions src/Foundation/Conventions/OptionsConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,37 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Conventions;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)]
public class OptionsConvention : IServiceConvention
{
[RequiresUnreferencedCode(
"Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(String, IConfiguration)"
)]
private static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(
IServiceCollection services,
string? name,
IConfiguration config
)
where TOptions : class =>
services.Configure<TOptions>(name, config);

private readonly MethodInfo _configureMethod;

/// <summary>
/// A convention that registers any options POCOs that are found with the <see cref="RegisterOptionsConfigurationAttribute" />
/// </summary>
public OptionsConvention()
{
_configureMethod = GetType().GetMethod(nameof(Configure), BindingFlags.NonPublic | BindingFlags.Static)!;
}
public OptionsConvention() => _configureMethod = GetType().GetMethod(nameof(Configure), BindingFlags.NonPublic | BindingFlags.Static)!;

/// <inheritdoc />
public void Register(IConventionContext context, IConfiguration configuration, IServiceCollection services)
{
var classes = context.TypeProvider.GetTypes(
s => s.FromAssemblyDependenciesOf<RegisterOptionsConfigurationAttribute>().GetTypes(f => f.WithAttribute<RegisterOptionsConfigurationAttribute>())
);

foreach (var options in classes)
{
var attribute = options.GetCustomAttribute<RegisterOptionsConfigurationAttribute>()!;
#pragma warning disable IL2060
_configureMethod.MakeGenericMethod(options).Invoke(null, [services, attribute.OptionsName, configuration.GetSection(attribute.ConfigurationKey),]);
_configureMethod.MakeGenericMethod(options).Invoke(null, [services, attribute.OptionsName, configuration.GetSection(attribute.ConfigurationKey)]);
#pragma warning restore IL2060
}
}

[RequiresUnreferencedCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(String, IConfiguration)")]
private static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(IServiceCollection services, string? name, IConfiguration config)
where TOptions : class
{
return services.Configure<TOptions>(name, config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Conventions;
/// </summary>
[PublicAPI]
[ExportConvention]
[ConventionCategory(ConventionCategory.Core)]
[ConventionCategory(ConventionCategory.Application)]
public class ServiceDiscoveryCoreConvention : IServiceConvention
{
/// <inheritdoc />
Expand All @@ -20,18 +20,3 @@ public void Register(IConventionContext context, IConfiguration configuration, I
services.ConfigureHttpClientDefaults(http => http.AddServiceDiscovery());
}
}

/// <summary>
/// Service conventions using service discovery
/// </summary>
[PublicAPI]
[ExportConvention]
[ConventionCategory(ConventionCategory.Application)]
public class ServiceDiscoveryConvention : IServiceConvention
{
/// <inheritdoc />
public void Register(IConventionContext context, IConfiguration configuration, IServiceCollection services)
{
services.ConfigureHttpClientDefaults(http => http.AddStandardResilienceHandler());
}
}
21 changes: 21 additions & 0 deletions src/Foundation/Conventions/StandardResilienceConvention.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Rocket.Surgery.Conventions;
using Rocket.Surgery.Conventions.DependencyInjection;

namespace Rocket.Surgery.LaunchPad.Foundation.Conventions;

/// <summary>
/// Service conventions using service discovery
/// </summary>
[PublicAPI]
[ExportConvention]
[ConventionCategory(ConventionCategory.Application)]
public class StandardResilienceConvention : IServiceConvention
{
/// <inheritdoc />
public void Register(IConventionContext context, IConfiguration configuration, IServiceCollection services)
{
services.ConfigureHttpClientDefaults(http => http.AddStandardResilienceHandler());
}
}
7 changes: 2 additions & 5 deletions src/Foundation/Conventions/TimeConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ public class TimeConvention : IServiceConvention, ISerilogConvention
/// Create the NodaTime convention
/// </summary>
/// <param name="options"></param>
public TimeConvention(FoundationOptions? options = null)
{
_options = options ?? new FoundationOptions();
}
public TimeConvention(FoundationOptions? options = null) => _options = options ?? new FoundationOptions();

/// <inheritdoc />
public void Register(IConventionContext context, IConfiguration configuration, IServiceProvider services, LoggerConfiguration loggerConfiguration)
Expand All @@ -49,6 +46,6 @@ public void Register(IConventionContext context, IConfiguration configuration, I
// Try add so that unit tests can insert fakes
services.TryAddSingleton(TimeProvider.System);
services.TryAddSingleton<IClock>(SystemClock.Instance);
services.TryAddSingleton(DateTimeZoneProviders.Tzdb);
services.TryAddSingleton<IDateTimeZoneProvider>(new DateTimeZoneCache(_options.DateTimeZoneSource));
}
}
81 changes: 13 additions & 68 deletions src/Foundation/ExistingValueOptionsFactory.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Rocket.Surgery.LaunchPad.Foundation;

/// <summary>
/// Helper methods for creating <see cref="ExistingValueOptionsFactory{TOptions}"/> instances.
/// Helper methods for creating <see cref="ExistingValueOptionsFactory{TOptions}" /> instances.
/// </summary>
public static class ExistingValueOptions
{
/// <summary>
/// Applys all of the <see cref="IConfigureOptions{TOptions}"/>, <see cref="IPostConfigureOptions{TOptions}"/>, and <see cref="IValidateOptions{TOptions}"/> instances to the options instance.
/// Applys all of the <see cref="IConfigureOptions{TOptions}" />, <see cref="IPostConfigureOptions{TOptions}" />, and <see cref="IValidateOptions{TOptions}" />
/// instances to the options instance.
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="options"></param>
Expand All @@ -19,45 +19,23 @@ public static class ExistingValueOptions
/// <exception cref="OptionsValidationException"></exception>
public static void Apply<TOptions>(IServiceProvider serviceProvider, TOptions options, string name) where TOptions : class, new()
{
var setups = serviceProvider.GetServices<IConfigureOptions<TOptions>>();
var postConfigures = serviceProvider.GetServices<IPostConfigureOptions<TOptions>>();
var validations = serviceProvider.GetServices<IValidateOptions<TOptions>>();

foreach (var setup in setups)
{
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
namedSetup.Configure(name, options);
else if (name == Options.DefaultName) setup.Configure(options);
}

foreach (var post in postConfigures)
{
post.PostConfigure(name, options);
}

var failures = new List<string>();
foreach (var validate in validations)
{
var result = validate.Validate(name, options);
if (result.Failed) failures.AddRange(result.Failures);
}

if (failures.Count > 0) throw new OptionsValidationException(name, typeof(TOptions), failures);
new ExistingValueOptionsFactory<TOptions>(
options,
serviceProvider.GetServices<IConfigureOptions<TOptions>>(),
serviceProvider.GetServices<IPostConfigureOptions<TOptions>>(),
serviceProvider.GetServices<IValidateOptions<TOptions>>()
).Create(name);
}
}

/// <summary>
/// Implementation of <see cref="IOptionsFactory{TOptions}" />.
/// </summary>
/// <typeparam name="TOptions">The type of options being requested.</typeparam>
public class ExistingValueOptionsFactory<TOptions> :
IOptionsFactory<TOptions>
public class ExistingValueOptionsFactory<TOptions> : OptionsFactory<TOptions>
where TOptions : class, new()
{
private readonly TOptions _instance;
private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
private readonly IEnumerable<IValidateOptions<TOptions>>? _validations;

/// <summary>
/// Initializes a new instance with the specified options configurations.
Expand All @@ -71,42 +49,9 @@ public ExistingValueOptionsFactory(
IEnumerable<IConfigureOptions<TOptions>> setups,
IEnumerable<IPostConfigureOptions<TOptions>> postConfigures,
IEnumerable<IValidateOptions<TOptions>> validations
)
{
) : base(setups, postConfigures, validations) =>
_instance = instance;
_setups = setups;
_postConfigures = postConfigures;
_validations = validations;
}

/// <summary>
/// Returns a configured <typeparamref name="TOptions" /> instance with the given <paramref name="name" />.
/// </summary>
public TOptions Create(string name)
{
var options = _instance;
foreach (var setup in _setups)
{
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
namedSetup.Configure(name, options);
else if (name == Options.DefaultName) setup.Configure(options);
}

foreach (var post in _postConfigures)
{
post.PostConfigure(name, options);
}

if (_validations is null) return options;
var failures = new List<string>();
foreach (var validate in _validations)
{
var result = validate.Validate(name, options);
if (result.Failed) failures.AddRange(result.Failures);
}

if (failures.Count > 0) throw new OptionsValidationException(name, typeof(TOptions), failures);

return options;
}
/// <inheritdoc />
protected override TOptions CreateInstance(string name) => _instance;
}
14 changes: 5 additions & 9 deletions src/Foundation/NodaTimeLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ public static class NodaTimeLoggerConfigurationExtensions
/// </summary>
/// <param name="configuration">The logger configuration to apply configuration to.</param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration NodaTimeTypes(this LoggerDestructuringConfiguration configuration)
{
return configuration
.AsScalar<Offset>()
.Destructure
.AsScalar<CalendarSystem>()
.Destructure
.With(new NodaTimeDestructuringPolicy());
}
public static LoggerConfiguration NodaTimeTypes(this LoggerDestructuringConfiguration configuration) =>
configuration
.AsScalar<Offset>()
.Destructure.AsScalar<CalendarSystem>()
.Destructure.With(new NodaTimeDestructuringPolicy());
}
32 changes: 32 additions & 0 deletions test/Extensions.Tests/AutoRegisterOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Rocket.Surgery.Conventions;
using Rocket.Surgery.LaunchPad.Foundation;

namespace Extensions.Tests;

public class AutoRegisterOptions(ITestOutputHelper testOutputHelper) : ConventionFakeTest(testOutputHelper)
{
[Fact]
public async Task Should_Register_Options()
{
await Init(x => x.ConfigureConfiguration(builder => builder.AddInMemoryCollection([new("OptionsA:A", "B"), new("OptionsB:B", "A")])));
ServiceProvider.GetRequiredService<IOptions<OptionsA>>().Value.A.Should().Be("B");
ServiceProvider.GetRequiredService<IOptions<OptionsB>>().Value.B.Should().Be("A");
}

[RegisterOptionsConfiguration("OptionsA")]
[PublicAPI]
private class OptionsA
{
public required string A { get; set; }
}

[RegisterOptionsConfiguration("OptionsB")]
[PublicAPI]
private class OptionsB
{
public required string B { get; set; }
}
}
30 changes: 1 addition & 29 deletions test/Extensions.Tests/FakeTimeConventionTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
using Rocket.Surgery.Conventions;
using Rocket.Surgery.LaunchPad.Foundation;
using Rocket.Surgery.LaunchPad.Testing;

namespace Extensions.Tests;
Expand Down Expand Up @@ -31,28 +28,3 @@ public async Task Clock_Convention_Override()
clock.GetCurrentInstant().Should().Be(Instant.FromUnixTimeSeconds(0) + Duration.FromMinutes(1));
}
}

public class AutoRegisterOptions(ITestOutputHelper testOutputHelper) : ConventionFakeTest(testOutputHelper)
{
[Fact]
public async Task Should_Register_Options()
{
await Init(x => x.ConfigureConfiguration(builder => builder.AddInMemoryCollection([new("OptionsA:A", "B"), new("OptionsB:B", "A"),])));
ServiceProvider.GetRequiredService<IOptions<OptionsA>>().Value.A.Should().Be("B");
ServiceProvider.GetRequiredService<IOptions<OptionsB>>().Value.B.Should().Be("A");
}

[RegisterOptionsConfiguration("OptionsA")]
[PublicAPI]
private class OptionsA
{
public required string A { get; set; }
}

[RegisterOptionsConfiguration("OptionsB")]
[PublicAPI]
private class OptionsB
{
public required string B { get; set; }
}
}
Loading

0 comments on commit 8b09739

Please sign in to comment.