ASP.NET Core 核心深度解析:Application Part 与 Feature Provider 完全指南
前言
在 ASP.NET Core 开发中,你是否思考过这些问题:
- 框架是如何发现项目中的控制器的?
- 为什么引用的类库中的控制器会自动生效?
- 如何实现插件化架构,动态加载外部程序集中的控制器?
答案就是 Application Part 和 Feature Provider。本文将深入剖析这两个核心概念,带你理解 ASP.NET Core 模块化架构的底层原理。
一、概念起源:为什么需要 Application Part?
1.1 传统问题
在传统的 ASP.NET MVC 5 中,控制器的发现机制相对固化:
- 只会扫描当前程序集
- 无法方便地共享跨项目的控制器
- 模块化、插件化架构实现困难
1.2 ASP.NET Core 的解决方案
ASP.NET Core 引入了 Application Part 抽象,将"功能发现"与"程序集"解耦:
┌─────────────────────────────────────────────────────────────┐
│ Application Part 设计哲学 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统方式:程序集 ──直接扫描──> 控制器 │
│ │
│ ASP.NET Core: │
│ 程序集 ──包装成──> Application Part ──Provider扫描──> 控制器 │
│ │
│ 优势:可以添加/移除 Part,可以自定义 Provider,灵活扩展 │
│ │
└─────────────────────────────────────────────────────────────┘
二、核心概念详解
2.1 Application Part(应用部件)
定义:Application Part 是对应用程序资源的抽象,它封装了 ASP.NET Core 发现各种功能所需的信息。
csharp
/// <summary>
/// 应用部件的抽象基类
/// </summary>
public abstract class ApplicationPart
{
/// <summary>
/// 部件名称
/// </summary>
public abstract string Name { get; }
}
它的定位:一个"零件容器",告诉框架"这里有可用的资源"。
2.2 AssemblyPart(程序集部件)
最常用的 Application Part 实现,封装一个 .NET 程序集。
csharp
/// <summary>
/// 基于程序集的应用程序部件
/// </summary>
public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider
{
public Assembly Assembly { get; }
/// <summary>
/// 获取程序集中定义的所有类型
/// </summary>
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
public override string Name => Assembly.GetName().Name;
public AssemblyPart(Assembly assembly)
{
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
}
}
其他 Application Part 实现
| 类型 | 用途 |
|---|---|
AssemblyPart |
封装普通程序集 |
CompiledRazorAssemblyPart |
封装编译后的 Razor 程序集 |
NativeCompiledFileProvider |
处理 Razor 编译源文件 |
2.3 Feature Provider(功能提供者)
定义:Feature Provider 是从 Application Part 中提取特定功能的策略。
csharp
/// <summary>
/// 标记接口,标识这是一个功能提供者
/// </summary>
public interface IApplicationFeatureProvider
{
}
/// <summary>
/// 泛型功能提供者接口
/// </summary>
/// <typeparam name="TFeature">要填充的功能类型</typeparam>
public interface IApplicationFeatureProvider<TFeature> : IApplicationFeatureProvider
{
/// <summary>
/// 从部件集合中填充功能
/// </summary>
void PopulateFeature(IEnumerable<ApplicationPart> parts, TFeature feature);
}
内置的 Feature Provider
IApplicationFeatureProvider
├── ControllerFeatureProvider → 发现控制器
├── ViewsFeatureProvider → 发现视图
├── TagHelperFeatureProvider → 发现 Tag Helper
├── RazorCompilationFeatureProvider→ 发现 Razor 编译源
└── MetadataReferenceFeatureProvider→ 提供编译引用
2.4 ApplicationPartManager(部件管理器)
定义:协调 Application Part 和 Feature Provider 的核心组件。
csharp
/// <summary>
/// 应用程序部件管理器
/// </summary>
public class ApplicationPartManager
{
/// <summary>
/// 应用部件集合
/// </summary>
public IList<ApplicationPart> ApplicationParts { get; } = new List<ApplicationPart>();
/// <summary>
/// 功能提供者集合
/// </summary>
public IList<IApplicationFeatureProvider> FeatureProviders { get; } = new List<IApplicationFeatureProvider>();
/// <summary>
/// 填充指定类型的功能
/// </summary>
public void PopulateFeature<TFeature>(TFeature feature)
{
// 获取所有能处理 TFeature 类型的 Provider
var providers = FeatureProviders.OfType<IApplicationFeatureProvider<TFeature>>();
foreach (var provider in providers)
{
provider.PopulateFeature(ApplicationParts, feature);
}
}
}
三、工作原理:完整的发现流程
3.1 核心流程图
- 注册阶段
根据发现的类型
生成路由表
注册到 DI 容器 - 发现阶段
遍历所有 ControllerFeatureProvider
遍历所有 ApplicationPart
判断类型是否满足控制器条件
添加到 ControllerFeature.Controllers - 构建阶段
应用启动
创建 ControllerFeature
调用 PopulateFeature - 配置阶段(可选)
ConfigureApplicationPartManager
添加/移除 ApplicationPart
添加/移除 FeatureProvider - 初始化阶段
调用 AddMvc/AddControllers
创建 ApplicationPartManager
添加入口程序集的 AssemblyPart
添加默认 FeatureProvider
3.2 详细代码流程
第一步:MVC 初始化时创建 PartManager
csharp
// 在 AddControllers() 内部(简化)
public static IMvcBuilder AddControllers(this IServiceCollection services)
{
// 创建 ApplicationPartManager
var partManager = new ApplicationPartManager();
// 添加默认的 AssemblyPart(当前入口程序集)
var entryAssembly = Assembly.GetEntryAssembly();
partManager.ApplicationParts.Add(new AssemblyPart(entryAssembly));
// 添加引用的相关程序集
var relatedAssemblies = GetRelatedAssemblies(entryAssembly);
foreach (var assembly in relatedAssemblies)
{
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
}
// 添加默认的 FeatureProvider
partManager.FeatureProviders.Add(new ControllerFeatureProvider());
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
partManager.FeatureProviders.Add(new TagHelperFeatureProvider());
// 注册到 DI 容器
services.TryAddSingleton(partManager);
return new MvcBuilder(services, partManager);
}
第二步:应用启动时发现控制器
csharp
// ControllerFeatureProvider 的实现
public class ControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var part in parts)
{
// 只处理 AssemblyPart
if (part is AssemblyPart assemblyPart)
{
foreach (var type in assemblyPart.Types)
{
// 判断是否为控制器
if (IsController(type))
{
feature.Controllers.Add(type);
}
}
}
}
}
private bool IsController(TypeInfo typeInfo)
{
// 控制器的判定条件:
// 1. 公开且非抽象
// 2. 不包含泛型参数
// 3. 是 ControllerBase 的子类或者标记了 [Controller] 特性
return typeInfo.IsPublic
&& !typeInfo.IsAbstract
&& !typeInfo.IsGenericTypeDefinition
&& (typeof(ControllerBase).IsAssignableFrom(typeInfo)
|| typeInfo.IsDefined(typeof(ControllerAttribute)));
}
}
四、实战场景
4.1 场景一:加载外部程序集中的控制器
csharp
// Module.dll 中的控制器(独立项目)
namespace MyModule.Controllers
{
[ApiController]
[Route("api/module")]
public class ModuleController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok("来自模块的响应");
}
}
// 主程序加载
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 加载外部程序集
var moduleAssembly = Assembly.LoadFrom("Plugins/MyModule.dll");
builder.Services.AddControllers()
.ConfigureApplicationPartManager(manager =>
{
// 添加外部程序集作为 Application Part
manager.ApplicationParts.Add(new AssemblyPart(moduleAssembly));
});
var app = builder.Build();
app.MapControllers();
app.Run();
}
}
4.2 场景二:排除特定程序集中的控制器
csharp
builder.Services.AddControllers()
.ConfigureApplicationPartManager(manager =>
{
var partsToRemove = manager.ApplicationParts
.Where(part => part.Name == "ObsoleteModule")
.ToList();
foreach (var part in partsToRemove)
{
manager.ApplicationParts.Remove(part);
}
});
4.3 场景三:自定义 Feature Provider
这是 Admin.Core 框架实现 Dynamic API 的核心技术:
csharp
/// <summary>
/// 自定义控制器发现提供者
/// 让实现 IDynamicApi 接口的类也被识别为控制器
/// </summary>
public class DynamicApiControllerFeatureProvider : ControllerFeatureProvider
{
private readonly Func<TypeInfo, bool> _selector;
public DynamicApiControllerFeatureProvider(Func<TypeInfo, bool> selector)
{
_selector = selector;
}
protected override bool IsController(TypeInfo typeInfo)
{
// 先调用基类判定(传统的 ControllerBase 继承)
if (base.IsController(typeInfo))
{
return true;
}
// 再应用自定义规则
return _selector(typeInfo);
}
}
// 使用自定义 Provider
services.AddControllers()
.ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(
new DynamicApiControllerFeatureProvider(type =>
type.IsPublic &&
!type.IsAbstract &&
type.ImplementedInterfaces.Contains(typeof(IDynamicApi))
)
);
});
4.4 场景四:插件化架构
csharp
/// <summary>
/// 插件加载器
/// </summary>
public class PluginLoader
{
public static void LoadPlugins(IMvcBuilder mvcBuilder, string pluginsPath)
{
if (!Directory.Exists(pluginsPath))
return;
var pluginFiles = Directory.GetFiles(pluginsPath, "*.Plugin.dll");
foreach (var file in pluginFiles)
{
var assembly = Assembly.LoadFrom(file);
var assemblyPart = new AssemblyPart(assembly);
mvcBuilder.ConfigureApplicationPartManager(manager =>
{
manager.ApplicationParts.Add(assemblyPart);
});
// 可选:加载配置文件
var config = new ConfigurationBuilder()
.AddJsonFile(Path.ChangeExtension(file, ".json"), optional: true)
.Build();
}
}
}
五、高级原理:FeatureProvider 的执行机制
5.1 多个 FeatureProvider 的协同
csharp
// 控制器发现 = 所有 ControllerFeatureProvider 的并集
public void PopulateControllers(ApplicationPartManager manager, ControllerFeature feature)
{
// 获取所有 ControllerFeatureProvider
var providers = manager.FeatureProviders
.OfType<IApplicationFeatureProvider<ControllerFeature>>();
foreach (var provider in providers)
{
provider.PopulateFeature(manager.ApplicationParts, feature);
// 每个 provider 都可以向 feature.Controllers 添加类型
}
// 最终 feature.Controllers 包含所有 Provider 发现的控制器
}
5.2 执行顺序图
ControllerFeature DynamicApiFeatureProvider ControllerFeatureProvider ApplicationParts ApplicationPartManager 应用启动 ControllerFeature DynamicApiFeatureProvider ControllerFeatureProvider ApplicationParts ApplicationPartManager 应用启动 Provider 列表: [P1, P2] PopulateFeature<ControllerFeature>() 获取所有 IApplicationFeatureProvider<ControllerFeature> PopulateFeature(Parts, Feature) 遍历所有 AssemblyPart 返回类型信息 判定是否为控制器 Feature.Controllers.Add(UserController) PopulateFeature(Parts, Feature) 遍历所有 AssemblyPart 返回类型信息 判定是否为控制器(含自定义规则) Feature.Controllers.Add(UserService) 包含 [UserController, UserService, ...]
六、与 Admin.Core Dynamic API 的关系
基于对 Application Part 和 Feature Provider 的深入理解,Admin.Core 的 Dynamic API 机制就清晰了:
csharp
public static IServiceCollection AddDynamicApi(this IServiceCollection services, DynamicApiOptions options)
{
// 1. 获取 ApplicationPartManager(必须已在 AddMvc 中创建)
var partManager = services.GetSingletonInstanceOrNull<ApplicationPartManager>();
if (partManager == null)
{
throw new InvalidOperationException("AddDynamicApi must be after AddMvc");
}
// 2. 添加自定义 FeatureProvider,扩展"控制器"的定义
partManager.FeatureProviders.Add(
new DynamicApiControllerFeatureProvider(options.SelectController)
);
// 3. 添加约定配置(路由、动词、参数绑定等)
services.Configure<MvcOptions>(o =>
{
o.Conventions.Add(new DynamicApiConvention(options.SelectController));
});
return services;
}
关键点:
- Dynamic API 不是替换 MVC ,而是扩展 MVC
- 通过添加 FeatureProvider,让 MVC 的控制器发现机制识别
IDynamicApi接口 - 通过添加 Convention,自动生成路由和 HTTP 动词
七、性能考量与最佳实践
7.1 性能影响
| 操作 | 影响 | 建议 |
|---|---|---|
| 增加 ApplicationPart | 启动时扫描类型增加 | 只添加必要的程序集 |
| 增加 FeatureProvider | 每个 Provider 都遍历 Parts | 避免过多自定义 Provider |
| 动态加载程序集 | 首次加载有 I/O 开销 | 使用异步加载,缓存类型信息 |
7.2 最佳实践清单
csharp
// ✅ 推荐:按需加载
if (featureFlags.EnableAdminModule)
{
manager.ApplicationParts.Add(new AssemblyPart(adminAssembly));
}
// ✅ 推荐:使用命名约定便于管理
public const string ModuleAssemblySuffix = ".Module.dll";
// ✅ 推荐:缓存程序集信息
private static readonly ConcurrentDictionary<string, Assembly> _loadedAssemblies = new();
// ❌ 避免:重复添加同一程序集
if (!manager.ApplicationParts.Any(p => p.Name == assembly.GetName().Name))
{
manager.ApplicationParts.Add(new AssemblyPart(assembly));
}
// ❌ 避免:运行时修改 PartManager(不支持)
// manager.ApplicationParts.Add(...) // 只能在启动时配置
八、总结
核心要点回顾
| 概念 | 一句话概括 |
|---|---|
| Application Part | 功能模块的容器,封装程序集等资源 |
| AssemblyPart | 最常用的 Part 实现,封装 .NET 程序集 |
| Feature Provider | 从 Part 中提取特定功能的策略 |
| ApplicationPartManager | 协调 Part 和 Provider 的管理器 |
架构价值
┌─────────────────────────────────────────────────────────────┐
│ Application Part 架构价值 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 模块化 ── 将应用拆分为独立的 Part │
│ 2. 插件化 ── 运行时动态加载外部 Part │
│ 3. 可扩展 ── 自定义 FeatureProvider 扩展框架能力 │
│ 4. 可配置 ── 灵活决定哪些 Part 参与功能发现 │
│ │
└─────────────────────────────────────────────────────────────┘
理解层次
层次1(基础):知道如何加载外部程序集中的控制器
层次2(进阶):理解 FeatureProvider 的工作机制
层次3(精通):能够自定义 FeatureProvider 实现框架扩展
层次4(架构):设计基于 Application Part 的插件化系统
Application Part 和 Feature Provider 是 ASP.NET Core 模块化架构的基石,理解它们不仅有助于日常开发,更是设计大型、可扩展应用的关键。希望本文能帮助你深入理解这两个核心概念。