要实现功能的模块化、插件化,首先模块要可以注册自己的服务和中间件,也就是每个模块要有独立的Startup
先实现一个简单的方案,将每个模块的Startup独立
源码:https://gitee.com/dot-net-core/startup-module.git
定义一个启动模块,用于在启动期间配置应用程序服务和中间件。一个应用程序可以为其每个模块定义多个启动模块。
public interface IStartupModule { /// <summary> /// 配置服务 /// </summary> void ConfigureServices(IServiceCollection services, ConfigureServicesContext context); /// <summary> /// 配置中间件管道 /// </summary> void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context); }
表示在启动期间初始服务的类。
public interface IApplicationInitializer { Task Invoke(); }
用于存储模块实例的集合、初始化类实例的集合,以及一些添加模块的方法
/// <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); } } }
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; } }
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; } }
用于执行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); } } } }
它实现了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); }; }
提供一个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) { } }
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>(); });
执行结果: