| File: Configuration\ConfigurationServiceEndpointProvider.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.ServiceDiscovery\Microsoft.Extensions.ServiceDiscovery.csproj (Microsoft.Extensions.ServiceDiscovery) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.Extensions.ServiceDiscovery.Configuration; /// <summary> /// A service endpoint provider that uses configuration to resolve resolved. /// </summary> internal sealed partial class ConfigurationServiceEndpointProvider : IServiceEndpointProvider, IHostNameFeature { private const string DefaultEndpointName = "default"; private readonly string _serviceName; private readonly string? _endpointName; private readonly bool _includeAllSchemes; private readonly string[] _schemes; private readonly IConfiguration _configuration; private readonly ILogger<ConfigurationServiceEndpointProvider> _logger; private readonly IOptions<ConfigurationServiceEndpointProviderOptions> _options; /// <summary> /// Initializes a new <see cref="ConfigurationServiceEndpointProvider"/> instance. /// </summary> /// <param name="query">The query.</param> /// <param name="configuration">The configuration.</param> /// <param name="logger">The logger.</param> /// <param name="options">Configuration provider options.</param> /// <param name="serviceDiscoveryOptions">Service discovery options.</param> public ConfigurationServiceEndpointProvider( ServiceEndpointQuery query, IConfiguration configuration, ILogger<ConfigurationServiceEndpointProvider> logger, IOptions<ConfigurationServiceEndpointProviderOptions> options, IOptions<ServiceDiscoveryOptions> serviceDiscoveryOptions) { _serviceName = query.ServiceName; _endpointName = query.EndpointName; _includeAllSchemes = serviceDiscoveryOptions.Value.AllowAllSchemes && query.IncludedSchemes.Count == 0; var allowedSchemes = serviceDiscoveryOptions.Value.ApplyAllowedSchemes(query.IncludedSchemes); _schemes = allowedSchemes as string[] ?? allowedSchemes.ToArray(); _configuration = configuration; _logger = logger; _options = options; } /// <inheritdoc/> public ValueTask DisposeAsync() => default; /// <inheritdoc/> public ValueTask PopulateAsync(IServiceEndpointBuilder endpoints, CancellationToken cancellationToken) { // Only add resolved to the collection if a previous provider (eg, an override) did not add them. if (endpoints.Endpoints.Count != 0) { Log.SkippedResolution(_logger, _serviceName, "Collection has existing endpoints"); return default; } // Get the corresponding config section. var section = _configuration.GetSection(_options.Value.SectionName).GetSection(_serviceName); if (!section.Exists()) { endpoints.AddChangeToken(_configuration.GetReloadToken()); Log.ServiceConfigurationNotFound(_logger, _serviceName, $"{_options.Value.SectionName}:{_serviceName}"); return default; } endpoints.AddChangeToken(section.GetReloadToken()); // Find an appropriate configuration section based on the input. IConfigurationSection? namedSection = null; string endpointName; if (string.IsNullOrWhiteSpace(_endpointName)) { // Treat the scheme as the endpoint name and use the first section with a matching endpoint name which exists endpointName = DefaultEndpointName; ReadOnlySpan<string> candidateNames = [DefaultEndpointName, .. _schemes]; foreach (var scheme in candidateNames) { var candidate = section.GetSection(scheme); if (candidate.Exists()) { endpointName = scheme; namedSection = candidate; break; } } } else { // Use the section corresponding to the endpoint name. endpointName = _endpointName; namedSection = section.GetSection(_endpointName); } var configPath = $"{_options.Value.SectionName}:{_serviceName}:{endpointName}"; if (!namedSection.Exists()) { Log.EndpointConfigurationNotFound(_logger, endpointName, _serviceName, configPath); return default; } List<ServiceEndpoint> resolved = []; Log.UsingConfigurationPath(_logger, configPath, endpointName, _serviceName); // Account for both the single and multi-value cases. if (!string.IsNullOrWhiteSpace(namedSection.Value)) { // Single value case. AddEndpoint(resolved, namedSection, endpointName); } else { // Multiple value case. foreach (var child in namedSection.GetChildren()) { if (!int.TryParse(child.Key, out _)) { throw new KeyNotFoundException($"The endpoint configuration section for service '{_serviceName}' endpoint '{endpointName}' has non-numeric keys."); } AddEndpoint(resolved, child, endpointName); } } int resolvedEndpointCount; if (_includeAllSchemes) { // Include all endpoints. foreach (var ep in resolved) { endpoints.Endpoints.Add(ep); } resolvedEndpointCount = resolved.Count; } else { // Filter the resolved endpoints to only include those which match the specified, allowed schemes. resolvedEndpointCount = 0; var minIndex = _schemes.Length; foreach (var ep in resolved) { if (ep.EndPoint is UriEndPoint uri && uri.Uri.Scheme is { } scheme) { var index = Array.IndexOf(_schemes, scheme); if (index >= 0 && index < minIndex) { minIndex = index; } } } foreach (var ep in resolved) { if (ep.EndPoint is UriEndPoint uri && uri.Uri.Scheme is { } scheme) { var index = Array.IndexOf(_schemes, scheme); if (index >= 0 && index <= minIndex) { ++resolvedEndpointCount; endpoints.Endpoints.Add(ep); } } else { ++resolvedEndpointCount; endpoints.Endpoints.Add(ep); } } } if (resolvedEndpointCount == 0) { Log.ServiceConfigurationNotFound(_logger, _serviceName, configPath); } else { Log.ConfiguredEndpoints(_logger, _serviceName, configPath, endpoints.Endpoints, resolvedEndpointCount); } return default; } string IHostNameFeature.HostName => _serviceName; private void AddEndpoint(List<ServiceEndpoint> endpoints, IConfigurationSection section, string endpointName) { if (!ServiceEndpoint.TryParse(section.Value, out var serviceEndpoint)) { throw new KeyNotFoundException($"The endpoint configuration section for service '{_serviceName}' endpoint '{endpointName}' has an invalid value with key '{section.Key}'."); } serviceEndpoint.Features.Set<IServiceEndpointProvider>(this); if (_options.Value.ShouldApplyHostNameMetadata(serviceEndpoint)) { serviceEndpoint.Features.Set<IHostNameFeature>(this); } endpoints.Add(serviceEndpoint); } public override string ToString() => "Configuration"; }