修改 Startup 加载 和Module 加载
This commit is contained in:
1 parent
c732ecb1d2
commit
c7fb447034
17 files changed
+462
-383
No files matched your search
@@ -1,125 +1,123 @@
|
||||
using MAX.Framework.Interfaces;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
//using MAX.Framework.Interfaces;
|
||||
//using Microsoft.AspNetCore.Builder;
|
||||
//using Microsoft.Extensions.Configuration;
|
||||
//using Microsoft.Extensions.Configuration.Json;
|
||||
//using Microsoft.Extensions.DependencyInjection;
|
||||
//using System.Reflection;
|
||||
|
||||
namespace MAX.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// 宿主(Program.cs)入口:模块两阶段装配 + 模块自己的配置文件。
|
||||
/// <code>
|
||||
/// var builder = WebApplication.CreateBuilder(args);
|
||||
/// builder.AddStoreHub(); // 阶段一:加载各模块配置文件 + 自动执行所有 IModule.AddModule
|
||||
///
|
||||
/// var app = builder.Build();
|
||||
/// // ... 宿主自己的中间件、认证、端点 ...
|
||||
/// app.UseStoreHubModules(); // 阶段二:管道装配(自动执行所有 IModule.UseModule)
|
||||
/// app.Run();
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 在 <see cref="WebApplicationBuilder"/> 上注册应用框架服务:
|
||||
/// <list type="number">
|
||||
/// <item>发现所有 <see cref="IModule"/>;</item>
|
||||
/// <item>把每个模块声明的 <see cref="IModule.ConfigurationFile"/> 并入宿主配置树(宿主 appsettings.json 不需要放模块的配置);</item>
|
||||
/// <item>再执行 <c>builder.Services.AddStoreHub(builder.Configuration, builder.Environment)</c>。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static WebApplicationBuilder AddStoreHub(this WebApplicationBuilder builder)
|
||||
{
|
||||
IEnumerable<Assembly> assemblies = ServiceCollectionExtensions.GetApplicationAssemblies();
|
||||
//namespace MAX.Framework.DependencyInjection
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// 宿主(Program.cs)入口:模块两阶段装配 + 模块自己的配置文件。
|
||||
// /// <code>
|
||||
// /// var builder = WebApplication.CreateBuilder(args);
|
||||
// /// builder.AddStoreHub(); // 阶段一:加载各模块配置文件 + 自动执行所有 IModule.AddModule
|
||||
// ///
|
||||
// /// var app = builder.Build();
|
||||
// /// // ... 宿主自己的中间件、认证、端点 ...
|
||||
// /// app.UseStoreHubModules(); // 阶段二:管道装配(自动执行所有 IModule.UseModule)
|
||||
// /// app.Run();
|
||||
// /// </code>
|
||||
// /// </summary>
|
||||
// public static class HostBuilderExtensions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// 在 <see cref="WebApplicationBuilder"/> 上注册应用框架服务:
|
||||
// /// <list type="number">
|
||||
// /// <item>发现所有 <see cref="IModule"/>;</item>
|
||||
// /// <item>把每个模块声明的 <see cref="IModule.ConfigurationFile"/> 并入宿主配置树(宿主 appsettings.json 不需要放模块的配置);</item>
|
||||
// /// <item>再执行 <c>builder.Services.AddStoreHub(builder.Configuration, builder.Environment)</c>。</item>
|
||||
// /// </list>
|
||||
// /// </summary>
|
||||
// public static WebApplicationBuilder AddStore(this WebApplicationBuilder builder)
|
||||
// {
|
||||
// IEnumerable<Assembly> assemblies = ServiceCollectionExtensions.GetApplicationAssemblies();
|
||||
|
||||
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证后面两个阶段拿到的是同一个对象
|
||||
Dictionary<Type, object> instances = [];
|
||||
ModuleRegistry modules = ServiceCollectionExtensions.DiscoverModules(assemblies, instances);
|
||||
// // 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证后面两个阶段拿到的是同一个对象
|
||||
// Dictionary<Type, object> instances = [];
|
||||
// ModuleRegistry modules = ServiceCollectionExtensions.DiscoverModules(assemblies, instances);
|
||||
|
||||
// 必须在跑 AddModule 之前并入,模块注册时才读得到自己的配置节
|
||||
foreach (IModule module in modules.Modules)
|
||||
{
|
||||
AddModuleConfigurationFile(builder.Configuration, module);
|
||||
}
|
||||
// // 必须在跑 AddModule 之前并入,模块注册时才读得到自己的配置节
|
||||
// foreach (IModule module in modules.Modules)
|
||||
// {
|
||||
// AddModuleConfigurationFile(builder.Configuration, module);
|
||||
// }
|
||||
|
||||
builder.Services.AddStoreHubCore(assemblies, modules, instances, builder.Configuration, builder.Environment);
|
||||
return builder;
|
||||
}
|
||||
// builder.Services.AddStoreHubCore(assemblies, modules, instances, builder.Configuration, builder.Environment);
|
||||
// return builder;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 执行所有模块的管道阶段(<see cref="IModule.UseModule"/>):注册中间件、看板、Map 端点等。
|
||||
/// 必须在 <c>builder.Build()</c> 之后、<c>app.Run()</c> 之前调用;
|
||||
/// 建议放在 <c>UseAuthorization()</c> 之后、<c>MapControllers()</c> 之前。
|
||||
/// </summary>
|
||||
public static WebApplication UseStoreHubModules(this WebApplication app)
|
||||
{
|
||||
// 宿主没调用过 AddStoreHub() 时容器里没有注册表,直接返回即可
|
||||
ModuleRegistry? registry = app.Services.GetService<ModuleRegistry>();
|
||||
if (registry is null)
|
||||
{
|
||||
return app;
|
||||
}
|
||||
// /// <summary>
|
||||
// /// 执行所有模块的管道阶段(<see cref="IModule.UseModule"/>):注册中间件、看板、Map 端点等。
|
||||
// /// </summary>
|
||||
// public static WebApplication UseStoreHubModules(this WebApplication app)
|
||||
// {
|
||||
// // 宿主没调用过 AddStoreHub() 时容器里没有注册表,直接返回即可
|
||||
// ModuleRegistry? registry = app.Services.GetService<ModuleRegistry>();
|
||||
// if (registry is null)
|
||||
// {
|
||||
// return app;
|
||||
// }
|
||||
|
||||
foreach (IModule module in registry.Modules)
|
||||
{
|
||||
try
|
||||
{
|
||||
module.UseModule(app);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 UseModule 执行失败。", ex);
|
||||
}
|
||||
}
|
||||
// foreach (IModule module in registry.Modules)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// module.UseModule(app);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 UseModule 执行失败。", ex);
|
||||
// }
|
||||
// }
|
||||
|
||||
return app;
|
||||
}
|
||||
// return app;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 把模块自己的配置文件并进宿主配置:插在 appsettings*.json 之后、环境变量/命令行之前,
|
||||
/// 这样模块配置能覆盖宿主 appsettings.json,同时仍然允许用环境变量/命令行覆盖它。
|
||||
/// </summary>
|
||||
private static void AddModuleConfigurationFile(IConfigurationBuilder configuration, IModule module)
|
||||
{
|
||||
string? fileName = module.ConfigurationFile;
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// /// <summary>
|
||||
// /// 把模块自己的配置文件并进宿主配置:插在 appsettings*.json 之后、环境变量/命令行之前,
|
||||
// /// 这样模块配置能覆盖宿主 appsettings.json,同时仍然允许用环境变量/命令行覆盖它。
|
||||
// /// </summary>
|
||||
// private static void AddModuleConfigurationFile(IConfigurationBuilder configuration, IModule module)
|
||||
// {
|
||||
// string? fileName = module.ConfigurationFile;
|
||||
// if (string.IsNullOrWhiteSpace(fileName))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
string path = Path.IsPathRooted(fileName)
|
||||
? fileName
|
||||
: Path.Combine(AppContext.BaseDirectory, fileName);
|
||||
// string path = Path.IsPathRooted(fileName)
|
||||
// ? fileName
|
||||
// : Path.Combine(AppContext.BaseDirectory, fileName);
|
||||
|
||||
JsonConfigurationSource source = new()
|
||||
{
|
||||
Path = path,
|
||||
Optional = true, // 文件不存在时不报错(模块没配就是没有)
|
||||
ReloadOnChange = true // 允许改配置不重启(与 appsettings.json 行为一致)
|
||||
};
|
||||
source.ResolveFileProvider();
|
||||
// JsonConfigurationSource source = new()
|
||||
// {
|
||||
// Path = path,
|
||||
// Optional = true, // 文件不存在时不报错(模块没配就是没有)
|
||||
// ReloadOnChange = true // 允许改配置不重启(与 appsettings.json 行为一致)
|
||||
// };
|
||||
// source.ResolveFileProvider();
|
||||
|
||||
IList<IConfigurationSource> sources = configuration.Sources;
|
||||
int index = sources.Count;
|
||||
// IList<IConfigurationSource> sources = configuration.Sources;
|
||||
// int index = sources.Count;
|
||||
|
||||
// 找到最后一个 appsettings*.json,插到它后面
|
||||
for (int i = sources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (sources[i] is FileConfigurationSource file
|
||||
&& Path.GetFileName(file.Path).StartsWith("appsettings", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// // 找到最后一个 appsettings*.json,插到它后面
|
||||
// for (int i = sources.Count - 1; i >= 0; i--)
|
||||
// {
|
||||
// if (sources[i] is FileConfigurationSource file
|
||||
// && Path.GetFileName(file.Path).StartsWith("appsettings", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// index = i + 1;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
sources.Insert(index, source);
|
||||
// sources.Insert(index, source);
|
||||
|
||||
if (configuration is IConfigurationRoot root)
|
||||
{
|
||||
root.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (configuration is IConfigurationRoot root)
|
||||
// {
|
||||
// root.Reload();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -1,20 +0,0 @@
|
||||
using MAX.Framework.Interfaces;
|
||||
|
||||
namespace MAX.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块注册表:保存 <c>AddStoreHub()</c> 阶段发现(并按优先级排序)的模块实例。
|
||||
/// 它被注册进容器,好让 <c>app.UseStoreHubModules()</c> 在 <c>builder.Build()</c> 之后拿到
|
||||
/// 「同一批实例」继续执行管道阶段,而不是重新反射创建一遍。
|
||||
/// </summary>
|
||||
internal sealed class ModuleRegistry
|
||||
{
|
||||
public ModuleRegistry(IReadOnlyList<IModule> modules)
|
||||
{
|
||||
Modules = modules;
|
||||
}
|
||||
|
||||
/// <summary>按优先级降序排列的模块实例</summary>
|
||||
public IReadOnlyList<IModule> Modules { get; }
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,13 @@ using MAX.Core.Interfaces;
|
||||
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;
|
||||
|
||||
@@ -25,70 +28,37 @@ namespace MAX.Framework.DependencyInjection
|
||||
];
|
||||
|
||||
/// <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)
|
||||
public static IServiceCollection AddStore(this IHostApplicationBuilder host)
|
||||
{
|
||||
IEnumerable<Assembly> assemblies = GetApplicationAssemblies();
|
||||
|
||||
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
|
||||
Dictionary<Type, object> instances = [];
|
||||
ModuleRegistry modules = DiscoverModules(assemblies, instances);
|
||||
|
||||
return services.AddStoreHubCore(assemblies, modules, instances, configuration, environment);
|
||||
return AddStoreCore(host, assemblies, instances);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册应用服务的公共实现:宿主入口(<c>builder.AddStoreHub()</c>)与 <see cref="IServiceCollection"/> 入口共用。
|
||||
/// </summary>
|
||||
internal static IServiceCollection AddStoreHubCore(
|
||||
this IServiceCollection services,
|
||||
internal static IServiceCollection AddStoreCore(
|
||||
IHostApplicationBuilder host,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
ModuleRegistry modules,
|
||||
IDictionary<Type, object> instances,
|
||||
IConfiguration? configuration,
|
||||
IHostEnvironment? environment)
|
||||
IDictionary<Type, object> instances)
|
||||
{
|
||||
RegisterDependencyMarkers(services, assemblies);
|
||||
RunStartups(services, assemblies, instances);
|
||||
RegisterDependencyMarkers(host.Services, assemblies);
|
||||
|
||||
ModuleContext context = new(
|
||||
services,
|
||||
new ServiceRegistrarProxy(services),
|
||||
configuration ?? new ConfigurationBuilder().Build(),
|
||||
environment);
|
||||
RunModules(host, assemblies, instances);
|
||||
|
||||
RunModules(modules, context);
|
||||
|
||||
// 注册表必须在 Build() 之前进容器,UseStoreHubModules() 才拿得到
|
||||
services.AddSingleton(modules);
|
||||
RunStartups(host.Services, assemblies, instances);
|
||||
|
||||
|
||||
services.AddControllers(options =>
|
||||
|
||||
host.Services.AddControllers(options =>
|
||||
{
|
||||
options.Filters.Add<GlobalExceptionFilter>();
|
||||
options.Filters.Add<UnifyResultFilter>();
|
||||
@@ -113,7 +83,7 @@ namespace MAX.Framework.DependencyInjection
|
||||
};
|
||||
|
||||
});
|
||||
return services;
|
||||
return host.Services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -149,39 +119,33 @@ namespace MAX.Framework.DependencyInjection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
foreach (IStartup startup in CreateInstances<IStartup>(assemblies, instances).OrderByDescending(s => s.Priority))
|
||||
Startups = CreateInstances<IStartup>(assemblies, instances).OrderByDescending(s => s.Priority);
|
||||
foreach (IStartup startup in Startups)
|
||||
{
|
||||
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)]);
|
||||
}
|
||||
|
||||
private static IEnumerable<IModule> Modules = [];
|
||||
/// <summary>
|
||||
/// 执行所有模块的服务注册阶段(<see cref="IModule.AddModule"/>)
|
||||
/// </summary>
|
||||
private static void RunModules(ModuleRegistry modules, ModuleContext context)
|
||||
private static void RunModules(IHostApplicationBuilder host, IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
|
||||
{
|
||||
foreach (IModule module in modules.Modules)
|
||||
Modules = CreateInstances<IModule>(assemblies, instances).OrderByDescending(s => s.Priority);
|
||||
foreach (IModule module in Modules)
|
||||
{
|
||||
try
|
||||
{
|
||||
module.AddModule(context);
|
||||
module.AddModule(host);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -234,7 +198,7 @@ namespace MAX.Framework.DependencyInjection
|
||||
internal static IEnumerable<Assembly> GetApplicationAssemblies()
|
||||
{
|
||||
// 使用 AppTypeFinder:扫描应用输出目录 + 已加载程序集,
|
||||
return StoreHubAppTypeFinder.Current.GetAssemblies();
|
||||
return StoreAppTypeFinder.Current.GetAssemblies();
|
||||
}
|
||||
|
||||
private static bool IsSystemInterface(Type type)
|
||||
@@ -244,5 +208,20 @@ namespace MAX.Framework.DependencyInjection
|
||||
|| type.Namespace.StartsWith("Microsoft", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
|
||||
public static IApplicationBuilder UseStore(this IApplicationBuilder app)
|
||||
{
|
||||
foreach (IStartup startup in Startups)
|
||||
{
|
||||
startup.Start((app as IHost).Services);
|
||||
}
|
||||
|
||||
foreach (IModule module in Modules)
|
||||
{
|
||||
module.UseModule(app);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ namespace MAX.Framework.DependencyInjection
|
||||
/// 本应用的类型查找器:在 AppTypeFinder 默认前缀(MAX. / Nexus.)之外,
|
||||
/// 增加 StoreHub、KJD 前缀,用于发现宿主和各业务模块的程序集。
|
||||
/// </summary>
|
||||
internal sealed class StoreHubAppTypeFinder : AppTypeFinder
|
||||
internal sealed class StoreAppTypeFinder : AppTypeFinder
|
||||
{
|
||||
public static StoreHubAppTypeFinder Current { get; } = new();
|
||||
public static StoreAppTypeFinder Current { get; } = new();
|
||||
|
||||
protected override List<string> AvailablePrefix()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user