using System.Reflection; using Autofac; namespace e_suite.DependencyInjection; public class ModuleDiscovery : IModuleDiscovery { private readonly IFileSystem _fileSystem; private readonly IAssemblyLoader _assemblyLoader; private readonly IAssemblyIdentity _assemblyIdentity; public ModuleDiscovery(IFileSystem fileSystem, IAssemblyLoader assemblyLoader, IAssemblyIdentity assemblyIdentity) { _fileSystem = fileSystem; _assemblyLoader = assemblyLoader; _assemblyIdentity = assemblyIdentity; } public void RegisterAllModuleIocRegistrations(ContainerBuilder builder) { var cacheDirectory = Path.Combine(AppContext.BaseDirectory, ".cache"); _fileSystem.CreateDirectory(cacheDirectory); var cachePath = Path.Combine(cacheDirectory, "module-cache.json"); var cache = LoadOrCreateModuleCache(cachePath); var basePath = AppContext.BaseDirectory; var assemblies = new List(); foreach (var module in cache.Modules) { var fullPath = Path.Combine(basePath, module); if (!_fileSystem.FileExists(fullPath)) throw new FileNotFoundException($"Cached module not found: {fullPath}"); assemblies.Add(_assemblyLoader.LoadFrom(fullPath)); } var registrationTypes = assemblies .SelectMany(a => a.GetTypes()) .Where(t => typeof(IIocRegistration).IsAssignableFrom(t) && t is { IsAbstract: false, IsInterface: false }) .ToList(); foreach (var type in registrationTypes) { var instance = (IIocRegistration)Activator.CreateInstance(type)!; instance.RegisterTypes(builder); } } private ModuleCache LoadOrCreateModuleCache(string cachePath) { var currentHash = ComputeAssemblyHash(); if (_fileSystem.FileExists(cachePath)) { var json = _fileSystem.ReadAllText(cachePath); var cache = System.Text.Json.JsonSerializer.Deserialize(json); if (cache is not null && cache.AssemblyHash == currentHash) return cache; } // Cache missing or invalid → regenerate var regenerated = RegenerateModuleCache(currentHash); var output = System.Text.Json.JsonSerializer.Serialize(regenerated, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); _fileSystem.WriteAllText(cachePath, output); return regenerated; } private ModuleCache RegenerateModuleCache(string assemblyHash) { var basePath = AppContext.BaseDirectory; var dlls = _fileSystem.GetFiles(basePath, "*.dll"); var modules = new List(); foreach (var dll in dlls) { try { var asm = _assemblyLoader.LoadFrom(dll); var hasRegistration = asm.GetTypes().Any(t => typeof(IIocRegistration).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface); if (hasRegistration) modules.Add(Path.GetFileName(dll)); } catch { // Ignore non-.NET DLLs or load failures } } return new ModuleCache { AssemblyHash = assemblyHash, Modules = modules }; } private string ComputeAssemblyHash() { var mainAssembly = _assemblyIdentity.GetAssemblyForHashing(); var path = mainAssembly.Location; using var stream = _fileSystem.OpenRead(path); using var sha = System.Security.Cryptography.SHA256.Create(); var hash = sha.ComputeHash(stream); return Convert.ToHexString(hash); } }