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 檔案內,使用底下程式碼來宣告這四個介面與類別。
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 方法,在這裡分別建立四個服務物件的對應,這四個介面都分別對應到同一個類別型別,實際完成的程式碼,將會如下所示:
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"
]
[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"
]
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")
};
}