2019年6月15日 星期六

ASP.NET Core 在相依性注入容器,使用 具範圍與單一 註冊不同介面對應到同一個類別

ASP.NET Core 在相依性注入容器,使用 具範圍與單一 註冊不同介面對應到同一個類別

在 ASP.NET Core 使用預設的相依性注入容器來進行 DI 注入服務物件的時候,若有兩個介面,IMessageSingleton1 / IMessageSingleton2 ,但是有個類別 MessageClass 分別有實作這兩個介面;現在,當使用 單一 Singleton 存留期 Lifetime 模式,分別的宣告這兩個介面都要對應到 MessageClass 類別上。現在的問題是,當要某個類別或者網站服務的控制器類別內,分別注入 IMessageSingleton1 / IMessageSingleton2 這兩個介面,此時,這兩個介面則會得到同一個 MessageClass 服務物件呢?還是會得到兩個分別獨立 MessageClass 物件呢?而對於另外一種 具範圍 Scoped 存留期 Lifetime 的狀態,同樣的宣告兩個介面 IMessageScope1 / IMessageScope2 ,並且讓 MessageClass 也實作這兩個介面,最後,使用 具範圍 Scoped 存留期 Lifetime 來分別在相依性注入容器內來註冊這兩個介面到 MessageClass 類別上。在此,產生同樣的問題那就是,在這樣的情境下,當要某個類別或者網站服務的控制器類別內,分別注入 IMessageScope1 / IMessageScope2 這兩個介面,此時,這兩個介面則會得到同一個 MessageClass 服務物件呢?還是會得到兩個分別獨立 MessageClass 物件呢?
要了要了解這些問題究竟會產生甚麼樣的結果,就在此篇文章來做個測試,了解實際運作情況,這篇文章的範例專案程式碼,可以從 GitHub 取得。
首先,先來宣告四個介面,分別是 IMessageSingleton1 / IMessageSingleton2 / IMessageSingleton1 / IMessageSingleton2 ,並且讓類別 MessageClass 要實作這四個介面,使用這樣的語法 public class MessageClass : IMessageScope1, IMessageScope2, IMessageSingleton1, IMessageSingleton2 ;在這個服務類別中將會使用建構函式取得當前執行個體的 HashCode 數值,並且記錄在該物件內,這樣,才能夠透過物件中的 HashCode 來分辨出當時注入的服務物件是否為相同一個。
在此,請先建立一個 ASP.NET Core 的 Web API 專案,接著在 Startup.cs 檔案內,使用底下程式碼來宣告這四個介面與類別。
C Sharp / C#
public interface IMessage
{
    string Write(string message);
}
public interface IMessageScope1 : IMessage
{
}
public interface IMessageScope2 : IMessage
{
}
public interface IMessageSingleton1 : IMessage
{
}
public interface IMessageSingleton2 : IMessage
{
}
public class MessageClass : IMessageScope1, IMessageScope2, IMessageSingleton1, IMessageSingleton2
{
    int HashCode;
    public MessageClass()
    {
        HashCode = this.GetHashCode();
        Console.WriteLine($"ConsoleMessage ({HashCode}) 已經被建立了");
    }
    public string Write(string message)
    {
        string result = $"[Console 輸出  ({HashCode})] {message}";
        Console.WriteLine(result);
        return result;
    }
}
接著,在同一個檔案內, Startup.cs ,找到 Startup 類別內的 ConfigureServices 方法,在這裡分別建立四個服務物件的對應,這四個介面都分別對應到同一個類別型別,實際完成的程式碼,將會如下所示:
C Sharp / C#
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.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IMessageScope1, MessageClass>();
        services.AddScoped<IMessageScope2, MessageClass>();
        services.AddSingleton<IMessageSingleton1, MessageClass>();
        services.AddSingleton<IMessageSingleton2, MessageClass>();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // 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.UseMvc();
    }
}
在 Controllers 目錄下,打開 ValuesController.cs 檔案;首先建立建構函式 ,在這個建構函式內分別注入四個服務物件,但是,都是使用不同的介面型別,在這裡可以透過介面的型別名稱看的出這些要透過相依性注入容器注入到建構函式內的存留期特性。最後在 Get 動作方法內,江浙四個服務物件的 HashCode 顯示出來,底下是執行後的結果。從執行結果可以看的出來,就算當時註冊到相依性注入容器內,不同的介面對應到同一個類別,使用的是 單一 Singleton 存留期,但是因為在相依性住物容器會依據當時抽象型別來當作 Key 鍵值,作為接下來要注入的服務物件,要使用甚麼樣的存留期來注入,因此,從執行結果內,將會看到不論是使用 單一 或者 具範圍 的方式,都會注入不同的服務物件。
[
"value1",
"value2",
"[Console 輸出 (9926279)] messageScope1",
"[Console 輸出 (30350669)] messageScope2",
"[Console 輸出 (23544769)] messageSingleton1",
"[Console 輸出 (4036177)] messageSingleton2"
]
C Sharp / C#
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IMessageScope1 messageScope1;
    private readonly IMessageScope2 messageScope2;
    private readonly IMessageSingleton1 messageSingleton1;
    private readonly IMessageSingleton2 messageSingleton2;

    public ValuesController(IMessageScope1 messageScope1, IMessageScope2 messageScope2,
        IMessageSingleton1 messageSingleton1, IMessageSingleton2 messageSingleton2)
    {
        this.messageScope1 = messageScope1;
        this.messageScope2 = messageScope2;
        this.messageSingleton1 = messageSingleton1;
        this.messageSingleton2 = messageSingleton2;
    }
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "value1", "value2",
            messageScope1.Write("messageScope1"), messageScope2.Write("messageScope2"),
            messageSingleton1.Write("messageSingleton1"), messageSingleton2.Write("messageSingleton2")
        };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }

    // POST api/values
    [HttpPost]
    public void Post([FromBody] string value)
    {
    }

    // PUT api/values/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody] string value)
    {
    }

    // DELETE api/values/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {
    }
}
現在,找到 ValuesController 類別內建構函式內,將原先的四個要注入的參數,重複一份,也就是要來注入八個參數,來看看會產生甚麼樣的執行結果。從底下的執行結果可以看的出來,剛剛複製出來的四個新的四個參數,所注入的服務物件,都會與前四個參數所注入的服務物件相同的。
[
"value1",
"value2",
"[Console 輸出 (6322590)] messageScope1",
"[Console 輸出 (51417822)] messageScope2",
"[Console 輸出 (25216348)] messageSingleton1",
"[Console 輸出 (13077851)] messageSingleton2",
"[Console 輸出 (6322590)] messageScope01",
"[Console 輸出 (51417822)] messageScope02",
"[Console 輸出 (25216348)] messageSingleton01",
"[Console 輸出 (13077851)] messageSingleton02"
]
C Sharp / C#
private readonly IMessageScope1 messageScope1;
private readonly IMessageScope2 messageScope2;
private readonly IMessageSingleton1 messageSingleton1;
private readonly IMessageSingleton2 messageSingleton2;
private readonly IMessageScope1 messageScope01;
private readonly IMessageScope2 messageScope02;
private readonly IMessageSingleton1 messageSingleton01;
private readonly IMessageSingleton2 messageSingleton02;

public ValuesController(IMessageScope1 messageScope1, IMessageScope2 messageScope2,
    IMessageSingleton1 messageSingleton1, IMessageSingleton2 messageSingleton2,
    IMessageScope1 messageScope01, IMessageScope2 messageScope02,
    IMessageSingleton1 messageSingleton01, IMessageSingleton2 messageSingleton02)
{
    this.messageScope1 = messageScope1;
    this.messageScope2 = messageScope2;
    this.messageSingleton1 = messageSingleton1;
    this.messageSingleton2 = messageSingleton2;
    this.messageScope01 = messageScope01;
    this.messageScope02 = messageScope02;
    this.messageSingleton01 = messageSingleton01;
    this.messageSingleton02 = messageSingleton02;
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
    return new string[] { "value1", "value2",
        messageScope1.Write("messageScope1"), messageScope2.Write("messageScope2"),
        messageSingleton1.Write("messageSingleton1"), messageSingleton2.Write("messageSingleton2"),
        messageScope01.Write("messageScope01"), messageScope02.Write("messageScope02"),
        messageSingleton01.Write("messageSingleton01"), messageSingleton02.Write("messageSingleton02")
    };
}



2019年6月14日 星期五

ASP.NET Core 下使用 Unity DI 容器做到屬性注入功能

ASP.NET Core 下使用 Unity DI 容器 Container 做到屬性注入功能

當在進行 ASP.NET Core 專案開發的時候,就會預設有提供一個 DI 容器,也就是 Microsoft.Extensions.DependencyInjection 這個套件,不過,這個套件說實在的功能上有些陽春,不過對於基本的相依性注入上的需求,已經相當的充分足夠了;若想要使用使用這個套件做到 屬性注入 Property Injection 這樣的功能(雖然屬性注入的功能並不建議使用這樣的功能),在 Microsoft.Extensions.DependencyInjection 套件下,是不提供這樣的機制;若想要使用更多的 DI 容器功能,這個時候要設定在 ASP.NET Core 專案內可以使用其他的 DI 容器,接著要設定當要產生控制器物件的時候,使用 Unity 相依性注入容器來做為產生與注入該控制器物件的來源。
這篇文章的專案範例原始碼可以從 GitHub 取得
請先建立一個 ASP.NET Core Web API 的專案
接著,請在這個專案內來安裝 Unity.Microsoft.DependencyInjection 這個 Unity DI 容器套件,以便可以在 ASP.NET Core 專案內使用 Unity 相依性注入的容器功能
現在,可以打開 Program.cs 這個檔案,在這個 public static IWebHostBuilder CreateWebHostBuilder(string[] args) 方法內,加入這個 .UseUnityServiceProvider() 方法呼叫 ,對 ASP.NET Core 專案來註冊 Unity DI Container 這個類別庫軟體。
C Sharp / C#
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseUnityServiceProvider()
            .UseStartup<Startup>();
}
現在打開 [Startup.cs] 這個檔案,在這個檔案內增加一個 IMessage 介面,與兩個實作 IMessage 這個介面的類別,分別是 ConsoleMessage, FileMessage。
C Sharp / C#
public interface IMessage
{
    string Send(string message);
}

public class ConsoleMessage : IMessage
{
    public string Send(string message)
    {
        string result = $"ConsoleMessage :{message}";
        Console.WriteLine(result);
        return result;
    }
}
public class FileMessage : IMessage
{
    public string Send(string message)
    {
        string result = $"FileMessage :{message}";
        Console.WriteLine(result);
        return result;
    }
}
請在 Startup 類別內,加入這個方法 ConfigureContainer ,這裡將會進行整個 DI 容器的抽象型別與具體實作類別的關係註冊,其方法將會傳入一個 IUnityContainer 型別參數,因此,在這裡將會這個參數 container 物件,使用 Unity DI Container 的語法,進行這些型別關係的註冊。
C Sharp / C#
public void ConfigureContainer(IUnityContainer container)
{
    // Could be used to register more types
    container.RegisterType<IMessage, ConsoleMessage>();
}
接著,打開 Controllers 資料夾下的 ValuesController.cs 這個檔案,在這個類別 ValuesController ,將會建立一個建構函式,這個函式將會接收一個 IMessage 參數,這個參數將會於這個控制器被建立的時候,將會注入到這個建構函式內。
另外,將會宣告一個 IMessage 型別的屬性 messageProperty,不過,在這裡將會想要使用 屬性注入的功能,當這個類別被產生的時候,將會透過 DI 容器來注入到這個屬性內;這裡將會使用 [Dependency] 標示在這個屬性上,這是 Unity DI 容器的使用宣告方式。
在 Get 動作內,請在 return 敘述上設定一個中斷點,現在開始執行這個專案,此時,程式將會停留在這個中斷點上;請將游標移動到 this.message 這個欄位上,就會看到如下圖的畫面,透過建構函式的注入方式,取得到一個 IMessage 的服務物件,該服務物件為 ConsoleMessage 的型別。
現在,請將游標移動到 meesageProperty 這個屬性上,雖然這個屬性有標示 Dependency 這個 Attribute,不過,且看到 DI 容器卻沒有使用 Unity 的屬性注入用法,使用屬性注入的方式將依個服務物件注入到該屬性上。
C Sharp / C#
public class ValuesController : ControllerBase
{
    private readonly IMessage message;
    [Dependency]
    public IMessage messageProperty { get; set; }

    public ValuesController(IMessage message)
    {
        this.message = message;
    }
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "value1", "value2", message.Send("Vulcan") };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }

    // POST api/values
    [HttpPost]
    public void Post([FromBody] string value)
    {
    }

    // PUT api/values/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody] string value)
    {
    }

    // DELETE api/values/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {
    }
}
現在,請打開 Startup.cs 檔案,在 Startup 類別內找到 ConfigureServices 方法,在 AddMvc() 方法內加入這個方法呼叫 .AddControllersAsServices(),因為預設 ASP 解析控制器將會使用內建的啟用器,所以使用這個方法呼叫,將會當要進行控制器型別解析的時候,可以使用 Unity 容器作為相依性注入的來源。
接著,請再度執行這個專案,此時將會停留在之前設定的中斷點,現在,將游標移動到 messageProperty 上,現在就會看到如下圖,這個屬性已經自動注入了服務物件。
C Sharp / C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .AddControllersAsServices();
}








2019年6月13日 星期四

.NET Core 的相依性注入的三種生命週期管理與使用 CreateScope 建立兩個 Scope 的用法差異

.NET Core 的相依性注入的三種生命週期管理與使用 CreateScope 建立兩個 Scope 的用法差異

在 .NET Core 平台下,可以透過 Microsoft.Extensions.DependencyInjection 套件,提供 DI Container 容器的服務,這裡需要先建立一個 ServiceCollection 物件,透過此物件進行各個型別對應的註冊需求,接著使用 ServiceCollection.BuildServiceProvider() 產生出一個 IServiceProvider 物件,如此,便可以透過該物件進行所需要服務物件的產生動作,這裡會透過建構函式注入的方式,將相依抽象介面或者具體類別相依的型別,產生出該服務的物件。
在 .NET Core 平台下,期 DI 容器共提供三種物件存留期 Service Lifetime ,分別是: 暫時性 Transient 、 具範圍 Scoped 、 單一 Singleton 這三種存留期模式,根據微軟官方文件上的說明,這三種存留期的意義為:
  • 暫時性 Transient
    每次從服務容器要求暫時性存留期服務時都會建立它們。 此存留期最適合用於輕量型的無狀態服務。
  • 具範圍 Scoped
    具範圍存留期服務會在每次用戶端要求 (連線)request (connection) 時建立一次。
  • 單一 Singleton
    當第一次收到有關單一資料庫存留期服務的要求時 (或是當執行 ConfigureServices 而且隨著服務註冊指定執行個體時),即會建立單一資料庫存留期服務。 每個後續要求都會使用相同的執行個體。
在這篇文章將會來探討這三種物件存留期的差異性,這篇文章的範例專案程式碼可以從 GitHub 取得
首先,先建立需要用到的介面 IMessage 與類別 ConsoleMessage,該介面內有宣告一個 Write 方法,而在 ConsoleMessage 類別內,將會在建構函式內將會取得該執行個體/物件的 Hash Code 值(等下會使用這個值來區分是否為同一個物件),接著在螢幕上顯示一段訊息,說明這個 ConsoleMessage 物件已經被建立起來了。
另外,也會建立一個解構式 ~ConsoleMessage() 方法,在此方法內將會顯示這個執行個體 Instance / 物件 Object 已經被 .NET GC Garbage Collection 資源回收了。
C Sharp / C#
public interface IMessage
{
    string Write(string message);
}
public class ConsoleMessage : IMessage
{
    int HashCode;
    public ConsoleMessage()
    {
        HashCode = this.GetHashCode();
        Console.WriteLine($"ConsoleMessage ({HashCode}) 已經被建立了");
    }
    public string Write(string message)
    {
        string result = $"[Console 輸出  ({HashCode})] {message}";
        Console.WriteLine(result);
        return result;
    }
    ~ConsoleMessage()
    {
        Console.WriteLine($"ConsoleMessage ({HashCode}) 已經被釋放了");
    }
}
現在,先來測試 暫時性 Transient 這樣的存留期的特性,在底下的測試程式碼,將會使用 serviceCollection.AddTransient<IMessage, ConsoleMessage>(); 敘述將 IMessage 與 ConsoleMessage 這兩個相依型別註冊到 DI 容器內,接著使用 serviceProvider1 = serviceCollection.BuildServiceProvider(); 敘述取得 IServiceProvider 物件。
接下來將會透過 message1 = serviceProvider1.GetService<IMessage>() 與 message2 = serviceProvider1.GetService<IMessage>() 這兩個敘述,分別請求 DI 容器來注入一個 ConsoleMessage 物件,此時,透過測試程式執行結果,將會知道 DI 容器將會產生兩個 ConsoleMessage 物件
在這篇文章中的測試方式,請在Release 建置模式來建立這個測試專案,接著切換到 CoreDILifetimeScope\CoreDILifetimeScope\bin\Release\netcoreapp2.2 目錄下,開啟 命令提示字元視窗,確認命令提示字元視窗是在這個目錄下,請執行這個測試專案,使用這個命令 dotnet CoreDILifetimeScope.dll,底下是執行結果內容。
在這兩個服務物件建立成功之後,將會執行該執行個體的 Write 方法,接著,把這兩個物件的變數設定為 null,並且使用 GC.Collect(2) 強制啟動 .NET 記憶體回收機制,若某個物件的記憶體被回收的話,將會看到類似這樣的訊息 ConsoleMessage (32854180) 已經被釋放了 這表示 message1 這個參考物件已經被系統回收記憶體空間了,不過,message2卻還沒有被記憶體回收,這是因為 message2 這個物件在呼叫記憶體回收方法之後,還會有用到,所以,該 message2 物件事沒有被回收的。
所以,當使用 暫時性 Transient 的物件存留期方式註冊到 DI 容器內,每一次請求 DI 容器解析出抽象型別的時候,都會建立一個新的對應具體類別的物件。
ConsoleMessage (32854180) 已經被建立了
[Console 輸出  (32854180)] M1 - Vulcan
[Console 輸出  (32854180)] M2 - Lee
[Console 輸出  (32854180)] M9 - Vulcan Lee
ConsoleMessage (43942917) 已經被建立了
[Console 輸出  (43942917)] M1_1 - Ada
[Console 輸出  (43942917)] M2_1 - Chan
ConsoleMessage (32854180) 已經被釋放了
[Console 輸出  (43942917)] M9_1 - Ada Chan
C Sharp / C#
IMessage message1;
IMessage message2;
IMessage message1_1;
IMessage message2_1;
IMessage message9_1;
IMessage message9;
IServiceProvider serviceProvider1;
IServiceProvider serviceProvider2;
IServiceProvider serviceProvider3;
IServiceCollection serviceCollection;
IServiceScope serviceScope2;
IServiceScope serviceScope3;

serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IMessage, ConsoleMessage>();
//serviceCollection.AddScoped<IMessage, ConsoleMessage>();
//serviceCollection.AddSingleton<IMessage, ConsoleMessage>();
serviceProvider1 = serviceCollection.BuildServiceProvider();

#region 使用預設 Scope
message1 = serviceProvider1.GetService<IMessage>();
message1.Write("M1 - Vulcan");
message2 = serviceProvider1.GetService<IMessage>();
message2.Write("M2 - Lee");
message1 = null;
message2 = null;
GC.Collect(2);
Thread.Sleep(1000);
message9 = serviceProvider1.GetService<IMessage>();
message9.Write("M9 - Vulcan Lee");
#endregion
延續剛剛的測試程式碼,請將 serviceCollection.AddTransient<IMessage, ConsoleMessage>(); 敘述註解起來,並且把這個 serviceCollection.AddSingleton<IMessage, ConsoleMessage>(); 敘述解除註解,重新建置、在命令提示字元視窗下重新執行一次,將會看到底下的內容。
在 單一 Singleton 存留期的註冊模式下,當要解析單一存留期的服務物件的時候,這個服務物件將不會被記憶體回收,直到這個 DI 容器因為應用程式結束之後,才會被回收。因此,輸出結果只會看到一次 ConsoleMessage (32854180) 已經被建立了 這樣的敘述,而每次呼叫 Write 敘述的時候,將會看到使用的是同一個物件。
ConsoleMessage (32854180) 已經被建立了
[Console 輸出  (32854180)] M1 - Vulcan
[Console 輸出  (32854180)] M2 - Lee
[Console 輸出  (32854180)] M9 - Vulcan Lee
延續剛剛的測試程式碼,請將 serviceCollection.AddSingleton<IMessage, ConsoleMessage>(); 敘述註解起來,並且把這個 serviceCollection.AddScoped<IMessage, ConsoleMessage>(); 敘述解除註解,重新建置、在命令提示字元視窗下重新執行一次,將會看到底下的內容。
在 具範圍 Scoped 存留期的註冊模式下,因為接下來進行服務物件解析的時候,所使用的 範圍 Scope 為這個 DI 容器的預設範圍,因此,當要解析 具範圍 Scoped 存留期的服務物件的時候,一旦配置這樣的服務物件之後,這個服務物件將不會被記憶體回收,直到這個 範圍 (也就是預設範圍) 物件不再使用之後,也就是 DI 容器因為應用程式結束之後,才會被回收。因此,輸出結果只會看到一次 ConsoleMessage (32854180) 已經被建立了 這樣的敘述,而每次呼叫 Write 敘述的時候,將會看到使用的是同一個物件。
ConsoleMessage (32854180) 已經被建立了
[Console 輸出  (32854180)] M1 - Vulcan
[Console 輸出  (32854180)] M2 - Lee
[Console 輸出  (32854180)] M9 - Vulcan Lee
在剛剛的練習中,具範圍 Scoped 存留期 的模式與 單一 Singleton 存留期 模式似乎運作方式都相同的,不過,其實這兩種存留期模式是不相同的;現在來測試多 具範圍 Scoped 存留期 的模式。
在 ASP.NET Core 開發框架下,每一個 HTTP 請求 Request 連線 Connection 連線請求的時候,都會自動產生 Scope,因此,在同一個 HTTP 請求連線中,若使用 具範圍 Scoped 存留期 註冊到 DI 容器下的時候,在相同的 HTTP 請求連線中所獲得的服務物件都會是同一個,而在不同的 HTTP 請求連線下,就會產生出另外一個新的服務物件。在此,就會模擬兩個 HTTP 請求連線自動建立起兩個範圍 Scope,這裡會使用多範圍物件的進行練習。
現在把這個練習專案的原始碼修改成為如下,同樣的,還是使用 serviceCollection.AddScoped<IMessage, ConsoleMessage>() 進行具範圍的存留期註冊,現在當使用 serviceProvider1 = serviceCollection.BuildServiceProvider(); 敘述取得 IServiceProvide 物件之後,就可以呼叫 serviceScope2 = serviceProvider1.CreateScope(); 方法,產生一個 IServiceScope 物件,接著透過這個 IServiceScope 物件再來產生一個 IServiceProvider 物件,這裡使用這個敘述: serviceProvider2 = serviceScope2.ServiceProvider;,現在在這個 serviceProvider2 進行注入的同一個介面型別的服務物件,都會是同一個。
然而,若使用 serviceScope3 = serviceProvider1.CreateScope(); 敘述來產生另外一個 IServiceScope 物件,緊接著使用 serviceProvider3 = serviceScope3.ServiceProvider; 物件來產生另外一個 IServiceProvider 物件,在這裡要注入多個具範圍的服務物件,都是同一個相同服務物件。
在這裡, message1, message2 都是使用 serviceProvider2 物件來注入 IMessage 介面的服務物件,從執行結果看到都是同一個物件;而 message1_1, message2_1 卻是使用 serviceProvider3 物件來注入 IMessage 介面的服務物件,從執行結果看到都是同一個物件;而透過 serviceProvider2 與 serviceProvider3 所產生的 IMessage 服務物件卻是不相同的。
ConsoleMessage (32854180) 已經被建立了
[Console 輸出  (32854180)] M1 - Vulcan
[Console 輸出  (32854180)] M2 - Lee
[Console 輸出  (32854180)] M9 - Vulcan Lee
ConsoleMessage (43942917) 已經被建立了
[Console 輸出  (43942917)] M1_1 - Ada
[Console 輸出  (43942917)] M2_1 - Chan
ConsoleMessage (32854180) 已經被釋放了
[Console 輸出  (43942917)] M9_1 - Ada Chan
現在,若把最後一行程式碼 message9.Write("M9 - Vulcan Lee"); 解除註解,並且建置、再度執行一次,將會看到底下的結果。這次的輸出結果與上面步驟的執行結果卻是不相同,差別在於這裡沒有任何物件被釋放掉。
ConsoleMessage (32854180) 已經被建立了
[Console 輸出  (32854180)] M1 - Vulcan
[Console 輸出  (32854180)] M2 - Lee
[Console 輸出  (32854180)] M9 - Vulcan Lee
ConsoleMessage (43942917) 已經被建立了
[Console 輸出  (43942917)] M1_1 - Ada
[Console 輸出  (43942917)] M2_1 - Chan
[Console 輸出  (43942917)] M9_1 - Ada Chan
[Console 輸出  (32854180)] M9 - Vulcan Lee
C Sharp / C#
IMessage message1;
IMessage message2;
IMessage message1_1;
IMessage message2_1;
IMessage message9_1;
IMessage message9;
IServiceProvider serviceProvider1;
IServiceProvider serviceProvider2;
IServiceProvider serviceProvider3;
IServiceCollection serviceCollection;
IServiceScope serviceScope2;
IServiceScope serviceScope3;

serviceCollection = new ServiceCollection();
//serviceCollection.AddTransient<IMessage, ConsoleMessage>();
serviceCollection.AddScoped<IMessage, ConsoleMessage>();
//serviceCollection.AddSingleton<IMessage, ConsoleMessage>();
serviceProvider1 = serviceCollection.BuildServiceProvider();

#region 使用兩個 Scope
serviceScope2 = serviceProvider1.CreateScope();
serviceProvider2 = serviceScope2.ServiceProvider;
message1 = serviceProvider2.GetService<IMessage>();
message1.Write("M1 - Vulcan");
message2 = serviceProvider2.GetService<IMessage>();
message2.Write("M2 - Lee");
message1 = null;
message2 = null;
GC.Collect(2);
Thread.Sleep(1000);
message9 = serviceProvider2.GetService<IMessage>();
message9.Write("M9 - Vulcan Lee");

serviceScope3 = serviceProvider1.CreateScope();
serviceProvider3 = serviceScope3.ServiceProvider;
message1_1 = serviceProvider3.GetService<IMessage>();
message1_1.Write("M1_1 - Ada");
message2_1 = serviceProvider3.GetService<IMessage>();
message2_1.Write("M2_1 - Chan");
message1_1 = null;
message2_1 = null;
GC.Collect(2);
Thread.Sleep(1000);
message9_1 = serviceProvider3.GetService<IMessage>();
message9_1.Write("M9_1 - Ada Chan");
// 若將底下的程式碼註解起來(在 AddScoped 模式),則 
// message1, message2 指向到 ConsoleMessage 會被釋放掉
//message9.Write("M9 - Vulcan Lee");
#endregion