ASP.NET Core模块化方案1

简介

要实现功能的模块化、插件化,首先模块要可以注册自己的服务和中间件,也就是每个模块要有独立的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();
    }

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>();
                });

执行结果:
ASP.NET Core模块化方案1

上一篇:使用Java Api之删除文件


下一篇:2019 上半年信息系统项目管理师上午综合知识真题(51)