深入解析ASP.NET Core MVC的模塊化設計[下篇]

ASP.NET Core MVC的“模塊化”設計使我們可以構成應用的基本單元Controller定義在任意的模塊(程序集)中,並在運行時動態加載和卸載。《設計篇》介紹了這種爲“飛行中的飛機加油”的方案的實現原理?本篇我們將演示將介紹“分散定義Controller”的N種實現方案。源代碼從這裏下載。

一、標註ApplicationPartAttribute特性
二、標註RelatedAssemblyAttribute特性
三、註冊ApplicationPartManager
四、添加ApplicationPart到現有ApplicationPartManager

一、標註ApplicationPartAttribute特性

接下來我們就通過幾個簡單的實例來演示如何將Controller類型定義在非入口應用所在的項目中。我們創建如圖1所示的解決方案,其中App是一個MVC應用類型的項目,而Foo則是一個普通的類庫項目,App具有針對Foo的項目引用。我們希望將部分Controller類型定義在Foo這個類庫項目中。

image_thumb[12]
圖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類型在默認情況下是不會被解析的。

image_thumb[14]
圖2 默認只解析MVC應用所在項目定義的Controller

如果希望MVC應用在進行Controller類型解析的時候將項目Foo編譯後的程序集(默認爲Foo.dll)包括進來,我們可以在應用所在項目中標註ApplicationPartAttribute特性將程序集Foo作爲應用的組成部分。所以我們在Program.cs中針對ApplicationPartAttribute特性進行了如下的標記。

[assembly:ApplicationPart("Foo")]

修改後的程序集啓動之後,再次利用瀏覽器按照按照相同的路徑對它發起請求,我們將得到如圖3所示的輸出結果。由於程序集Foo成爲了當前應用的有效組成部分,定義在它裏面的BarController自然也成爲了當前應用有效的Controller類型。

image_thumb[16]
圖3 解析ApplicationPartAttribute特性指向程序集中的Controller類型

二、標註RelatedAssemblyAttribute特性

除了在入口程序集上標註ApplicationPartAttribute特性將某個程序集作爲當前應用的有效組成部分之外,我們也可以通過標註RelatedAssemblyAttribute達到相同的目的。根據前面的介紹,我們知道RelatedAssemblyAttribute特性只能標註到入口程序集或者ApplicationPartAttribute特性指向的程序集中,所以我們可以將RelatedAssemblyAttribute特性標註到Foo項目中將另一個程序集包含進行。爲此我們在解決方案中添加了另一個類庫項目Bar(如圖4所示),併爲App添加針對Bar的項目引用,然後在Bar項目中定義一個類似於FooController的BarController類型。

image_thumb[19]
圖4 將部分Controller類型定義在Foo和Bar項目中

爲了將項目Bar編譯後生成的程序集(默認爲Bar.dll)作爲當前應用的組成部分,我們可以選擇在App或者Foo項目中標註一個指向它的RelatedAssemblyAttribute特性。對於我們演示的實例來說,我們選擇在FooController.cs中以如下形式標註一個指向程序集Bar的RelatedAssemblyAttribute特性。

[assembly: RelatedAssembly("Bar")]

修改後的程序集啓動之後,再次利用瀏覽器按照按照相同的路徑對它發起請求,我們將得到如圖5所示的輸出結果。由於程序集Bar成爲了當前應用的有效組成部分,定義在它裏面的BazController自然也成爲了當前應用有效的Controller類型。

image_thumb[21]
圖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類型?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章