修改 Startup 加载 和Module 加载

This commit is contained in:
chfei committed 2026-09-23 18:47:54 +08:00
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();
// }
// }
// }
//}