5

深入解析ASP.NET Core MVC的模块化设计[下篇]

 6 months ago
source link: https://www.cnblogs.com/artech/p/18052365
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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类型在默认情况下是不会被解析的。

图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类型。

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类型?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK