ASP.NET Core MVC的“模塊化”設計使我們可以構成應用的基本單元Controller定義在任意的模塊(程序集)中,並在運行時動態加載和卸載。《設計篇》介紹了這種爲“飛行中的飛機加油”的方案的實現原理?本篇我們將演示將介紹“分散定義Controller”的N種實現方案。源代碼從這裏下載。
一、標註ApplicationPartAttribute特性
二、標註RelatedAssemblyAttribute特性
三、註冊ApplicationPartManager
四、添加ApplicationPart到現有ApplicationPartManager
一、標註ApplicationPartAttribute特性
接下來我們就通過幾個簡單的實例來演示如何將Controller類型定義在非入口應用所在的項目中。我們創建如圖1所示的解決方案,其中App是一個MVC應用類型的項目,而Foo則是一個普通的類庫項目,App具有針對Foo的項目引用。我們希望將部分Controller類型定義在Foo這個類庫項目中。
我們在App項目中定義瞭如下這個HomeController。如代碼片段所示,我們在構造函數中注入了ApplicationPartManager對象,並利用它得到當前應用範圍內所有有效Controller類型。在執行應用根路徑的Action方法Index中,我們將得到的有效Controller類型名稱呈現出來。如下所示的FooController類型是我們在Foo項目中定義的Controller類型。
public class HomeController : Controller { private readonly IEnumerable<Type> _controllerTypes; public HomeController(ApplicationPartManager manager) { var feature = new ControllerFeature(); manager.PopulateFeature(feature); _controllerTypes = feature.Controllers; } [HttpGet("/")] public string Index() { var lines = _controllerTypes.Select(it => it.Name); return string.Join(Environment.NewLine, lines.ToArray()); } }
public class FooController { public void Index() => throw new NotImplementedException(); }
在啓動這個演示程序後,如果利用瀏覽器通過根路徑訪問定義在HomeController類型中的Action方法Index,我們會得到如圖2所示的輸出結果。從輸出結果可以看出,定義在非MVC應用項目Foo中的Controller類型在默認情況下是不會被解析的。
圖2 默認只解析MVC應用所在項目定義的Controller
如果希望MVC應用在進行Controller類型解析的時候將項目Foo編譯後的程序集(默認爲Foo.dll)包括進來,我們可以在應用所在項目中標註ApplicationPartAttribute特性將程序集Foo作爲應用的組成部分。所以我們在Program.cs中針對ApplicationPartAttribute特性進行了如下的標記。
[assembly:ApplicationPart("Foo")]
修改後的程序集啓動之後,再次利用瀏覽器按照按照相同的路徑對它發起請求,我們將得到如圖3所示的輸出結果。由於程序集Foo成爲了當前應用的有效組成部分,定義在它裏面的BarController自然也成爲了當前應用有效的Controller類型。
圖3 解析ApplicationPartAttribute特性指向程序集中的Controller類型
二、標註RelatedAssemblyAttribute特性
除了在入口程序集上標註ApplicationPartAttribute特性將某個程序集作爲當前應用的有效組成部分之外,我們也可以通過標註RelatedAssemblyAttribute達到相同的目的。根據前面的介紹,我們知道RelatedAssemblyAttribute特性只能標註到入口程序集或者ApplicationPartAttribute特性指向的程序集中,所以我們可以將RelatedAssemblyAttribute特性標註到Foo項目中將另一個程序集包含進行。爲此我們在解決方案中添加了另一個類庫項目Bar(如圖4所示),併爲App添加針對Bar的項目引用,然後在Bar項目中定義一個類似於FooController的BarController類型。
圖4 將部分Controller類型定義在Foo和Bar項目中
爲了將項目Bar編譯後生成的程序集(默認爲Bar.dll)作爲當前應用的組成部分,我們可以選擇在App或者Foo項目中標註一個指向它的RelatedAssemblyAttribute特性。對於我們演示的實例來說,我們選擇在FooController.cs中以如下形式標註一個指向程序集Bar的RelatedAssemblyAttribute特性。
[assembly: RelatedAssembly("Bar")]
修改後的程序集啓動之後,再次利用瀏覽器按照按照相同的路徑對它發起請求,我們將得到如圖5所示的輸出結果。由於程序集Bar成爲了當前應用的有效組成部分,定義在它裏面的BazController自然也成爲了當前應用有效的Controller類型。
圖5 解析RelatedAssemblyAttribute特性指向程序集中的Controller類型
三、註冊ApplicationPartManager
由於針對有效Controller類型的解析是利用註冊的ApplicationPartManager對象實現的,所以我們完全可以通過註冊一個ApplicationPartManager對象的方式達到相同的目的。接下來我們將上一個演示實例中標註的ApplicationPartAttribute和RelatedAssemblyAttribute特性刪除,並將承載程序修改爲如下的形式。
var manager = new ApplicationPartManager(); var entry = Assembly.GetEntryAssembly()!; var foo = Assembly.Load(new AssemblyName("Foo")); var bar = Assembly.Load(new AssemblyName("Bar")); manager.ApplicationParts.Add(new AssemblyPart(entry)); manager.ApplicationParts.Add(new AssemblyPart(foo)); manager.ApplicationParts.Add(new AssemblyPart(bar)); manager.FeatureProviders.Add(new ControllerFeatureProvider()); var builder = WebApplication.CreateBuilder(args); builder.Services .AddSingleton(manager) .AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run();
如上面的代碼片段所示,我們創建了一個ApplicationPartManager對象,並在其ApplicationParts屬性中顯式添加了指向入口程序集以及Foo和Bar程序集的AssemblyPart對象。爲了能夠讓這個ApplicationPartManager對象具有解析Controller類型的能力,我們在其FeatureProviders中添加了一個ControllerFeatureProvider對象。在後續的應用承載程序中,我們將這個ApplicationPartManager對象作爲服務註冊到依賴注入框架中。修改後的程序集啓動之後,再次利用瀏覽器按照按照相同的路徑對它發起請求,我們依然會得到如圖5所示的輸出結果。
四、添加ApplicationPart到現有ApplicationPartManager
其實我們沒有必要註冊一個新的,按照如下的方式將Foo、Bar程序集轉換成AssemblyPart並將其添加到現有的ApplicationPartManager之中也可以達到相同的目的。
var builder = WebApplication.CreateBuilder(args); builder.Services .AddControllers() .AddApplicationPart(Assembly.Load(new AssemblyName("Foo"))) .AddApplicationPart(Assembly.Load(new AssemblyName("Bar"))); var app = builder.Build(); app.MapControllers(); app.Run();
深入解析ASP.NET Core MVC的模塊化設計[上篇]
深入解析ASP.NET Core MVC的模塊化設計[下篇]
如何實現運行時動態定義Controller類型?