Files
2026-11-06 19:25:36 +08:00

328 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MAX.Core.DependencyInjection;
using MAX.Core.Interfaces;
using MAX.Core.Logger;
using MAX.Framework.Commons;
using MAX.Framework.Dtos;
using MAX.Framework.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Win32;
using System.Configuration;
using System.Diagnostics;
using System.Reflection;
using MAX.Plugins;
namespace MAX.Framework.DependencyInjection
{
/// <summary>
/// IOC 注册入口(由宿主 Program.cs 调用)
/// </summary>
public static class ServiceCollectionExtensions
{
private static readonly Type[] DependencyMarkers =
[
typeof(ISingletonDependency),
typeof(IScopedDependency),
typeof(ITransientDependency),
];
/// <summary>
/// 添加框架注册
/// </summary>
public static IServiceCollection AddStore(this IHostApplicationBuilder host)
{
Debug.WriteLine("[Store] 开始注册应用服务");
RunDebugStep("Logger", "注册日志服务", () => host.AddLogger());
//host.Services.AddSingleton<ILoggerFacade>(NLogger.Instance);//注册log
IEnumerable<Assembly> assemblies = StoreAppTypeFinder.Current.GetAssemblies(host.Configuration["Plugins:Path"]).ToArray();
foreach (var assembly in assemblies)
Debug.WriteLine($"[Assembly] 已发现:{assembly.FullName}");
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
Dictionary<Type, object> instances = [];
return AddStoreCore(host, assemblies, instances);
}
/// <summary>
/// 注册应用服务的公共实现:宿主入口(<c>builder.AddStoreHub()</c>)与 <see cref="IServiceCollection"/> 入口共用。
/// </summary>
internal static IServiceCollection AddStoreCore(
IHostApplicationBuilder host,
IEnumerable<Assembly> assemblies,
IDictionary<Type, object> instances)
{
RunDebugStep("DI", "注册依赖标记服务", () => RegisterDependencyMarkers(host.Services, assemblies));
RunDebugStep("Module", "注册全部模块", () => RunModules(host, assemblies, instances));
RunDebugStep("Plugin", "注册全部插件", () => RunPlugins(host, assemblies, instances));
RunDebugStep("Startup", "初始化并注册启动服务", () => RunStartups(host.Services, assemblies, instances));
RunDebugStep("MediatR", "按命令特性注册管道", () => host.Services.AddMediatRAttributedBehaviors(assemblies.ToArray()));
Debug.WriteLine("[MVC] 开始注册控制器和全局过滤器");
var mvc = host.Services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
options.Filters.Add<UnifyResultFilter>();
}).ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
// 取出所有校验错误信息,拼成一个字符串(或者只取第一个)
var errors = context.ModelState
.Where(e => e.Value?.Errors.Count > 0)
.SelectMany(e => e.Value!.Errors.Select(err => err.ErrorMessage))
.ToList();
var msg = string.Join("; ", errors);
var result = ApiResult.Fail(msg, ResultCode.ValidationError);
return new ObjectResult(result)
{
StatusCode = 200 // 和其他接口保持一致,用Code表达失败,而不是HTTP 400
};
};
});
foreach (var assembly in assemblies)
{
mvc.AddApplicationPart(assembly);
Debug.WriteLine($"[MVC] 已添加 ApplicationPart:{assembly.GetName().Name}");
}
Debug.WriteLine($"[Store] 应用服务注册完成,共 {host.Services.Count} 个服务描述");
return host.Services;
}
/// <summary>
/// 自动注册标记接口(ISingletonDependency / IScopedDependency / ITransientDependency)的实现类型
/// </summary>
private static void RegisterDependencyMarkers(IServiceCollection services, IEnumerable<Assembly> assemblies)
{
foreach (Type type in GetConcreteTypes(assemblies))
{
Type? marker = DependencyMarkers.FirstOrDefault(m => m.IsAssignableFrom(type));
if (marker is null)
{
continue;
}
ServiceLifetime lifetime = marker == typeof(ISingletonDependency) ? ServiceLifetime.Singleton
: marker == typeof(IScopedDependency) ? ServiceLifetime.Scoped
: ServiceLifetime.Transient;
// 注册为自身
services.Add(new ServiceDescriptor(type, type, lifetime));
Debug.WriteLine($"[DI] 注册自身:{type.FullName},Lifetime={lifetime}");
// 注册为实现的接口(排除标记接口与系统接口)
foreach (Type iface in type.GetInterfaces())
{
if (DependencyMarkers.Contains(iface) || IsSystemInterface(iface))
{
continue;
}
services.Add(new ServiceDescriptor(iface, type, lifetime));
Debug.WriteLine($"[DI] 注册接口:{iface.FullName} -> {type.FullName},Lifetime={lifetime}");
}
}
}
private static IEnumerable<IStartup> Startups = [];
/// <summary>
/// 发现所有 <see cref="IStartup"/> 实现,按 <see cref="IStartup.Priority"/> 降序执行
/// </summary>
private static void RunStartups(IServiceCollection services, IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
{
IServiceRegistrar registrar = new ServiceRegistrarProxy(services);
Startups = CreateInstances<IStartup>(assemblies, instances).Where(s => s is not IPlugin).OrderByDescending(s => s.Priority);
foreach (IStartup startup in Startups)
{
RunDebugStep("Startup", $"初始化 {startup.GetType().FullName},Priority={startup.Priority}", startup.Initialize);
RunDebugStep("Startup", $"注册服务 {startup.GetType().FullName}", () => startup.Register(registrar));
}
}
private static IEnumerable<IModule> Modules = [];
/// <summary>
/// 执行所有模块的服务注册阶段(<see cref="IModule.AddModule"/>)
/// </summary>
private static void RunModules(IHostApplicationBuilder host, IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
{
Modules = CreateInstances<IModule>(assemblies, instances).Where(m => m is not IPlugin).OrderByDescending(s => s.Priority);
foreach (IModule module in Modules)
{
try
{
RunDebugStep("Module", $"注册 {module.GetType().FullName},Priority={module.Priority}", () => module.AddModule(host));
}
catch (Exception ex)
{
// 带上模块名,避免只看到一个没有上下文的异常
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败:{ex.Message}", ex);
}
}
}
/// <summary>
/// 反射创建(或复用)所有实现 <typeparamref name="T"/> 的具体类型实例
/// </summary>
private static IEnumerable<T> CreateInstances<T>(IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances) where T : class
{
foreach (Type type in GetConcreteTypes(assemblies).Where(t => typeof(T).IsAssignableFrom(t)))
{
if (!instances.TryGetValue(type, out object? instance))
{
instance = Activator.CreateInstance(type, true)
?? throw new InvalidOperationException($"无法创建 {type.FullName} 的实例(需要有参数量为 0 的构造函数)。");
instances[type] = instance;
Debug.WriteLine($"[Instance] 已创建:{type.FullName}");
}
else
{
Debug.WriteLine($"[Instance] 复用实例:{type.FullName}");
}
yield return (T)instance;
}
}
private static IEnumerable<Type> GetConcreteTypes(IEnumerable<Assembly> assemblies)
{
return assemblies
.SelectMany(SafeGetTypes)
.Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericTypeDefinition);
}
private static IEnumerable<Type> SafeGetTypes(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
Debug.WriteLine($"[Assembly] 部分类型加载失败:{assembly.FullName}");
foreach (var error in ex.LoaderExceptions)
Debug.WriteLine($"[Assembly] 类型加载错误:{error?.Message}");
return ex.Types.OfType<Type>();
}
}
/// <summary>
/// 应用程序集(模块、Startup 都从这里发现)
/// </summary>
internal static IEnumerable<Assembly> GetApplicationAssemblies()
{
// 使用 AppTypeFinder:扫描应用输出目录 + 已加载程序集,
return StoreAppTypeFinder.Current.GetAssemblies();
}
private sealed class PluginRuntime : IDisposable
{
public List<(IPlugin Plugin, PluginContext Context)> Entries { get; } = [];
public void Dispose()
{
foreach (var entry in Entries) entry.Context.Dispose();
}
}
private static void RunPlugins(IHostApplicationBuilder host, IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
{
var runtime = new PluginRuntime();
var ids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
foreach (var plugin in CreateInstances<IPlugin>(assemblies, instances).OrderByDescending(p => p.Priority))
{
if (plugin is IModule || plugin is IStartup)
throw new InvalidOperationException($"插件 {plugin.GetType().FullName} 不能同时实现 IModule/IStartup。");
if (string.IsNullOrWhiteSpace(plugin.Id) || !ids.Add(plugin.Id))
throw new InvalidOperationException($"插件标识为空或重复:{plugin.Id}");
try
{
var directory = Path.GetDirectoryName(plugin.GetType().Assembly.Location)!;
Debug.WriteLine($"[Plugin] 加载独立配置:{plugin.Id},目录={directory},环境={host.Environment.EnvironmentName}");
var context = new PluginContext(plugin.Id, directory, host.Environment);
runtime.Entries.Add((plugin, context));
host.Services.AddKeyedSingleton<PluginContext>(plugin.Id, (provider, _) =>
{
// 只要上下文被解析,就让容器持有并管理全部插件配置的释放。
provider.GetRequiredService<PluginRuntime>();
return context;
});
var typedContext = typeof(PluginContext<>).MakeGenericType(plugin.GetType());
host.Services.AddSingleton(typedContext, provider =>
// 复用键名注册,确保仅解析泛型上下文时也由容器管理配置的释放。
Activator.CreateInstance(typedContext, provider.GetRequiredKeyedService<PluginContext>(plugin.Id))!);
RunDebugStep("Plugin", $"注册 {plugin.Id} ({plugin.GetType().FullName}),Priority={plugin.Priority}",
() => plugin.ConfigureServices(host.Services, context));
}
catch (Exception ex)
{
Debug.WriteLine($"[Plugin] 注册失败:{plugin.Id},{ex.Message}");
throw new InvalidOperationException($"插件 {plugin.Id} ({plugin.GetType().Assembly.Location}) 注册失败:{ex.Message}", ex);
}
}
host.Services.AddSingleton(_ => runtime);
}
catch { runtime.Dispose(); throw; }
}
private static bool IsSystemInterface(Type type)
{
return type.Namespace is null
|| type.Namespace.StartsWith("System", StringComparison.Ordinal)
|| type.Namespace.StartsWith("Microsoft", StringComparison.Ordinal);
}
private static void RunDebugStep(string category, string description, Action action)
{
Debug.WriteLine($"[{category}] 开始:{description}");
try
{
action();
Debug.WriteLine($"[{category}] 完成:{description}");
}
catch (Exception ex)
{
Debug.WriteLine($"[{category}] 失败:{description},{ex.Message}");
throw;
}
}
public static IApplicationBuilder UseStore(this IApplicationBuilder app)
{
Debug.WriteLine("[Store] 开始装配应用管道");
RunDebugStep("Middleware", "注册统一状态码中间件", () => app.UseMiddleware<UnifyStatusCodeMiddleware>());
foreach (var (plugin, context) in app.ApplicationServices.GetRequiredService<PluginRuntime>().Entries)
{
try { RunDebugStep("Plugin", $"装配管道 {plugin.Id}", () => plugin.Configure(app, context)); }
catch (Exception ex) { throw new InvalidOperationException($"插件 {plugin.Id} 管道装配失败:{ex.Message}", ex); }
}
foreach (IStartup startup in Startups)
{
RunDebugStep("Startup", $"执行启动回调 {startup.GetType().FullName}", () => startup.Start((app as IHost).Services));
}
foreach (IModule module in Modules)
{
RunDebugStep("Module", $"装配管道 {module.GetType().FullName}", () => module.UseModule(app));
}
Debug.WriteLine("[Store] 应用管道装配完成");
return app;
}
}
}