Backend/e-suite.DependencyInjection/ModuleDiscovery.cs

125 lines
3.9 KiB
C#

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<Assembly>();
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<ModuleCache>(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<string>();
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);
}
}