2019年12月12日 星期四

ASP.NET Core Blazor 單向與雙向資料綁定

ASP.NET Core Blazor 單向與雙向資料綁定

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


幾乎所有的 GUI 開發框架,都需要具備有資料綁定 Data Binding 這樣的功能,若正在使用的開發框架 Framework 沒有提供類似這樣的功能,則十分建議可以不用再繼續使用了;而當然,對於強大的 Blazor 開發框架,也是具有這樣的特性,因此,將會透過這篇文章中的兩個頁面範例,進行展示出來這樣好用的功能是要如何來使用。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

單向資料綁定 Oneway Data Binding

在 Blazor 中所謂的單向資料綁定,指的是當 .NET CLR 的物件有異動的時候,將會進行更新到 UI 上的,對於像是在 MVVM Model View ViewModel 設計模式下,對於單向資料綁定可以使用於當 ViewModel 內的屬性有異動的時候,將會更新 View 上的顯示內容,或者是僅有當 View 上的顯示內容有異動的時候,才會更新到 ViewModel 內的屬性上,反之則不會成立。
想要進行這樣的專案開發練習,可以參考底下的操作步驟
  • 打開 Visual Studio 2019 開發工具
  • 當 [Visual Studio 2019] 對話窗出現之後,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,請找出 [Blazor 應用程式] 這個專案開發範本,並且點選這個專案開發範本
  • 請點選右下角 [下一步] 按鈕
  • 出現 [設定新的專案] 對話窗,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱],完成後,請點選右下角 [建立] 按鈕
    在這個範例程式碼中,將會建立一個 BlazorDataBinding 專案名稱
  • 此時將會看到 [建立新的 Blazor 應用程式] 對話窗,這裡可以根據當時開發專案的需要,自行決定是否有調整 Blazor 專案的其他特性,若無,請點選右下角的 [建立] 按鈕
  • 此時,這個 Blazor 專案已經建立完成
現在可以開始來建立一個單向資料綁定的頁面,練習如何設計單向資料綁定的程式設計方法:
  • 滑鼠右擊 [Pages] 資料夾,選擇 [加入] > [新增項目] 功能項目
  • 當 [新增項目] 對話窗顯示之後,請找到並且選擇 [Blazor 元件] 這個項目名稱
  • 在左下方的名稱欄位中,輸入該 Blazor 元件的名稱
    這裡將會建立一個 OnewayBinding 新 Blazor 元件
    Blazor 元件檔案名稱都會使用 .razor 作為其副檔名
  • 最後,點選右下角的 [新增] 按鈕
請將底下的 Blazor 頁面之標記與程式碼,寫入到這個檔案內
@page "/OnewayBinding"
@using System.Threading
@using System.Threading.Tasks

<h3>單向資料綁定</h3>

<div>
    @Message
</div>
<buttn class="btn btn-outline-primary"
       @onclick="Start">開始動作</buttn>
<div class="@background" style="height:300px;border:none">
    @background
</div>

@code {
    string Message = "尚未接收到任何訊息";
    string background = "bg-transparent";
    int index = 0;
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken token;

    void Start()
    {
        Message = "已經接收到要開始動作指令了";
    }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            CancellationToken token = cts.Token;
            ShowCycle(token);
        }
        return Task.FromResult(0);
    }
    async Task ShowCycle(CancellationToken token)
    {
        while(true)
        {
            ShowColor();
            StateHasChanged();
            await Task.Delay(900);
            if(token.IsCancellationRequested)
            {
                break;
            }
        }
    }
    void ShowColor()
    {
        switch (index)
        {
            case 0:
                background = "bg-primary";
                break;
            case 1:
                background = "bg-secondary";
                break;
            case 2:
                background = "bg-success ";
                break;
            case 3:
                background = "bg-danger";
                break;
            case 4:
                background = "bg-warning";
                break;
            case 5:
                background = "bg-info";
                break;
            case 6:
                background = "bg-light";
                break;
            case 7:
                background = "bg-dark";
                break;
            case 8:
                background = "bg-white";
                break;
            default:
                break;
        }
        index++;
        if (index > 8) index = 0;
    }
}
在這個頁面中,將會宣告一個 Message 變數,並且透過 Razor 語法,將這個變數顯示在 HTML 標記內,如同這樣 <div>@Message</div> ,如此,當 Message 變數值有異動的時候,就會變更 HTML 上面的內容,也就達成了更新網頁內容的目的;這一切的效果都會透過 Blazor 內建的資料綁定機制來完成,而這裡所使用的技巧就是單向 Oneway Data Binding 資料綁定技術,最重要的是,所有的過程,開發者都不再需要透過 JavaScript 或者相關 JS 開發框架 Angular / React / Vue 等等來達成。
為了要能夠呈現出當 Message 變數有變動,網頁會如何顯示,這裡將會標記一個 <button> 標記,並且使用 @onclick="Start" 這樣的語法來宣告,當這個按鈕被觸發的時候,將會執行該頁面中的 Start 方法,在 Start() 方法內,將會執行 Message = "已經接收到要開始動作指令了"; 敘述,也就是說 Message 這個變數值已經變動了,此時,可以觀察網頁畫面,就會看到這個頁面上的文字,會從 [尚未接收到任何訊息] 轉變成為 [尚未接收到任何訊息] ,這一切發生的效果是不是很神奇呀。
另外,在這個頁面中,也會使用到 Blazor Componet Lifecycle 元件生命週期的事件:OnAfterRenderAsync ,該事件會當整個網頁已經顯示在瀏覽器上的時候,會被觸發執行,在這個事件委派方法內,將會執行 ShowCycle() 方法,這是一個 Fire and Forget 射後不理 的非同步工作方法。
在 ShowCycle 方法內,每隔 0.9 秒的時間,變更 background 變數的數值,這個變數將會綁定到網頁上的 div 標籤的 class 屬性上,也就是將這個變數字串內容,設定為各種 Bootstrap 4 的背景顏色類別宣告,這樣將會達成每隔 0.9 秒的時間,就會更換這個 div 標記內的背景顏色效果;因為該 div 的 class 宣告值有異動,透過 Blazor 的重新渲染機制,將會更新網頁中 DOM 內容,造成顏色有變化效果。
不過,要特別注意的是,當 background 字串值變動之後,將會需要呼叫 StateHasChanged() 這個方法,該方法將會 通知組件其狀態已更改。如果適用,這將導致重新渲染組件;也就是說,若沒有加入這個方法呼叫,每隔 0.9 秒將不會有任何背景顏色的變化出來。
底下將會是執行後的螢幕畫面
一開始顯示網頁的時候,在按鈕的上方文字會顯示為 : 尚未接收到任何訊息,而在最下方的區塊,將會不斷地變換背景顏色。
現在,可以點選螢幕上的按鈕,此時該按鈕的上方文字將會顯示為 : 已經接收到要開始動作指令了
這一切的成果,都將會透過 Blazor 單向資料綁定來做到。

雙向資料綁定 Twoway Data Binding

現在,要來體驗 雙向資料綁定 Twoway Data Binding,在 Blazor 中所謂的雙向資料綁定,指的是當 .NET CLR 的物件有異動的時候,將會進行更新到 UI 上,而當網頁上綁定的值有變動的時候,例如,使用者在該 UI 上輸入了任何資料,此時,將會更新到 .NET CLR 的變數上,反之亦然,也就是當 .NET CLR 變數有變動的時候,也會更新到 HTML 網頁上 DOM 內容。
現在可以開始來建立一個雙向資料綁定的頁面,練習如何設計雙向資料綁定的程式設計方法:
  • 滑鼠右擊 [Pages] 資料夾,選擇 [加入] > [新增項目] 功能項目
  • 當 [新增項目] 對話窗顯示之後,請找到並且選擇 [Blazor 元件] 這個項目名稱
  • 在左下方的名稱欄位中,輸入該 Blazor 元件的名稱
    這裡將會建立一個 TwowayBinding 新 Blazor 元件
    Blazor 元件檔案名稱都會使用 .razor 作為其副檔名
  • 最後,點選右下角的 [新增] 按鈕
請將底下的 Blazor 頁面之標記與程式碼,寫入到這個檔案內
@page "/TwowayBinding"

<h3>雙向資料綁定</h3>

<input type="text" value="@Message1" />
<div>@Message1</div>
<input type="text" @bind="Message2" />
<div>@Message2</div>
<input type="range" class="form-control-range" max="8" min="0" step="1"
       @bind="Index" @bind:event="oninput" />
<div class="@background" style="height:200px;border:none">
    @background
</div>
@code {
    string Message1 = "Message1";
    string Message2 = "Message2";
    string background = "bg-transparent";
    private int index;

    public int Index
    {
        get { return index; }
        set
        {
            index = value;
            ShowColor();
        }
    }

    void ShowColor()
    {
        switch (Index)
        {
            case 0:
                background = "bg-primary";
                break;
            case 1:
                background = "bg-secondary";
                break;
            case 2:
                background = "bg-success ";
                break;
            case 3:
                background = "bg-danger";
                break;
            case 4:
                background = "bg-warning";
                break;
            case 5:
                background = "bg-info";
                break;
            case 6:
                background = "bg-light";
                break;
            case 7:
                background = "bg-dark";
                break;
            case 8:
                background = "bg-white";
                break;
            default:
                break;
        }
    }
}
在這個頁面中,將會同樣的會展現兩個功能特色,在上半部會有兩個 input 文字輸入盒,而在這兩個 input 文字輸入盒的下方,將會有 div 標籤,將綁定在 input 文字輸入盒內的文字內容,顯示在網頁上;在這兩個 input 宣告標記中,可以看到使用了不同的宣告語法,在前者將會把 HTML 內的這個 input 標記之 value 屬性,也就是使用這樣的 value="@Message1" 宣告語法,綁定到 .NET CLR 的 Message1 這個變數內,而這樣的做法將會是屬於單向資料綁定,也就是說,若使用者在網頁上的第一個 input 上輸入任何文字,將不會更新到 Message1 這個變數上,也就是對於 <div>@Message1</div> 標記而言,顯示的內容都是相同的,因為 Blazor 不會透過 DOM 內容,來變更這個 div 內的文字內容。
對於第二個 input 標記而言,將會使用了 @bind="Message2" 這樣的宣告語法,這是一個雙向資料綁定的宣告語法,也就是說,當使用者在第二個 input 文字輸入盒,輸入了任何文字,將會造成下方的 <div>@Message2</div> 標記也會更新,因為,當使用者變更 input 輸入文字內容,就會同時更新 .NET CLR Message2 物件內的值,而當這個 Message2 的物件值變動後,就會透過資料綁定機制,更新了 DOM 內 <div>@Message2</div> 標記內容,如此,將會造成可以顯示出使用者最新輸入的文字內容了。
在最下方的 input 標記,宣告型別為 type="range" 來形成一個 滑動桿 輸入控制項, 這裡宣告了 @bind="Index" 語法,讓滑動桿輸入的值,可以使用雙向資料綁定的方式,綁定到 .NET CLR 內的 Index 變數內,另外,為了要能夠做到當滑動桿的值又變化的時候,可以立即觸發雙向資料綁定的效果,而不再使用滑鼠點擊到網頁的其他地方,來觸發雙向資料綁定的動作,因此,在這裡將會使用 @bind:event="oninput" 這樣的宣告方式,設定了只要滑動桿的值有變化的時候,立即會觸發雙向資料綁定動作。
當因為雙向資料綁定機制,導致 .NET CLR 的 Index 變數有變動的時候,將會使得該屬性 C# Property 之設定存取子被執行,這裡將會把 index 這個支援欄位的變數值進行更新,接著,將會呼叫 ShowColor 方法,如此,將會造成最下方的 div 標記的背景顏色有所變更。
這裡可以觀察到,這裡將沒有呼叫 StateHasChanged() 這個方法,但是,同樣的會進行更新 DOM 的內容,也就是會變更 HTML 顯示內容。
底下將會是執行後的螢幕畫面
當一開始執行的時候,最上方的兩個文字輸入盒,都會顯示預設的 .NET CLR 變數值,這是因為,雙方都具有單向綁定的特性。
現在,在第一個文字輸入盒內,輸入任何文字之後,發現到其下方的 div 標記內的文字,沒有同步更新,這是因為第一個 input 使用的是單向資料綁定的宣告語法。
對於第二個 input 文字輸入盒,在此輸入任何文字,便可以看到底下的 div 區塊內的文字,有同步更新,這是因為這裡使用了雙向資料綁定的宣告語法。
接著請使用滑鼠滑動滑動桿,便可以看到滑動桿的下方 div 區塊的背景顏色,會不斷的變化顏色




2019年12月11日 星期三

ASP.NET Core Blazor dependency injection 之元件的存留期

ASP.NET Core Blazor dependency injection 之元件的存留期

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


接續上篇文章 ASP.NET Core Blazor 在元件 Component 上的注入各種服務的存留期研究,實際進行 ASP.NET Core 中的三種存留期的注入使用方式與表現狀態,在這篇文章中,將來檢視 Blazor 中的 元件 Component Scoped 具範圍的行為表現。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載
在這裡將會建立一個 Blazor 伺服器端的專案,並且在 Pages 資料夾下,建立一個 Blazor 元件 , DifferentInjection ,其程式碼如下:
@page "/DifferentInjection" @inherits OwningComponentBase<IMessageScoped>
  @inject IMessageTransient messageTransient1 @inject IMessageTransient
  messageTransient2 @inject IMessageScoped messageScoped1 @inject IMessageScoped
  messageScoped2 @inject IMessageSingleton messageSingleton1 @inject
  IMessageSingleton messageSingleton2

  <h3>DifferentInjection</h3>

  <div class="bg-info">
    messageTransient1 的物件為 @messageTransient1.Hash()
  </div>
  <div class="bg-warning">
    messageTransient2 的物件為 @messageTransient2.Hash()
  </div>
  <div class="bg-info">messageScoped1 的物件為 @messageScoped1.Hash()</div>
  <div class="bg-warning">messageScoped1 的物件為 @messageScoped2.Hash()</div>
  <div class="bg-info">
    messageSingleton1 的物件為 @messageSingleton1.Hash()
  </div>
  <div class="bg-warning">
    messageSingleton2 的物件為 @messageSingleton2.Hash()
  </div>
  <div />
  <div class="btn btn-outline-danger">
    messageTransient3 的物件為 @messageTransient3.Hash()
  </div>
  <div class="btn btn-outline-success">
    messageScoped3 的物件為 @messageScoped3.Hash()
  </div>
  <div class="btn btn-outline-dark">
    messageSingleton3 的物件為 @messageTransient3.Hash()
  </div>
  <div class="btn btn-outline-warning">
    OwningComponentBase 的物件為 @Service.Hash()
  </div>

  @code { [Inject] public IMessageTransient messageTransient3 { get; set; }
  [Inject] public IMessageScoped messageScoped3 { get; set; } [Inject] public
  IMessageSingleton messageSingleton3 { get; set; } }</IMessageScoped
>
在上面使用了兩個新的注入技巧,一個是在最上面使用了 @inherits OwningComponentBase<IMessageSingleton> 語法,這樣將會使用了元件具範圍的方式來注入該物件,而且,一個頁面上僅能夠宣告一個,不能多個,這是為什麼呢?請大家思考看看。
另外一個注入物件的方式,那就是宣告一個屬性 Property,並且使用 Inject 屬性 Attribute 來標示建立的 C# Property,這樣也可以達到注入物件的效果,現在來看看執行效果:
這裡是第一次執行的結果
不論是使用 @inject 語法,或者使用 [inject] attribute 屬性宣告方式,都可以達到注入物件的效果,而且所注入的物件,也都是按照當初註冊的存留期設定方式來表現;對於最後一個元件具範圍,需要使用 Service 這個變數來存取注入的物件,例如,在這裡是使用 @Service.Hash() 這樣的方式,而且從螢幕截圖的最下方可以看到(黃色底、黑色文字),這裡並沒有使用這次請求 Request 所使用的 Scoped 物件,而是獲得另外一個新的物件,雖然當初宣告的語法為 OwningComponentBase<IMessageScoped> 是要注入一個 Scoped 的物件,不過,這裡是享有 元件具範圍 Component Scoped 的特性。
底下為重新整理同一個網頁的執行結果
其中,因為產生一個新的 Request,所以,對於 Scoped 的存留期的注入,將會重新配置一個新的物件,並且不論使用哪種注入語法;對於 Singleton 的存留期注入,得到的還是同一個物件,與尚未整理前看到的相同。




2019年12月10日 星期二

ASP.NET Core Blazor 在元件 Component 上的注入各種服務的存留期研究

ASP.NET Core Blazor 在元件 Component 上的注入各種服務的存留期研究

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


當在使用 Blazor 進行專案開發的時候,可以使用 ASP.NET Core 提供的相依性注入服務功能,此時僅需要宣告與建立所需要的抽象介面與具體實作類別,接著在 Startup 類別內的 ConfigureServices 方法內,進行註冊這些抽象介面與類別到相依性入 DI Container 容器內;當想要使用這些服務的時候,便可以在所建立的元件 Component 內,使用 @inject 語法便可以將這些服務注入到該元件內來使用。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載
底下將會是關於抽象型別與具體實作類別和 Startup 類別的程式碼
namespace BlazorScopedSingleton
{
    public interface IMessageTransient
    {
        string Hash();
    }
    public interface IMessageScoped
    {
        string Hash();
    }
    public interface IMessageSingleton
    {
        string Hash();
    }
    public class MessageTransient : IMessageTransient
    {
        public string Hash()
        {
            return $"MessageTransient : {this.GetHashCode()}";
        }
    }
    public class MessageScoped : IMessageScoped
    {
        public string Hash()
        {
            return $"MessageScoped : {this.GetHashCode()}";
        }
    }
    public class MessageSingleton : IMessageSingleton
    {
        public string Hash()
        {
            return $"MessageSingleton : {this.GetHashCode()}";
        }
    }
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // 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.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();

            services.AddTransient<IMessageTransient, MessageTransient>();
            services.AddScoped<IMessageScoped, MessageScoped>();
            services.AddSingleton<IMessageSingleton, MessageSingleton>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}
在上面的程式碼中,將會建立三個介面並且使用這三個介面分別實作三個具體實作類別,從這些介面與類別名稱,可以充分表達是要進行 DI 存留期 Transient , Scoped , Singleton 的測試,了解到這些相依性注入的存留期在 Blazor 中表現行為為何。
這裡所建立的 Blazor 專案為 Serivce 端的 Blazor 專案。
首先,將會建立一個名為 ForContainer 的 Component 元件,其程式碼如下所示
@page "/ForContainer"
@inject IMessageTransient messageTransient1
@inject IMessageTransient messageTransient2
@inject IMessageScoped messageScoped1
@inject IMessageScoped messageScoped2
@inject IMessageSingleton messageSingleton1
@inject IMessageSingleton messageSingleton2

<h3>ForContainer</h3>

<div class="bg-info">messageTransient1 的物件為 @messageTransient1.Hash()</div>
<div class="bg-warning">messageTransient2 的物件為 @messageTransient2.Hash()</div>
<div class="bg-info">messageScoped1 的物件為 @messageScoped1.Hash()</div>
<div class="bg-warning">messageScoped1 的物件為 @messageScoped2.Hash()</div>
<div class="bg-info">messageSingleton1 的物件為 @messageSingleton1.Hash()</div>
<div class="bg-warning">messageSingleton2 的物件為 @messageSingleton2.Hash()</div>

@code {

}
在這裡,將會使用 @inject 語法,將不同介面所需要的服務,注入到這個元件中,其中,每個介面都會注入兩次到不同的變數內,以便觀察有何變化。
現在可以執行專案,並且切換網址列為 https://localhost:5001/ForContainer
底下為第一次執行的結果,從這裡可以看的出來,當使用 Transient 存留期註冊的服務,只要每次注入這個服務的時候,就會得到不同的物件,因為 DI Container 容器會立即產生出的新的執行個體出來;當使用 Scoped 存留期,因為是在同一個 Connection ,所以,會得到同一個執行個體物件,同樣的,Singleton 存留期,在這次執行過程中,也是得到相同的物件。
現在,在瀏覽器上開啟一個新的標籤頁次,輸入同樣的網址,看看執行結果
當然,對於 Transient 存留期,同樣的會產生兩個嶄新的物件,而對於 Scoped 的注入請求,因為在這裡是一個新的連線,因此,在這次新的連線過程中,多次注入同一個介面,會得到同一個物件,這點也是沒有問題;而對於 Singleton 存留期,因為這個專案還在執行中,所以,將會得到與上面同樣的物件,沒有任何變化。
此時,再來產生一個新的元件,在這個元件中,將會使用剛剛設計的 ForContainer 元件兩次,現在,來看看這樣會有甚麼執行結果。
@page "/ForMultipleComponent"
@using BlazorScopedSingleton.Pages

<h3>ForMultipleComponent</h3>

<ForContainer />
<ForContainer />

@code {

}
這裡將會打開 https://localhost:5001/ForMultipleComponent 網址,執行結果如下面螢幕截圖
很清楚的,只要這個專案還在執行階段,只要使用 Singleton 方式來注入,不論是單一元件,還是開多個網頁甚至使用巢狀元件的方式,都會得到同一個物件,這裡的表現符合預期;而對 Transient 而言,也是一樣,只要每次注入的時候,都會得到一個新的物件;至於 Scoped 這樣的存留期,將過這裡的測試,得到只要是在同一個連現階段,不論是對於單一元件或者同樣的元件要顯示多次,都會在同一個連線過程中,得到同一個物件。
現在,再來重新整理這個網址 https://localhost:5001/ForMultipleComponent 網址,執行結果如下面螢幕截圖,這樣的結果也是符合預期的。




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