2019年12月9日 星期一

ASP.NET Core 的相依性注入容器 Dependency Injection IoC Container 進行註冊同一個抽象介面多次的研究

ASP.NET Core 的相依性注入容器 Dependency Injection IoC Container 進行註冊同一個抽象介面多次的研究

當在使用 ASP.NET Coer 專案來進行開發的時候,若在 Startup 類別中的 ConfigureServices 方法內使用 IServiceCollection 實作物件來進行需要用到的服務註冊,若發生底下的情況,究竟會發生甚麼問題呢?
  • 同一個介面,註冊了不同的具體實作類別,會有甚麼情況?
  • 當要進行解析的時候,究竟會取得哪個具體實作類別呢?
  • IServiceCollection 內,是會存在於多筆的同一個介面註冊資訊,還是會只有一個呢?
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

測試程式碼說明

在這裡,將會建立一個空白的 ASP.NET Core 專案,並且在 Startup.cs 這個專案內,填入底下程式碼:
namespace MultiDIRegister
{
    public interface IMessage
    {
        string Output(string msg);
    }
    public class ConsoleMessage : IMessage
    {
        public string Output(string msg)
        {
            return $"Console : {msg}</br>";
        }
    }
    public class FileMessage : IMessage
    {
        public string Output(string msg)
        {
            return $"File : {msg}</br>";
        }
    }
    public class Startup
    {
        IServiceCollection Services;
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            Services = services;
            services.AddTransient<IMessage, ConsoleMessage>();
            services.AddTransient<IMessage, FileMessage>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
            IMessage message)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var item in Services)
            {
                if(item.ServiceType.Name.Contains("IMessage"))
                {
                    sb.Append($"{item.ServiceType.Name} => {item.ImplementationType.Name}");
                    sb.Append("</br>");
                }
            }
                if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync(message.Output("Hello World!"));
                    await context.Response.WriteAsync(sb.ToString());
                });
            });
        }
    }
}
在這裡將會建立一個 IMessage 抽象介面型別,在該介面內僅會宣告一個方法 Output 該方法將會回傳一個字串,其中可以從回傳的字串得知這是由哪個具體實作類別所產生的;接著設計兩個類別 ConsoleMessage 與 FileMessage ,這兩個類別都會實作 IMessage
由於需要知道 .NET Core 的 DI 容器內究竟有多少筆 IMessage 介面的註冊資料,因此,宣告一個欄位在 Startup 類別內,使用 IServiceCollection Services; 語法,接著會在 ConfigureServices 方法內將 IServiceCollection 的物件設定給 Services 欄位變數。
另外,在 ConfigureServices 方法內,使用 services.AddTransient<IMessage, ConsoleMessage>(); services.AddTransient<IMessage, FileMessage>(); 同時註冊兩筆同樣的介面,卻對應到不同的類別上。
另外,將會在 Configure 方法內,注入 IMessage 的具體實作物件,看看輸出結果,究竟是由哪個類別來產生的呢?還有要來檢查 IServiceCollection 的物件內,究竟有多少筆關於 IMessage 的紀錄產生呢?
現在,來進行測試看看各種不同問題:

同一個介面,註冊了不同的具體實作類別,會有甚麼情況?

若執行了上述的程式碼,會造成這個專案當掉嗎?
答案是不會喔,整體專案可以正常的執行與運作

當要進行解析的時候,究竟會取得哪個具體實作類別呢?

這樣的話,當需要注入 IMessage 介面的具體實作物件的時候,到底會注入哪個類別產生的執行個體 Instance 呢?
從執行結果可以看的出來,ASP.NET Core 的 DI 容器,將會使用最後進行註冊的 FileMessage 類別所產生的物件,因此,在這裡得到一個結論,若同一個抽象型別,註冊了多筆紀錄到 Dependency Injection Container 容器內,將會由最後註冊的紀錄取得勝利,而不是先註冊先贏喔

IServiceCollection 內,是會存在於多筆的同一個介面註冊資訊,還是會只有一個呢?

從執行結果可以清出的看到,這兩筆註冊紀錄,都存在於 DI Container 內

執行結果

File : Hello World!
IMessage => ConsoleMessage
IMessage => FileMessage



沒有留言:

張貼留言