在上一節,透過描述我們看到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所在的組件資訊。下一節會再詳細說明。