知識全聚集 .Net Core 技術突破 | 如何實現一個模組化方案一

初久的私房菜發表於2020-09-18

簡介

模組化的介紹一共2篇

這一篇我們實現一個功能非常簡單的StartupModules模組化。

第二篇我們來實現一個ABP的模組化效果。

思考

其實來簡單想一下模組化的實驗思路,寫個介面=>模組類繼承該介面=>專案啟動反射檢索=>呼叫介面實現。
那麼具體到程式碼實踐應該怎麼寫呢。

開始

第一步

第一步就是寫一個模組化介面類的嘛!
新建類 IStartupModule

然後寫一個反射檢索全文誰繼承了這個介面的方法
新建類 StartupModulesOptions


程式碼解釋: Activator.CreateInstance 與指定引數匹配程度最高的建構函式來建立指定型別的例項
ps:白話文就是,你給我Type我給你建立個對應的例項
更一個有意思的是 Assembly.GetEntryAssembly()! 這個! 是不是很好奇怕
ps:我第一次看到這個語法也蒙了,問了好多人大家都沒用過,這個語法同TS中的斷言,是非null型別斷言,意思就是我斷言我這個方法返回的內容絕對不是null。

第二步

到這裡來看我們是不是已經拿到了所有繼承介面的模組那麼怎麼在該呼叫的地方呼叫呢,缺啥寫啥

第三步

執行程式碼也有了,我該怎麼呼叫呢。
接下來只要在 在Program 的 WebHost 呼叫.UseStartupModules() 流程就可以載入我們的 ConfigureServices 了

中間來插播一下

請讓我掏出來一個器大的東西來說 他就是: IStartupFilter ,這個東西是個啥東西呢。來看一段原始碼

namespace Microsoft.AspNetCore.Hosting
{
    public interface IStartupFilter
    {
        Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
    }
}

這裡我們看到了 Configure 他返回一個 IApplicationBuilder 他是怎麼用的呢
我們新建一個空的Web專案的時候不知道有沒有注意過 UseStaticFiles 這個函式

 public void Configure(IApplicationBuilder app)
        {
            app.UseStaticFiles();
            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
            });
        }

在這個方法中,你可以直接使用方法提供的IApplicationBuilder引數,並且可以向其中新增各種中介軟體。使用IStartupFilter, 你可以指定並返回一個Action型別的泛型委託,這意味你除了可以使用方法提供的泛型委託配置IApplicationBuilder物件, 還需要返回一個泛型委託。

我們來看一段程式碼 Build(); 這個會呼叫BuildApplication方法

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()    
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseStartup<Startup>()
            .Build();

        host.Run(); 
    }
}

private RequestDelegate BuildApplication()
{
    ..
    IApplicationBuilder builder = builderFactory.CreateBuilder(Server.Features);
    builder.ApplicationServices = _applicationServices;

    var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
    Action<IApplicationBuilder> configure = _startup.Configure;
    foreach (var filter in startupFilters.Reverse())
    {
        configure = filter.Configure(configure);
    }

    configure(builder);

    return builder.Build();
}

首先,此方法建立IApplicationBuilder的例項,該例項將用於構建中介軟體管道,並將ApplicationServices設定為已配置的DI容器。
接下來的程式碼塊很意思。首先,從DI容器中獲取了一個集合IEnumerable<IStartupFilter>
我們可以配置多個IStartupFilter來形成一個管道,所以這個方法只是從容器中取出它們。
現在我們通過迴圈遍歷每個IStartupFilter(以相反的順序),傳入Startup.Configure方法,然後更新區域性變數configure來建立Configure方法的管道。

第四步

我們自己如何來實現 一個IStartupFilter 讓他幫我們呼叫 Configure。

第五步

在 WebHostBuilderExtensions類 UseStartupModules 方法 ConfigureServices 下用 IStartupFilter 注入實現
這樣在Build() 的時候就會呼叫模組的方法了

ActivatorUtilities.CreateInstance<ModulesStartupFilter>(sp, runner) // 第二個引數是在建立例項的時候 給建構函式注入的第一個引數

測試一下

新建 Core Web專案 在 Program.cs
 Host.CreateDefaultBuilder(args)
 .ConfigureWebHostDefaults(webBuilder =>
    {
         // 進行模組對映
         webBuilder.UseStartupModules().UseStartup<Startup>();
});

在 Startup.cs ConfigureServices和Configure 下打一個 Console.WriteLine

新建 類 HangfireStartupModule 繼承 IStartupModule
public class HangfireStartupModule : IStartupModule
{
        public void ConfigureServices(IServiceCollection services)
        {
            Console.WriteLine("HangfireStartupModule----ConfigureServices");
        }
        public void Configure(IApplicationBuilder app)
        {
            Console.WriteLine("HangfireStartupModule----Configure");
        }
}
結果

一個非常簡單的模組化就完工了,當然這個是基礎版本,只能拿來借鑑思路學習下。

補充模組的 ConfigureServices 和 Configure 傳遞上下文

新建類 ConfigureServicesContext 和 ConfigureMiddlewareContext

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

    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 的方法

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

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

鳴謝

玩雙截棍的熊貓、NETCore-大黃瓜

思路來源:https://github.com/henkmollema/StartupModules

相關文章