ASP.NET Core 核心深度解析:Application Part 与 Feature Provider 完全指南

ASP.NET Core 核心深度解析:Application Part 与 Feature Provider 完全指南

前言

ASP.NET Core 开发中,你是否思考过这些问题:

  • 框架是如何发现项目中的控制器的?
  • 为什么引用的类库中的控制器会自动生效?
  • 如何实现插件化架构,动态加载外部程序集中的控制器?

答案就是 Application PartFeature 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 核心流程图

  1. 注册阶段
    根据发现的类型
    生成路由表
    注册到 DI 容器
  2. 发现阶段
    遍历所有 ControllerFeatureProvider
    遍历所有 ApplicationPart
    判断类型是否满足控制器条件
    添加到 ControllerFeature.Controllers
  3. 构建阶段
    应用启动
    创建 ControllerFeature
    调用 PopulateFeature
  4. 配置阶段(可选)
    ConfigureApplicationPartManager
    添加/移除 ApplicationPart
    添加/移除 FeatureProvider
  5. 初始化阶段
    调用 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 模块化架构的基石,理解它们不仅有助于日常开发,更是设计大型、可扩展应用的关键。希望本文能帮助你深入理解这两个核心概念。

相关推荐
飞瀑2 小时前
ASP.NET Core MVC 核心架构深度解析
架构·mvc·.net core
.NET修仙日记4 天前
2026 .NET 面试八股文:高频题 + 答案 + 原理(面试加分技巧)
面试·职场和发展·.net·.net core·微软技术
时光追逐者4 天前
2026 年 .NET 客户端常用 MVVM 框架推荐
c#·.net·mvvm·.net core
.NET修仙日记7 天前
2026 .NET 面试八股文:高频题 + 答案 + 原理(高级核心篇)
面试·职场和发展·c#·.net·.net core·.net 8
雪飞鸿16 天前
ArrayPoolWrapper简洁、安全的ArrayPool
c#·.net·.net core·原创
风口旁的猪17 天前
一套可落地的 .NET 8 微服务/分布式工程实践
docker·consul·.net core·efcore·refit
EdisonZhou24 天前
MAF快速入门(24)整合多个Skill来源
llm·agent·.net core
hez201025 天前
C# 15 类型系统改进:Union Types
c#·.net·.net core
csdn_aspnet1 个月前
了解 ASP.NET Core 中的防伪技术
后端·asp.net·csrf·.net core