using FrameworkBLL.ReportOrchestration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System.Reflection; namespace FrameworkSL.ReportOrchestration { /// /// Singleton registry populated at startup by scanning module assemblies for /// ReportEndpointBase subclasses decorated with [ReportEndpoint]. /// Implements IReportEndpointRegistry (defined in FrameworkBLL) so the /// QueryOrchestrator can use it without depending on FrameworkSL. /// Thread-safe after initial population (write-once, read-many). /// public sealed class ReportEndpointRegistry : IReportEndpointRegistry { private readonly IServiceProvider _sp; private readonly ILogger _logger; private readonly Dictionary _types = new(StringComparer.OrdinalIgnoreCase); public ReportEndpointRegistry( IServiceProvider sp, ILogger logger) { _sp = sp; _logger = logger; } /// /// Scans the assembly for ReportEndpointBase subclasses with [ReportEndpoint] /// and registers them by their ReportCode. /// Called once at startup from AddReportOrchestration(). /// public void ScanAssembly(Assembly assembly) { var endpointTypes = assembly.GetTypes() .Where(t => !t.IsAbstract && t.IsClass && t.IsSubclassOf(typeof(ReportEndpointBase))); foreach (var type in endpointTypes) { var attr = type.GetCustomAttribute(inherit: true); if (attr is null || string.IsNullOrWhiteSpace(attr.ReportCode)) continue; if (_types.TryAdd(attr.ReportCode, type)) { _logger.LogInformation( "ReportEndpointRegistry: registered {Type} for ReportCode='{Code}'", type.Name, attr.ReportCode); } else { _logger.LogWarning( "ReportEndpointRegistry: duplicate ReportCode='{Code}' — {Existing} kept, {New} ignored.", attr.ReportCode, _types[attr.ReportCode].Name, type.Name); } } } public IReportEndpoint? Resolve(string reportCode) { if (!_types.TryGetValue(reportCode, out var type)) { _logger.LogWarning( "ReportEndpointRegistry: no endpoint registered for ReportCode='{Code}'.", reportCode); return null; } // Create a new scope so Scoped dependencies (DAL, BLL, connections) are // properly lifetime-managed per resolve call. using var scope = _sp.CreateScope(); return scope.ServiceProvider.GetRequiredService(type) as IReportEndpoint; } public IReadOnlyList GetRegisteredReportCodes() => _types.Keys.ToList(); } }