Background and motivation
Background and Motivation
HttpServiceEndpointResolver currently caches and reuses resolver instances using a key derived from:
request.RequestUri.GetLeftPart(UriPartial.Authority)
This works well when the logical authority fully describes the service discovery context. However, there are scenarios where endpoint resolution depends on additional request-specific metadata.
In our case, we use the standard HttpClient flow together with the existing service discovery infrastructure. Some requests contain special discovery-related query parameters, prefixed with X_, which influence how endpoints should be resolved.
For example:
https://storage-api/reports/image.png?X_DiscoveryTenant=tenant-a&X_DiscoveryRegion=eu-west&format=png
In this request:
format is an ordinary application parameter,
X_DiscoveryTenant and X_DiscoveryRegion are intended to participate in service discovery.
Different combinations of discovery parameters may require independent resolver entries and endpoint watchers. However, because the current implementation only considers the URI authority, the following requests all share the same resolver instance:
https://storage-api/reports/image.png?X_DiscoveryTenant=tenant-a&X_DiscoveryRegion=eu-west
https://storage-api/reports/image.png?X_DiscoveryTenant=tenant-b&X_DiscoveryRegion=eu-west
Today, applications requiring this behavior must implement workarounds outside of the service discovery infrastructure, such as:
- encoding discovery metadata into the authority portion of the URI,
- maintaining an external cache that maps a deterministic key to discovery metadata,
- creating multiple
HttpClient instances for combinations of discovery parameters,
- avoiding
ResolvingHttpDelegatingHandler and HttpServiceEndpointResolver altogether by implementing a custom DelegatingHandler that directly uses ServiceEndpointResolver.
The latter approach allows applications to implement their own resolver reuse strategy. However, it also requires reimplementing functionality that is currently provided by ResolvingHttpDelegatingHandler, including selecting an endpoint from the resolver result set and translating the selected endpoint into the final request URI (for example, functionality equivalent to GetUriWithEndpoint). As a result, applications must duplicate parts of the existing service discovery pipeline rather than extending it through a supported customization point.
These approaches add complexity and move resolver identity concerns outside of the service discovery implementation.
Providing an extensibility point for resolver key generation would allow advanced scenarios to participate in the existing resolver lifecycle management while preserving the current behavior as the default.
This proposal is not intended to change endpoint selection behavior itself. Rather, it aims to provide applications with a way to control how resolver instances are identified and reused when additional request-specific discovery metadata is involved.
API Proposal
namespace Microsoft.Extensions.ServiceDiscovery;
public interface IServiceDiscoveryKeyProvider
{
string GetKey(HttpRequestMessage request);
}
API Usage
builder.Services.AddServiceDiscovery();
builder.Services.AddSingleton<IServiceDiscoveryKeyProvider, QueryBasedServiceDiscoveryKeyProvider>();
builder.Services.AddHttpClient<MyClient>()
.AddServiceDiscovery();
public sealed class QueryBasedServiceDiscoveryKeyProvider : IServiceDiscoveryKeyProvider
{
public string GetKey(HttpRequestMessage request)
{
var uri = request.RequestUri!;
// Some simplified example implemenation
var query = string.Join(
"&",
uri.Query
.TrimStart('?')
.Split('&', StringSplitOptions.RemoveEmptyEntries)
.Select(part => part.Split('=', 2))
.Where(parts => parts[0].StartsWith("X_", StringComparison.OrdinalIgnoreCase))
.OrderBy(parts => parts[0], StringComparer.OrdinalIgnoreCase)
.Select(parts => parts.Length == 2
? $"{parts[0]}={parts[1]}"
: parts[0]));
return string.IsNullOrEmpty(query)
? uri.GetLeftPart(UriPartial.Authority)
: $"{uri.GetLeftPart(UriPartial.Authority)}?{query}";
}
}
await httpClient.GetAsync(
"https://storage-api/reports/image.png?X_DiscoveryTenant=tenant-a&X_DiscoveryRegion=eu-west&format=png");
await httpClient.GetAsync(
"https://storage-api/reports/image.png?X_DiscoveryRegion=eu-west&X_DiscoveryTenant=tenant-a&format=jpeg");
await httpClient.GetAsync(
"https://storage-api/other-resource?X_DiscoveryTenant=tenant-b&X_DiscoveryRegion=eu-west");
Generated resolver keys:
https://storage-api?X_DiscoveryRegion=eu-west&X_DiscoveryTenant=tenant-a
https://storage-api?X_DiscoveryRegion=eu-west&X_DiscoveryTenant=tenant-a
https://storage-api?X_DiscoveryRegion=eu-west&X_DiscoveryTenant=tenant-b
Expected behavior:
- The first two requests reuse the same resolver instance because they produce the same resolver key.
- The third request uses a different resolver instance because the discovery metadata differs.
- The request path and ordinary application query parameters do not participate in resolver key generation.
- Only query parameters intended for service discovery (for example, those prefixed with
X_) are included in the resolver key.
- Discovery parameters are ordered deterministically before generating the resolver key.
Alternative Designs
Instead of introducing a dedicated IServiceDiscoveryKeyProvider abstraction, resolver key generation could be exposed through an options-based delegate.
Risks
No response
Background and motivation
Background and Motivation
HttpServiceEndpointResolvercurrently caches and reuses resolver instances using a key derived from:This works well when the logical authority fully describes the service discovery context. However, there are scenarios where endpoint resolution depends on additional request-specific metadata.
In our case, we use the standard
HttpClientflow together with the existing service discovery infrastructure. Some requests contain special discovery-related query parameters, prefixed withX_, which influence how endpoints should be resolved.For example:
In this request:
formatis an ordinary application parameter,X_DiscoveryTenantandX_DiscoveryRegionare intended to participate in service discovery.Different combinations of discovery parameters may require independent resolver entries and endpoint watchers. However, because the current implementation only considers the URI authority, the following requests all share the same resolver instance:
Today, applications requiring this behavior must implement workarounds outside of the service discovery infrastructure, such as:
HttpClientinstances for combinations of discovery parameters,ResolvingHttpDelegatingHandlerandHttpServiceEndpointResolveraltogether by implementing a customDelegatingHandlerthat directly usesServiceEndpointResolver.The latter approach allows applications to implement their own resolver reuse strategy. However, it also requires reimplementing functionality that is currently provided by
ResolvingHttpDelegatingHandler, including selecting an endpoint from the resolver result set and translating the selected endpoint into the final request URI (for example, functionality equivalent toGetUriWithEndpoint). As a result, applications must duplicate parts of the existing service discovery pipeline rather than extending it through a supported customization point.These approaches add complexity and move resolver identity concerns outside of the service discovery implementation.
Providing an extensibility point for resolver key generation would allow advanced scenarios to participate in the existing resolver lifecycle management while preserving the current behavior as the default.
This proposal is not intended to change endpoint selection behavior itself. Rather, it aims to provide applications with a way to control how resolver instances are identified and reused when additional request-specific discovery metadata is involved.
API Proposal
API Usage
Generated resolver keys:
Expected behavior:
X_) are included in the resolver key.Alternative Designs
Instead of introducing a dedicated
IServiceDiscoveryKeyProviderabstraction, resolver key generation could be exposed through an options-based delegate.Risks
No response