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/");
}