Files
StoreWeb/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs
T
2026-09-18 19:22:49 +08:00

249 lines
8.5 KiB
C#
Raw 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.Framework.Commons;
using MAX.Framework.Dtos;
using MAX.Framework.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Diagnostics;
using System.Reflection;
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>
/// 兼容入口:只有服务集合,没有配置与环境(模块拿到的 <see cref="ModuleContext.Configuration"/> 是空配置),
/// 也拿不到 IConfigurationBuilder,所以模块自己的配置文件不会加载。新代码请用 <c>builder.AddStoreHub()</c>。
/// </summary>
public static IServiceCollection AddStoreHub(this IServiceCollection services)
{
return services.AddStoreHub(null, null);
}
/// <summary>
/// 带配置的注册入口:<c>builder.Services.AddStoreHub(builder.Configuration)</c>
/// </summary>
public static IServiceCollection AddStoreHub(this IServiceCollection services, IConfiguration? configuration)
{
return services.AddStoreHub(configuration, null);
}
/// <summary>
/// 注册应用服务:
/// <list type="number">
/// <item>自动注册所有实现 <see cref="ISingletonDependency"/> / <see cref="IScopedDependency"/> / <see cref="ITransientDependency"/> 的类型;</item>
/// <item>发现所有 <see cref="IStartup"/>,按 <see cref="IStartup.Priority"/> 降序执行 Initialize + Register;</item>
/// <item>发现所有 <see cref="IModule"/>,按 <see cref="IModule.Priority"/> 降序执行 <see cref="IModule.AddModule"/>,
/// 并把模块实例登记进容器,留给 <c>app.UseStoreHubModules()</c> 执行管道阶段。</item>
/// </list>
/// 这个入口不加载模块自己的配置文件,需要模块配置请用 <c>builder.AddStoreHub()</c>。
/// </summary>
public static IServiceCollection AddStoreHub(this IServiceCollection services, IConfiguration? configuration, IHostEnvironment? environment)
{
IEnumerable<Assembly> assemblies = GetApplicationAssemblies();
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
Dictionary<Type, object> instances = [];
ModuleRegistry modules = DiscoverModules(assemblies, instances);
return services.AddStoreHubCore(assemblies, modules, instances, configuration, environment);
}
/// <summary>
/// 注册应用服务的公共实现:宿主入口(<c>builder.AddStoreHub()</c>)与 <see cref="IServiceCollection"/> 入口共用。
/// </summary>
internal static IServiceCollection AddStoreHubCore(
this IServiceCollection services,
IEnumerable<Assembly> assemblies,
ModuleRegistry modules,
IDictionary<Type, object> instances,
IConfiguration? configuration,
IHostEnvironment? environment)
{
RegisterDependencyMarkers(services, assemblies);
RunStartups(services, assemblies, instances);
ModuleContext context = new(
services,
new ServiceRegistrarProxy(services),
configuration ?? new ConfigurationBuilder().Build(),
environment);
RunModules(modules, context);
// 注册表必须在 Build() 之前进容器,UseStoreHubModules() 才拿得到
services.AddSingleton(modules);
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
};
};
});
return services;
}
/// <summary>
/// 自动注册标记接口(ISingletonDependency / IScopedDependency / ITransientDependency)的实现类型
/// </summary>
private static void RegisterDependencyMarkers(IServiceCollection services, IEnumerable<Assembly> assemblies)
{
foreach (Type type in GetConcreteTypes(assemblies))
{
Debug.WriteLine(type.Namespace);
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));
// 注册为实现的接口(排除标记接口与系统接口)
foreach (Type iface in type.GetInterfaces())
{
if (DependencyMarkers.Contains(iface) || IsSystemInterface(iface))
{
continue;
}
services.Add(new ServiceDescriptor(iface, type, lifetime));
}
}
}
/// <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);
foreach (IStartup startup in CreateInstances<IStartup>(assemblies, instances).OrderByDescending(s => s.Priority))
{
startup.Initialize();
startup.Register(registrar);
}
}
/// <summary>
/// 发现所有 <see cref="IModule"/> 实现,只创建实例,不执行任何阶段
/// </summary>
internal static ModuleRegistry DiscoverModules(IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
{
return new ModuleRegistry([.. CreateInstances<IModule>(assemblies, instances).OrderByDescending(m => m.Priority)]);
}
/// <summary>
/// 执行所有模块的服务注册阶段(<see cref="IModule.AddModule"/>)
/// </summary>
private static void RunModules(ModuleRegistry modules, ModuleContext context)
{
foreach (IModule module in modules.Modules)
{
try
{
module.AddModule(context);
}
catch (Exception ex)
{
// 带上模块名,避免只看到一个没有上下文的异常
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败。", 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;
}
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)
{
return ex.Types.OfType<Type>();
}
}
/// <summary>
/// 应用程序集(模块、Startup 都从这里发现)
/// </summary>
internal static IEnumerable<Assembly> GetApplicationAssemblies()
{
// 使用 AppTypeFinder:扫描应用输出目录 + 已加载程序集,
return StoreHubAppTypeFinder.Current.GetAssemblies();
}
private static bool IsSystemInterface(Type type)
{
return type.Namespace is null
|| type.Namespace.StartsWith("System", StringComparison.Ordinal)
|| type.Namespace.StartsWith("Microsoft", StringComparison.Ordinal);
}
}
}