简介
要实现功能的模块化、插件化,首先模块要可以注册自己的服务和中间件,也就是每个模块要有独立的Startup
先实现一个简单的方案,将每个模块的Startup独立
开始
模块接口
定义一个启动模块,用于在启动期间配置应用程序服务和中间件。一个应用程序可以为其每个模块定义多个启动模块。
public interface IStartupModule
{
/// <summary>
/// 配置服务
/// </summary>
void ConfigureServices(IServiceCollection services, ConfigureServicesContext context);
/// <summary>
/// 配置中间件管道
/// </summary>
void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context);
}
初始化类接口
表示在启动期间初始服务的类。
public interface IApplicationInitializer
{
Task Invoke();
}
StartupModulesOptions
用于存储模块实例的集合、初始化类实例的集合,以及一些添加模块的方法
/// <summary>
/// 启动模块的选项
/// </summary>
public class StartupModulesOptions
{
/// <summary>
/// 模块集合
/// </summary>
public ICollection<IStartupModule> StartupModules { get; } = new List<IStartupModule>();
/// <summary>
/// 初始化类集合
/// </summary>
public ICollection<Type> ApplicationInitializers { get; } = new List<Type>();
/// <summary>
/// Settings
/// </summary>
public IDictionary<string, object> Settings { get; set; } = new Dictionary<string, object>();
/// <summary>
/// 从入口程序集发现模块
/// </summary>
public void DiscoverStartupModules() => DiscoverStartupModules(Assembly.GetEntryAssembly()!);
/// <summary>
/// 从指定程序集发现模块
/// </summary>
public void DiscoverStartupModules(params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0 || assemblies.All(a => a == null))
{
throw new ArgumentException("No assemblies to discover startup modules from specified.", nameof(assemblies));
}
foreach (var type in assemblies.SelectMany(a => a.ExportedTypes))
{
if (typeof(IStartupModule).IsAssignableFrom(type))
{
var instance = Activate(type);
StartupModules.Add(instance);
}
else if (typeof(IApplicationInitializer).IsAssignableFrom(type))
{
ApplicationInitializers.Add(type);
}
}
}
/// <summary>
/// 添加IStartupModule类型的实例
/// </summary>
public void AddStartupModule<T>(T startupModule) where T : IStartupModule
=> StartupModules.Add(startupModule);
/// <summary>
/// 添加IStartupModule类型的实例
/// </summary>
public void AddStartupModule<T>() where T : IStartupModule
=> AddStartupModule(typeof(T));
/// <summary>
/// 添加IStartupModule类型的实例
/// </summary>
public void AddStartupModule(Type type)
{
if (typeof(IStartupModule).IsAssignableFrom(type))
{
var instance = Activate(type);
StartupModules.Add(instance);
}
else
{
throw new ArgumentException(
$"Specified startup module '{type.Name}' does not implement {nameof(IStartupModule)}.",
nameof(type));
}
}
/// <summary>
/// Adds an inline middleware configuration to the application.
/// </summary>
/// <param name="action">A callback to configure the middleware pipeline.</param>
public void ConfigureMiddleware(Action<IApplicationBuilder, ConfigureMiddlewareContext> action) =>
StartupModules.Add(new InlineMiddlewareConfiguration(action));
private IStartupModule Activate(Type type)
{
try
{
return (IStartupModule)Activator.CreateInstance(type)!;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create instance for {nameof(IStartupModule)} type '{type.Name}'.", ex);
}
}
}
ConfigureMiddlewareContext
Configure方法用到的上下文
public class ConfigureMiddlewareContext
{
public ConfigureMiddlewareContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, IServiceProvider serviceProvider, StartupModulesOptions options)
{
Configuration = configuration;
HostingEnvironment = hostingEnvironment;
ServiceProvider = serviceProvider;
Options = options;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment HostingEnvironment { get; }
public IServiceProvider ServiceProvider { get; }
public StartupModulesOptions Options { get; }
}
ConfigureServicesContext
ConfigureService方法用到的上下文
public class ConfigureServicesContext
{
public ConfigureServicesContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, StartupModulesOptions options)
{
Configuration = configuration;
HostingEnvironment = hostingEnvironment;
Options = options;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment HostingEnvironment { get; }
public StartupModulesOptions Options { get; }
}
StartupModuleRunner
用于执行StartupModulesOptions中模块、初始化类的方法
public class StartupModuleRunner
{
private readonly StartupModulesOptions _options;
public StartupModuleRunner(StartupModulesOptions options)
{
_options = options;
}
/// <summary>
/// 调用模块的ConfigureServices方法
/// </summary>
public void ConfigureServices(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
{
var ctx = new ConfigureServicesContext(configuration, hostingEnvironment, _options);
foreach (var cfg in _options.StartupModules)
{
cfg.ConfigureServices(services, ctx);
}
}
/// <summary>
/// 调用模块的Configure方法
/// </summary>
public void Configure(IApplicationBuilder app, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
{
using var scope = app.ApplicationServices.CreateScope();
var ctx = new ConfigureMiddlewareContext(configuration, hostingEnvironment, scope.ServiceProvider, _options);
foreach (var cfg in _options.StartupModules)
{
cfg.Configure(app, ctx);
}
}
/// <summary>
/// 调用IApplicationInitializer实例。
/// </summary>
public async Task RunApplicationInitializers(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var applicationInitializers = _options.ApplicationInitializers
.Select(t =>
{
try
{
return ActivatorUtilities.CreateInstance(scope.ServiceProvider, t);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create instace of {nameof(IApplicationInitializer)} '{t.Name}'.", ex);
}
})
.Cast<IApplicationInitializer>();
foreach (var initializer in applicationInitializers)
{
try
{
await initializer.Invoke();
}
catch (Exception ex)
{
throw new InvalidOperationException($"An exception occured during the execution of {nameof(IApplicationInitializer)} '{initializer.GetType().Name}'.", ex);
}
}
}
}
ModulesStartupFilter
它实现了IStartupFilter,在调用入口程序集的Startup的Configure方法之前执行
它调用配置的IStartupModule中的Configure方法和IApplicationInitializer
public class ModulesStartupFilter : IStartupFilter
{
private readonly StartupModuleRunner _runner;
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _hostingEnvironment;
public ModulesStartupFilter(StartupModuleRunner runner, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
{
_runner = runner;
_configuration = configuration;
_hostingEnvironment = hostingEnvironment;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => app =>
{
_runner.Configure(app, _configuration, _hostingEnvironment);
_runner.RunApplicationInitializers(app.ApplicationServices).GetAwaiter().GetResult();
next(app);
};
}
InlineMiddlewareConfiguration
提供一个IStartupModule类型,可以不创建特定的IStartupModule类,只指定Configure方法的委托
public class InlineMiddlewareConfiguration : IStartupModule
{
private readonly Action<IApplicationBuilder, ConfigureMiddlewareContext> _action;
public InlineMiddlewareConfiguration(Action<IApplicationBuilder, ConfigureMiddlewareContext> action)
{
_action = action;
}
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) => _action(app, context);
public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context) { }
}
WebHostBuilderExtensions
WebHostBuilder的扩展类
public static class WebHostBuilderExtensions
{
/// <summary>
/// 配置启动模块并从入口程序集自动发现IStartupModule
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> instance.</param>
/// <returns>The <see cref="IWebHostBuilder"/> instance.</returns>
public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder) =>
UseStartupModules(builder, options => options.DiscoverStartupModules());
/// <summary>
/// 配置启动模块并从指定程序集自动发现IStartupModule
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> instance.</param>
/// <param name="assemblies">The assemblies to discover startup modules from.</param>
/// <returns>The <see cref="IWebHostBuilder"/> instance.</returns>
public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder, params Assembly[] assemblies) =>
UseStartupModules(builder, options => options.DiscoverStartupModules(assemblies));
/// <summary>
/// 为StartupModulesOptions配置启动模块
/// </summary>
public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder, Action<StartupModulesOptions> configure)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var options = new StartupModulesOptions();
configure(options);
if (options.StartupModules.Count == 0 && options.ApplicationInitializers.Count == 0)
{
return builder;
}
var runner = new StartupModuleRunner(options);
builder.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IStartupFilter>(sp => ActivatorUtilities.CreateInstance<ModulesStartupFilter>(sp, runner));
var configureServicesContext = new ConfigureServicesContext(hostContext.Configuration, hostContext.HostingEnvironment, options);
runner.ConfigureServices(services, hostContext.Configuration, hostContext.HostingEnvironment);
});
return builder;
}
}
如何使用
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartupModules(options=>options.DiscoverStartupModules(typeof(Module1StartupModule).Assembly)).UseStartup<Startup>();
});
执行结果: