120 lines
4.5 KiB
C#
120 lines
4.5 KiB
C#
using System.Reflection;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Router.Common.Models;
|
|
|
|
namespace StellaOps.Microservice;
|
|
|
|
/// <summary>
|
|
/// Discovers endpoints using runtime reflection.
|
|
/// </summary>
|
|
public sealed class ReflectionEndpointDiscoveryProvider : IEndpointDiscoveryProvider
|
|
{
|
|
private readonly StellaMicroserviceOptions _options;
|
|
private readonly IEnumerable<Assembly> _assemblies;
|
|
private readonly IServiceProviderIsService? _serviceProviderIsService;
|
|
private readonly ILogger? _logger;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ReflectionEndpointDiscoveryProvider"/> class.
|
|
/// </summary>
|
|
/// <param name="options">The microservice options.</param>
|
|
/// <param name="assemblies">The assemblies to scan for endpoints.</param>
|
|
public ReflectionEndpointDiscoveryProvider(
|
|
StellaMicroserviceOptions options,
|
|
IEnumerable<Assembly>? assemblies = null,
|
|
IServiceProviderIsService? serviceProviderIsService = null,
|
|
ILogger? logger = null)
|
|
{
|
|
_options = options;
|
|
_assemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies();
|
|
_serviceProviderIsService = serviceProviderIsService;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyList<EndpointDescriptor> DiscoverEndpoints()
|
|
{
|
|
var endpoints = new List<EndpointDescriptor>();
|
|
|
|
foreach (var assembly in _assemblies)
|
|
{
|
|
try
|
|
{
|
|
foreach (var type in assembly.GetTypes())
|
|
{
|
|
var attribute = type.GetCustomAttribute<StellaEndpointAttribute>();
|
|
if (attribute is null) continue;
|
|
|
|
if (!typeof(IStellaEndpoint).IsAssignableFrom(type))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Type {type.FullName} has [StellaEndpoint] but does not implement IStellaEndpoint.");
|
|
}
|
|
|
|
if (_serviceProviderIsService is not null && !_serviceProviderIsService.IsService(type))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var claims = attribute.RequiredClaims
|
|
.Select(c => new ClaimRequirement { Type = c })
|
|
.ToList();
|
|
|
|
var descriptor = new EndpointDescriptor
|
|
{
|
|
ServiceName = _options.ServiceName,
|
|
Version = _options.Version,
|
|
Method = attribute.Method,
|
|
Path = attribute.Path,
|
|
DefaultTimeout = TimeSpan.FromSeconds(attribute.TimeoutSeconds),
|
|
SupportsStreaming = attribute.SupportsStreaming,
|
|
RequiringClaims = claims,
|
|
HandlerType = type
|
|
};
|
|
|
|
endpoints.Add(descriptor);
|
|
}
|
|
}
|
|
catch (ReflectionTypeLoadException ex)
|
|
{
|
|
if (_logger is not null)
|
|
{
|
|
var assemblyName = assembly.FullName ?? assembly.GetName().Name ?? "unknown";
|
|
if (ex.LoaderExceptions is not null && ex.LoaderExceptions.Length > 0)
|
|
{
|
|
foreach (var loaderException in ex.LoaderExceptions)
|
|
{
|
|
if (loaderException is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_logger.LogWarning(
|
|
loaderException,
|
|
"Endpoint discovery skipped assembly {Assembly} due to type load error.",
|
|
assemblyName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning(
|
|
ex,
|
|
"Endpoint discovery skipped assembly {Assembly} due to type load error.",
|
|
assemblyName);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning(
|
|
ex,
|
|
"Endpoint discovery skipped assembly {Assembly} due to reflection error.",
|
|
assembly.FullName ?? assembly.GetName().Name ?? "unknown");
|
|
}
|
|
}
|
|
|
|
return endpoints;
|
|
}
|
|
}
|