328 lines
13 KiB
C#
328 lines
13 KiB
C#
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;
|
||
}
|
||
|
||
}
|
||
}
|