在上一節,透過描述我們看到controller class在被產生instance過程中,其中IActionInvokerFactory 會被Resolve成ActionInvokerFactory實體。這時候建構子內的參數物件也會繼續透過DI機制被Resolve成實體。
而在”UseEndpoint to Map Controller”這節中,實際上ApplicationPartManager此instance的產出是因為它的身分是ControllerActionDescriptorProvider的建構子參數物件。在被Resolve 出來時,ApplicationPartManager 的ApplicationParts 集合已經包含 Controller Class 所屬的組件資訊。
回顧下圖在”UseEndpoint to Map Controller”這節中的圖:
但是令人好奇的是,建構子內並沒有去新增ApplicationPartManager 的ApplicationParts 集合內容的程式碼,但為何剛開始被建構出來的這個物件此集合內已經有值了?
原因是在這個時間點之前就已經被Resolve 過了,Resolve 的形式是 Singleton;且透過ApplicationPartManager.PopulateDefaultParts將組件資訊包裝到ApplicationPartManager.ApplicationParts 集合,供後續再進一步取出Controller與Action 資訊。在確切的時間點會在不久之後做描述。
我們可以在Dot Net Core架構中做一個簡單的Resolve Singleton 物件,證明其記憶體位置與內容是可以被保存到下一次被 Resolve 出來(仍為Singleton的實體)。
以下步驟:
Step1: 建立簡單的類別TestClass ,如下:
public class TestClass
{
public List<string> ListStringCollection;
public TestClass()
{
if (ListStringCollection is null)
ListStringCollection = new List<string>();
}
public void AddString(string strText)
{
ListStringCollection?.Add(strText);
}
}
Step2: 於Startup 類別新增一個static欄位IApplicationBuilder AppBuilder,於ConfigureServices函式中註冊此類別;於Configure函式中 Resolve TestClass,並於TestClass. ListStringCollection字串集合加入一個日期字串。然後把IapplicationBuilder的實體指派到
Startup. AppBuilder。 如下:
public class Startup
{
public static IApplicationBuilder AppBuilder;
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.AddControllers();
services.AddSingleton<TestClass>();
}
// 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();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//Demo Singleton
TestClass ts = (TestClass)app.ApplicationServices.GetService(typeof(TestClass));
ts.ListStringCollection.Add(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
System.Diagnostics.Debug.Write( $" The class memory location is {AddressHelper.GetAddress(ts)}" );
System.Diagnostics.Debug.Write($"The first element in collection is : {ts.ListStringCollection[0]} ");
//cache IApplicationBuilder instance
AppBuilder = app;
}
}
Step3: 執行專案,並觀察Startup. Configure中,利用AppBuilder的serviceProviderEngineScope來做IOC的Resolve Service,所被Resolve 出來的 TestClass 記憶體位置與其成員內容。
Step4: Request to Controller Action (Default Controller “WeatherForecastController”)
Step5: 觀察執行到Controller的Action 時,再次Resolve出的TestClass 是否記憶體位置與ListStringCollection字串集合所存的字串內容是否與之前一致。
如上圖,最後Resolve 出 TestClass時,都未再對其成員內容做任何變動,建構子也是如第一張圖一樣沒有更動成員的內容;而記憶體位置與ListStringCollection字串集合所存的字串內容都與在 Startup. Configure中所指定的時間字串是一致的。
在Startup. ConfigureServices中,如果是register service 前就先產生實體且設定內容,也可以有一樣的結果。方式如下:
public class Startup
{
public static IApplicationBuilder AppBuilder;
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.AddControllers();
// original
//services.AddSingleton<TestClass>();
// new instance then register service
TestClass ts = new TestClass();
ts.ListStringCollection.Add(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
System.Diagnostics.Debug.Write($" The class memory location is {AddressHelper.GetAddress(ts)} \n ");
System.Diagnostics.Debug.Write($"The first element in collection is : {ts.ListStringCollection[0]} ");
services.AddSingleton(ts);
}
// 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();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//Demo Singleton
TestClass ts = (TestClass)app.ApplicationServices.GetService(typeof(TestClass));
System.Diagnostics.Debug.Write( $" The class memory location is {AddressHelper.GetAddress(ts)} \n " );
System.Diagnostics.Debug.Write($"The first element in collection is : {ts.ListStringCollection[0]} ");
//cache IApplicationBuilder instance
AppBuilder = app;
}
}
所以透過上述證明,Singleton的實體一旦被Resolve出來,記憶體位置都會保留起來。同理可以說明,當處理Resolve Controller Class時 ,ApplicationPartManager 這個Class,在執行endpoint middleware階段被Resolve出來時已經包含Controller所在的組件資訊。不久之後會再詳細說明。