ASP.NET Core MVC的“模組化”設計使我們可以構成應用的基本單元Controller定義在任意的模組(程式集)中,並在執行時動態載入和解除安裝。《設計篇》介紹了這種為“飛行中的飛機加油”的方案的實現原理?本篇我們將演示將介紹“分散定義Controller”的N種實現方案。原始碼從這裡下載。
一、標註ApplicationPartAttribute特性
二、標註RelatedAssemblyAttribute特性
三、註冊ApplicationPartManager
四、新增ApplicationPart到現有ApplicationPartManager
一、標註ApplicationPartAttribute特性
接下來我們就透過幾個簡單的例項來演示如何將Controller型別定義在非入口應用所在的專案中。我們建立如圖1所示的解決方案,其中App是一個MVC應用型別的專案,而Foo則是一個普通的類庫專案,App具有針對Foo的專案引用。我們希望將部分Controller型別定義在Foo這個類庫專案中。
圖1 將部分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型別?