From 00a68c7bd0bf722e994f30db1843699cc19f72da Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:27:47 +0200 Subject: [PATCH 1/3] Add IDiscoveryClient.InstancesFetched event --- .../DiscoveryInstancesFetchedEventArgs.cs | 23 +++++ .../src/Common/Discovery/IDiscoveryClient.cs | 5 ++ src/Common/src/Common/PublicAPI.Unshipped.txt | 4 + .../ConfigServerDiscoveryServiceTest.cs | 4 + .../ConfigurationDiscoveryClient.cs | 61 ++++++++++++- .../src/Configuration/PublicAPI.Unshipped.txt | 2 + .../src/Consul/ConsulDiscoveryClient.cs | 7 ++ .../src/Consul/PublicAPI.Unshipped.txt | 1 + .../src/Eureka/EurekaDiscoveryClient.cs | 73 +++++++++++++--- .../src/Eureka/PublicAPI.Unshipped.txt | 1 + .../ConfigurationDiscoveryClientTest.cs | 86 +++++++++++++++++++ .../Eureka.Test/EurekaDiscoveryClientTest.cs | 33 +++++-- .../RoundRobinLoadBalancerTest.cs | 8 ++ 13 files changed, 288 insertions(+), 20 deletions(-) create mode 100644 src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs diff --git a/src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs b/src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs new file mode 100644 index 0000000000..2f51827683 --- /dev/null +++ b/src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Common.Discovery; + +/// +/// Provides data for the event. +/// +public sealed class DiscoveryInstancesFetchedEventArgs : EventArgs +{ + /// + /// Gets the updated list of service instances, grouped by service ID. + /// + public IReadOnlyDictionary> InstancesByServiceId { get; } + + public DiscoveryInstancesFetchedEventArgs(IReadOnlyDictionary> instancesByServiceId) + { + ArgumentNullException.ThrowIfNull(instancesByServiceId); + + InstancesByServiceId = instancesByServiceId; + } +} diff --git a/src/Common/src/Common/Discovery/IDiscoveryClient.cs b/src/Common/src/Common/Discovery/IDiscoveryClient.cs index 598095b2a9..bd2b8c3cb8 100644 --- a/src/Common/src/Common/Discovery/IDiscoveryClient.cs +++ b/src/Common/src/Common/Discovery/IDiscoveryClient.cs @@ -14,6 +14,11 @@ public interface IDiscoveryClient /// string Description { get; } + /// + /// Occurs when service instances have been fetched from the discovery server. + /// + public event EventHandler InstancesFetched; + /// /// Gets information used to register the local service instance (this app) to the discovery server. /// diff --git a/src/Common/src/Common/PublicAPI.Unshipped.txt b/src/Common/src/Common/PublicAPI.Unshipped.txt index 7dc5c58110..ddcc76f1b3 100644 --- a/src/Common/src/Common/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs +Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs.DiscoveryInstancesFetchedEventArgs(System.Collections.Generic.IReadOnlyDictionary!>! instancesByServiceId) -> void +Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs.InstancesByServiceId.get -> System.Collections.Generic.IReadOnlyDictionary!>! +Steeltoe.Common.Discovery.IDiscoveryClient.InstancesFetched -> System.EventHandler! diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs index 2b193cad38..556dfc475e 100644 --- a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs +++ b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs @@ -114,6 +114,10 @@ private sealed class TestDiscoveryClient : IDiscoveryClient { public string Description => throw new NotImplementedException(); +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + public Task> GetServiceIdsAsync(CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs b/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs index 86aaad4529..837d4d538a 100644 --- a/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs +++ b/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Collections.ObjectModel; using Microsoft.Extensions.Options; using Steeltoe.Common.Discovery; @@ -10,17 +11,69 @@ namespace Steeltoe.Discovery.Configuration; /// /// A discovery client that reads service instances from app configuration. /// -public sealed class ConfigurationDiscoveryClient : IDiscoveryClient +public sealed class ConfigurationDiscoveryClient : IDiscoveryClient, IDisposable { private readonly IOptionsMonitor _optionsMonitor; + private readonly IDisposable? _changeTokenRegistration; public string Description => "A discovery client that returns service instances from app configuration."; + /// + /// Occurs when the configuration of service instances has been reloaded. + /// + public event EventHandler? InstancesFetched; + public ConfigurationDiscoveryClient(IOptionsMonitor optionsMonitor) { ArgumentNullException.ThrowIfNull(optionsMonitor); _optionsMonitor = optionsMonitor; + _changeTokenRegistration = optionsMonitor.OnChange(OnOptionsChanged); + } + + private void OnOptionsChanged(ConfigurationDiscoveryOptions options) + { + if (InstancesFetched != null) + { + ReadOnlyDictionary> instancesByServiceId = ToServiceInstanceMap(options.Services); + var eventArgs = new DiscoveryInstancesFetchedEventArgs(instancesByServiceId); + RaiseFetchEvent(eventArgs); + } + } + + private static ReadOnlyDictionary> ToServiceInstanceMap(IList services) + { + // @formatter:wrap_chained_method_calls chop_always + // @formatter:wrap_before_first_method_call true + + return services + .Where(service => service.ServiceId != null) + .GroupBy(service => service.ServiceId!, StringComparer.OrdinalIgnoreCase) + .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping + .Select(instance => (IServiceInstance)instance) + .ToList() + .AsReadOnly()) + .AsReadOnly(); + + // @formatter:wrap_before_first_method_call restore + // @formatter:wrap_chained_method_calls restore + } + + private void RaiseFetchEvent(DiscoveryInstancesFetchedEventArgs eventArgs) + { + // Execute on separate thread, so we won't block the configuration system in case the handler logic is expensive. + ThreadPool.QueueUserWorkItem(_ => + { + try + { + InstancesFetched?.Invoke(this, eventArgs); + } + catch (Exception) + { + // Intentionally left empty. Adding a logger to the constructor is a breaking change. + // Adding an extra constructor confuses the service container. + } + }); } /// @@ -54,4 +107,10 @@ public Task ShutdownAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } + + /// + public void Dispose() + { + _changeTokenRegistration?.Dispose(); + } } diff --git a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt index 7dc5c58110..02bc4bc7e9 100644 --- a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Steeltoe.Discovery.Configuration.ConfigurationDiscoveryClient.Dispose() -> void +Steeltoe.Discovery.Configuration.ConfigurationDiscoveryClient.InstancesFetched -> System.EventHandler? diff --git a/src/Discovery/src/Consul/ConsulDiscoveryClient.cs b/src/Discovery/src/Consul/ConsulDiscoveryClient.cs index 22d3df0ef3..2521aba036 100644 --- a/src/Discovery/src/Consul/ConsulDiscoveryClient.cs +++ b/src/Discovery/src/Consul/ConsulDiscoveryClient.cs @@ -30,6 +30,13 @@ public sealed class ConsulDiscoveryClient : IDiscoveryClient /// public string Description => "A discovery client for HashiCorp Consul."; + /// + /// This event is never raised. The Consul client doesn't implement caching. + /// +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + /// /// Initializes a new instance of the class. /// diff --git a/src/Discovery/src/Consul/PublicAPI.Unshipped.txt b/src/Discovery/src/Consul/PublicAPI.Unshipped.txt index 7dc5c58110..e1d5475e90 100644 --- a/src/Discovery/src/Consul/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Consul/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Steeltoe.Discovery.Consul.ConsulDiscoveryClient.InstancesFetched -> System.EventHandler? diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index ac6852233d..841b9e7dda 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Collections.ObjectModel; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -69,6 +70,9 @@ internal ApplicationInfoCollection Applications /// public event EventHandler? ApplicationsFetched; + /// + public event EventHandler? InstancesFetched; + public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, EurekaClient eurekaClient, IOptionsMonitor clientOptionsMonitor, HealthCheckHandlerProvider healthCheckHandlerProvider, TimeProvider timeProvider, ILogger logger) @@ -326,7 +330,8 @@ internal async Task FetchRegistryAsync(bool doFullUpdate, CancellationToken canc await _registryFetchAsyncLock.WaitAsync(cancellationToken); - ApplicationsFetchedEventArgs eventArgs; + ApplicationsFetchedEventArgs? applicationsEventArgs = null; + DiscoveryInstancesFetchedEventArgs? instancesEventArgs = null; try { @@ -344,33 +349,75 @@ internal async Task FetchRegistryAsync(bool doFullUpdate, CancellationToken canc UpdateLastRemoteInstanceStatusFromCache(); - eventArgs = new ApplicationsFetchedEventArgs(_remoteApps); + if (ApplicationsFetched != null) + { + applicationsEventArgs = new ApplicationsFetchedEventArgs(_remoteApps); + } + + if (InstancesFetched != null) + { + ReadOnlyDictionary> instancesByServiceId = ToServiceInstanceMap(_remoteApps); + instancesEventArgs = new DiscoveryInstancesFetchedEventArgs(instancesByServiceId); + } } finally { _registryFetchAsyncLock.Release(); } - OnApplicationsFetched(eventArgs); + if (applicationsEventArgs != null || instancesEventArgs != null) + { + RaiseFetchEvents(applicationsEventArgs, instancesEventArgs); + } } - private void OnApplicationsFetched(ApplicationsFetchedEventArgs? args) + private static ReadOnlyDictionary> ToServiceInstanceMap(ApplicationInfoCollection apps) { - if (args != null) + // @formatter:wrap_chained_method_calls chop_always + // @formatter:wrap_before_first_method_call true + + return apps + .SelectMany(app => app.Instances) + .GroupBy(instance => instance.AppName, StringComparer.OrdinalIgnoreCase) + .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping + .Select(instance => instance.ToServiceInstance()) + .ToList() + .AsReadOnly()) + .AsReadOnly(); + + // @formatter:wrap_before_first_method_call restore + // @formatter:wrap_chained_method_calls restore + } + + private void RaiseFetchEvents(ApplicationsFetchedEventArgs? applicationsEventArgs, DiscoveryInstancesFetchedEventArgs? instancesEventArgs) + { + // Execute on separate thread, so we won't block the periodic refresh in case the handler logic is expensive. + ThreadPool.QueueUserWorkItem(_ => { - // Execute on separate thread, so we won't block the periodic refresh in case the handler logic is expensive. - ThreadPool.QueueUserWorkItem(_ => + try { - try + if (applicationsEventArgs != null) { - ApplicationsFetched?.Invoke(this, args); + ApplicationsFetched?.Invoke(this, applicationsEventArgs); } - catch (Exception exception) + } + catch (Exception exception) + { + LogFailedToHandleEvent(exception, nameof(ApplicationsFetched)); + } + + try + { + if (instancesEventArgs != null) { - LogFailedToHandleEvent(exception, nameof(ApplicationsFetched)); + InstancesFetched?.Invoke(this, instancesEventArgs); } - }); - } + } + catch (Exception exception) + { + LogFailedToHandleEvent(exception, nameof(InstancesFetched)); + } + }); } internal async Task FetchFullRegistryAsync(CancellationToken cancellationToken) diff --git a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt index e09ccfe57f..1379bc4083 100644 --- a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.UseAspNetCoreUrls.get -> bool Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.UseAspNetCoreUrls.set -> void +Steeltoe.Discovery.Eureka.EurekaDiscoveryClient.InstancesFetched -> System.EventHandler? diff --git a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs index dcbb000c47..a85628e591 100644 --- a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs +++ b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using FluentAssertions.Extensions; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -188,4 +190,88 @@ public void Does_not_register_multiple_times() services.Count.Should().Be(beforeServiceCount); } + + [Fact] + public async Task InstancesFetched_event_is_raised_after_configuration_change() + { + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Discovery": { + "Services": [ + { + "ServiceId": "serviceA", + "host": "instanceA1", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceA", + "host": "instanceA2", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceB", + "host": "instanceB1", + "port": 443, + "isSecure": true + } + ] + } + } + """); + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); + builder.Services.AddConfigurationDiscoveryClient(); + await using WebApplication webApplication = builder.Build(); + + ConfigurationDiscoveryClient discoveryClient = webApplication.Services.GetServices().OfType().Single(); + int eventCount = 0; + DiscoveryInstancesFetchedEventArgs? eventArgs = null; + + discoveryClient.InstancesFetched += (_, args) => + { + eventCount++; + eventArgs = args; + }; + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Discovery": { + "Services": [ + { + "ServiceId": "serviceA", + "host": "instanceA1", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceB", + "host": "instanceB1", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceB", + "host": "instanceB2", + "port": 443, + "isSecure": true + } + ] + } + } + """); + + fileProvider.NotifyChanged(); + + SpinWait.SpinUntil(() => eventCount == 1, 5.Seconds()).Should().BeTrue(); + + eventArgs.Should().NotBeNull(); + eventArgs.InstancesByServiceId.Should().HaveCount(2); + eventArgs.InstancesByServiceId.Should().ContainKey("serviceA").WhoseValue.Should().HaveCount(1); + eventArgs.InstancesByServiceId.Should().ContainKey("serviceB").WhoseValue.Should().HaveCount(2); + } } diff --git a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs index 84f39e1df6..d0148a0cb3 100644 --- a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs @@ -88,7 +88,7 @@ public sealed class EurekaDiscoveryClientTest "instance": [ { "instanceId": "localhost:foo", - "hostName": "localhost", + "hostName": "modified-host", "app": "FOO", "ipAddr": "192.168.56.1", "status": "UP", @@ -635,7 +635,7 @@ public async Task Can_manipulate_request_headers() } [Fact] - public async Task ApplicationEventsFireOnChangeDuringFetch() + public async Task ApplicationEventsFireAfterFetch() { var appSettings = new Dictionary { @@ -655,17 +655,38 @@ public async Task ApplicationEventsFireOnChangeDuringFetch() webApplication.Services.GetRequiredService().Using(handler); var discoveryClient = webApplication.Services.GetRequiredService(); - int eventCount = 0; + int applicationsEventCount = 0; + ApplicationsFetchedEventArgs? applicationsEventArgs = null; + int instancesEventCount = 0; + DiscoveryInstancesFetchedEventArgs? instancesEventArgs = null; + + discoveryClient.ApplicationsFetched += (_, args) => + { + applicationsEventCount++; + applicationsEventArgs = args; + }; - discoveryClient.ApplicationsFetched += (_, _) => eventCount++; + discoveryClient.InstancesFetched += (_, args) => + { + instancesEventCount++; + instancesEventArgs = args; + }; await discoveryClient.FetchRegistryAsync(true, TestContext.Current.CancellationToken); - SpinWait.SpinUntil(() => eventCount == 1, 5.Seconds()).Should().BeTrue(); + SpinWait.SpinUntil(() => applicationsEventCount == 1 && instancesEventCount == 1, 5.Seconds()).Should().BeTrue(); await discoveryClient.FetchRegistryAsync(false, TestContext.Current.CancellationToken); - SpinWait.SpinUntil(() => eventCount == 2, 5.Seconds()).Should().BeTrue(); + SpinWait.SpinUntil(() => applicationsEventCount == 2 && instancesEventCount == 2, 5.Seconds()).Should().BeTrue(); handler.Mock.VerifyNoOutstandingExpectation(); + + applicationsEventArgs.Should().NotBeNull(); + InstanceInfo newInstanceInfo = applicationsEventArgs.Applications.Should().ContainSingle().Which.Instances.Should().ContainSingle().Which; + newInstanceInfo.ActionType.Should().Be(ActionType.Modified); + + instancesEventArgs.Should().NotBeNull(); + IServiceInstance newServiceInstance = instancesEventArgs.InstancesByServiceId.Should().ContainKey("FOO").WhoseValue.Should().ContainSingle().Which; + newServiceInstance.Uri.ToString().Should().Be("http://modified-host:8080/"); } private sealed class ExtraRequestHeadersDelegatingHandler : DelegatingHandler diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs index 0f41aab316..afb96b2624 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs @@ -257,6 +257,10 @@ private sealed class TestDiscoveryClient(IServiceInstance? instance = null) : ID public string Description => throw new NotImplementedException(); +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + public Task> GetServiceIdsAsync(CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -289,6 +293,10 @@ private sealed class ThrowingDiscoveryClient : IDiscoveryClient { public string Description => throw new NotImplementedException(); +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + public IServiceInstance GetLocalServiceInstance() { throw new InvalidOperationException(); From c27bd8ae1967bdb0cdc57ef17fdc1244d679ff35 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:44:18 +0200 Subject: [PATCH 2/3] Consul: don't use UseNetworkInterfaces to determine ports (reverts change from #1666) --- .../src/Consul/Configuration/ConsulDiscoveryOptions.cs | 3 +-- src/Discovery/src/Consul/ConfigurationSchema.json | 2 +- .../src/Consul/PostConfigureConsulDiscoveryOptions.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs index fd9b8b77d9..93e3b565f5 100644 --- a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs @@ -176,8 +176,7 @@ public sealed class ConsulDiscoveryOptions /// /// Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true. /// - /// This property is ignored when or is explicitly configured, or when is - /// true. + /// This property is ignored when or is explicitly configured. /// public bool UseAspNetCoreUrls { get; set; } = true; } diff --git a/src/Discovery/src/Consul/ConfigurationSchema.json b/src/Discovery/src/Consul/ConfigurationSchema.json index a00e7f69bf..58acfc8ab2 100644 --- a/src/Discovery/src/Consul/ConfigurationSchema.json +++ b/src/Discovery/src/Consul/ConfigurationSchema.json @@ -188,7 +188,7 @@ }, "UseAspNetCoreUrls": { "type": "boolean", - "description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured, or when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.UseNetworkInterfaces' is true." + "description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured." }, "UseNetworkInterfaces": { "type": "boolean", diff --git a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs index 91dcd274cc..424cec5f9f 100644 --- a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs @@ -62,7 +62,7 @@ public void PostConfigure(string? name, ConsulDiscoveryOptions options) options.HostName = options.IPAddress; } - if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null, UseNetworkInterfaces: false }) + if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null }) { ICollection addresses = _configuration.GetListenAddresses(); SetSchemeWithPortFromListenAddresses(options, addresses); From a50dda979d772251ac578b445d955184b09b0ade Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:07:05 +0200 Subject: [PATCH 3/3] Cleanup queries, fix case insensivity --- .../src/Configuration/ConfigurationDiscoveryClient.cs | 4 ++-- src/Discovery/src/Eureka/EurekaDiscoveryClient.cs | 2 +- .../Configuration.Test/ConfigurationDiscoveryClientTest.cs | 4 ++-- src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs b/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs index 837d4d538a..37a787c777 100644 --- a/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs +++ b/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs @@ -50,9 +50,9 @@ private static ReadOnlyDictionary> ToSer .Where(service => service.ServiceId != null) .GroupBy(service => service.ServiceId!, StringComparer.OrdinalIgnoreCase) .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping - .Select(instance => (IServiceInstance)instance) + .Cast() .ToList() - .AsReadOnly()) + .AsReadOnly(), StringComparer.OrdinalIgnoreCase) .AsReadOnly(); // @formatter:wrap_before_first_method_call restore diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index 841b9e7dda..e955b3c454 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -382,7 +382,7 @@ private static ReadOnlyDictionary> ToSer .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping .Select(instance => instance.ToServiceInstance()) .ToList() - .AsReadOnly()) + .AsReadOnly(), StringComparer.OrdinalIgnoreCase) .AsReadOnly(); // @formatter:wrap_before_first_method_call restore diff --git a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs index a85628e591..7fdf7aa3df 100644 --- a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs +++ b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs @@ -271,7 +271,7 @@ public async Task InstancesFetched_event_is_raised_after_configuration_change() eventArgs.Should().NotBeNull(); eventArgs.InstancesByServiceId.Should().HaveCount(2); - eventArgs.InstancesByServiceId.Should().ContainKey("serviceA").WhoseValue.Should().HaveCount(1); - eventArgs.InstancesByServiceId.Should().ContainKey("serviceB").WhoseValue.Should().HaveCount(2); + eventArgs.InstancesByServiceId.Should().ContainKey("ServiceA").WhoseValue.Should().HaveCount(1); + eventArgs.InstancesByServiceId.Should().ContainKey("ServiceB").WhoseValue.Should().HaveCount(2); } } diff --git a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs index d0148a0cb3..1891be394b 100644 --- a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs @@ -685,7 +685,7 @@ public async Task ApplicationEventsFireAfterFetch() newInstanceInfo.ActionType.Should().Be(ActionType.Modified); instancesEventArgs.Should().NotBeNull(); - IServiceInstance newServiceInstance = instancesEventArgs.InstancesByServiceId.Should().ContainKey("FOO").WhoseValue.Should().ContainSingle().Which; + IServiceInstance newServiceInstance = instancesEventArgs.InstancesByServiceId.Should().ContainKey("foo").WhoseValue.Should().ContainSingle().Which; newServiceInstance.Uri.ToString().Should().Be("http://modified-host:8080/"); }