乐于分享
好东西不私藏

从源码到自定义:手撕 ASP.NET Core IOC 容器全流程

从源码到自定义:手撕 ASP.NET Core IOC 容器全流程

内置 IOC 不支持属性注入?Autofac 到底怎么接管底层容器的?自己写一个 IOC 容器替换进去需要几步?

如果你也被这些问题困扰过,这篇文章带你从 ASP.NET Core 源码 出发,逐行拆解 IOC 容器的注册、实例化、替换全流程,最后手写一个简易 IOC 容器完成框架替换。

建议收藏,反复阅读。

01前情回顾:从启动到响应,IOC 始终在场

在之前的源码专题中,我们已经完成了1件大事:

  • 启动 & 响应两大流程
    拆解,启动流程分为 6 个环节,从 WebApplication.CreateBuilder 到 app.Run() 的完整链路

在整个启动流程中,有一个角色贯穿始终——IOC 容器

无论是框架内置服务的注册、开发者自定义服务的注入,还是第三方容器(如 Autofac)的接管,所有动作都围绕 IOC 展开。但我们在前两期只是"路过"了它,没有深入。今天,我们就来彻底拆解它。

02IOC 三层递进扩展:从注册替换到容器替换

ASP.NET Core 的扩展性,本质上就是基于 IOC 的无止境扩展。按照扩展的深度和层级,可以划分为三个层次,层层递进:

层级
扩展点
核心能力
使用场景
第一层
IControllerFactory
替换控制器工厂,接管控制器创建流程
框架最常用扩展点,日志记录、权限校验、统一缓存等
第二层
IControllerActivator
自定义控制器激活器,支持属性注入、简易AOP
需要属性注入、方法拦截等场景,不换容器但增强功能
第三层
IServiceProviderFactory
完整替换底层 IOC 容器(如 Autofac)
AOP、Module 模块化注册、多种注册方式等高级需求

扩展1:IOC 注册替换 —— IControllerFactory

这是 ASP.NET Core 最常用的框架扩展点,几乎无处不在。

默认情况下,框架使用 DefaultControllerFactory 来创建控制器。我们可以自定义实现 IControllerFactory 接口,替换掉默认实现:

小技巧:可以把源码 DefaultControllerFactory ,抄过来,然后改个名就成自己的了...
注册方式有两种:AddTransient 直接注册,或用 Replace 替换默认实现:
关键理解:成功替换后,框架的默认实现就被换成了我们的自定义逻辑。你可以在 CreateController 里写日志、做权限校验、加缓存,任何你想要的事情。这种"注册替换"的扩展方式,在日常开发中用得特别特别多
想一想:你的项目中,有没有遇到过需要在每个控制器执行前统一加一段逻辑的场景?是用 Filter 做的,还是用中间件?现在你知道了,IControllerFactory 也是一个选择,而且它更贴近控制器实例化这一层。

扩展2:自定义控制器激活器 —— IControllerActivator

默认容器不支持属性注入,也不支持 AOP。但在 ASP.NET Core 范围内,我们可以通过自定义 IControllerActivator 来扩展这些能力。

先看自定义激活器的实现:

配合一个简单的特性标识:
在控制器中使用:
注册激活器:

AddControllersAsServices() 的作用

这里有一个关键方法 AddControllersAsServices(),它的作用是把控制器类型注册到 IOC 容器中

不加这个方法,控制器类型不会注册到容器中,GetService(controllerType) 会返回 null。加上之后,控制器本身也变成了 IOC 管理的服务,才能被容器正确实例化。

扩展2 的本质:这也是标准的 IOC 注册替换扩展,但在替换的同时添加了自定义动作——属性注入。甚至搞个简易 AOP 扩展也不难。但这是"小道",满足一些特殊需求。如果要更彻底的 AOP、更灵活的注册方式,需要进入第三层。

扩展3:完整替换底层 IOC 容器 —— Autofac

这是最常见的容器替换方案。很多人会用 Autofac,但很想懂 why——为什么要换?怎么换的?

Autofac 的优势

  • AOP 扩展方便
      原生支持 Castle DynamicProxy,方法拦截开箱即用
  • 注册方式灵活
     支持泛型注册、反射注册、实例注册、委托注册、程序集批量注册等 N 种方式
  • Module 模块化
     通过自定义 Module 类,将不同业务模块的注册逻辑解耦

实操步骤

第一步:Nuget 安装

第二步:替换容器工厂
第三步:Autofac 式注册
第四步:Module 模块化 + AOP 扩展
AOP 拦截器实现(基于 Castle.DynamicProxy):

03上帝视角:内置 IOC 完整生命周期

要理解 IOC 容器的全貌,需要先理解一个核心设计:注册和实例化是分开的

IOC 容器的两件事被拆开设计

第一件事:注册 —— ServiceCollection,负责收集所有服务注册关系

第二件事:实例化 —— ServiceCollection.BuildServiceProvider(),生成真正的容器实例

生命周期四阶段

阶段
时机
核心动作
阶段1:初始化Builder
ConfigBuilder 时
只要有 ServiceCollection 就直接写入;没有的话就保存到委托,后面再执行
阶段2:Build() 实例化
builder.Build() 时
实例化 ServiceCollection,执行各种默认注册(都是基于默认容器)
阶段3:容器替换
Build() 内部
基于 Factory 支持容器实例替换,注册关系会转换过去
阶段4:请求分配Scope
每次 Http 请求
按 HttpContext 分配 IOC 容器 Scope 实例
关键理解:你在 Program.cs 中写的 builder.Services.AddXxx(),并不是在那一刻就完成了注册的"最终生效"。它只是把注册关系保存到了 ServiceCollection(或者暂存为委托)。真正的注册执行,发生在 builder.Build() 阶段。

04源码拆解:IOC 注册与实例化流程

各种 IOC 注册的本质

系统中的各种 IOC 注册,本质上都在做一件事:写入 ServiceCollection

  • 框架内置注册(如 AddMvcCoreAddRouting 等)→ 写入 ServiceCollection
  • 开发者注册(builder.Services.AddTransient 等)→ 写入 ServiceCollection
  • 如果在 Builder 初始化阶段还没有 ServiceCollection,就先写入委托,后面执行委托
查看.NET CORE 关键源码

三段逻辑逐行解读:

  1. 实例化 ServiceCollection
     这是 IOC 注册的容器集合,所有的注册关系都存在这里
  2. 添加各种内置注册
     框架自身需要的服务全部注册进来,包括默认的 DefaultServiceProviderFactory——这就是为什么你不替换时,用的是内置容器
  3. 执行开发者委托
     你在 Program.cs 中写的 builder.Services.AddXxx()本质是注册了委托,在这里统一执行。注意顺序——在默认注册之后。

05核心亮点:容器替换源码逐行拆解

这是本文的核心亮点。容器替换到底是怎么发生的?答案在 Build() 里面的 GetProviderFromFactory 方法。

.NET CORE 核心源码

GetProviderFromFactory 方法逐行解读

三段逻辑讲透"默认容器如何被第三方容器接管"

第一段:先用内置方式构建一个"临时"的默认容器实例。这个容器实例的主要作用不是给业务用,而是从中获取工厂

第二段:从默认容器中获取 IServiceProviderFactory。这就是扩展点的核心!开发者在 Program.cs 中写的 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()),本质就是注册了一个不同的工厂。这个工厂会在这一步被取出来。

第三段:用取到的工厂创建 Builder 和 Provider。对于 Autofac 来说,CreateBuilder 会把 ServiceCollection 中的所有注册关系转换到 Autofac 的 ContainerBuilder 中,CreateServiceProvider 则生成 Autofac 的容器实例并包装成 IServiceProvider 返回。

所以替换了 Factory,就能替换掉容器实例。整个框架后续用的 Provider 就全部走第三方容器了。

06手写简易自定义 IOC 容器:四层结构完整设计

理解了容器替换的原理,我们来手写一个简易 IOC 容器,完成框架替换。需要准备四层组件:

组件
职责
对应接口/类
自定义容器接口
定义注册和获取实例的契约
IElevenContainer
自定义Factory
实现 IServiceProviderFactory,生成 Provider + 转换注册关系
YueContainerFactory
自定义Builder
真正负责完成注册关系转换和 Provider 生成
YueContainerBuilder
自定义Provider
最终提供实例获取的地方,包裹自定义容器
YueServiceProvider

第一层:自定义容器接口 IElevenContainer

三个方法:泛型注册、类型注册、实例获取。这是最简化的容器契约。

第二层:自定义 Factory —— YueContainerFactory

Factory 必须实现 IServiceProviderFactory<YueContainerBuilder> 接口,承担两个职责

  • 生成 Provider
  • 把注册的映射关系转换到自实现容器

第三层:自定义 Builder —— YueContainerBuilder

Builder 真正负责完成注册关系转换 + 生成 Provider

第四层:自定义 Provider —— YueServiceProvider

Provider 是最终提供实例获取的地方,里面包裹上自定义容器(或包裹内置容器做过渡):

替换接入框架

四层组件准备好后,只需一行代码完成替换:

// 替换为自己的容器工厂
builder.Host.UseServiceProviderFactory(new YueContainerFactory());

四层组件完整调用链路

逻辑闭环:Factory 是入口 → 找 Builder → Builder 完成映射关系转换 + 生成 Provider → Provider 是包了一层的 Container。之后框架所有的 GetService 调用都会走到你的 Provider,你的 Provider 再调用你的容器完成实例获取。逻辑完全闭环。

本文的手写容器是简化版,ElevenContainer 的 Regist/Resolve 方法留了 NotImplementedException。完整的 IOC 容器需要支持生命周期管理、依赖解析、循环依赖检测等,复杂度很高。但这四层结构的设计思路是完整的,理解了这个骨架,再看 Autofac 源码就清晰多了。

07完整 IOC 知识点总结

IOC 容器核心知识点回顾

  • 内置 IOC 容器的使用
     —— AddTransient/AddScoped/AddSingleton 三种生命周期,构造函数注入,GetService/GetServices 获取实例
  • IOC/DI 的理解
     —— 控制反转是思想,依赖注入是实现。容器负责实例化和管理对象生命周期,开发者只声明依赖
  • IOC 三层扩展
     —— 扩展1: IControllerFactory 注册替换;扩展2: IControllerActivator 激活器自定义(属性注入/简易AOP);扩展3: IServiceProviderFactory 容器整体替换(Autofac)
  • 源码解读 IOC 初始化
     —— BuildCommonServices 中先 new ServiceCollection,再添加内置注册,最后执行开发者委托。执行顺序:内置注册 → 开发者委托
  • 容器替换核心
     —— GetProviderFromFactory 方法三段逻辑:获取默认容器 → 获取工厂 → 用工厂创建新 Provider。替换 Factory 即替换容器
  • 第三方容器接入
     —— Autofac 通过实现 IServiceProviderFactory 接口,在 CreateBuilder 阶段转换注册关系,在 CreateServiceProvider 阶段生成新容器
  • 手写简易 IOC 容器
     —— 四层结构:IElevenContainer(容器接口)+ YueContainerFactory(工厂)+ YueContainerBuilder(转换器)+ YueServiceProvider(提供者)

HttpContext.RequestServices 作用域容器原理

最后点明一个关键原理:每次 HTTP 请求来了,框架会创建一个 Scope 级别的 ServiceProvider,保存到 base.HttpContext.RequestServices

所有的实例获取都基于这个 Scope Provider——这就是 Scoped 生命周期的实现原理。同一个请求内,Scoped 服务是同一个实例;不同请求之间,Scoped 服务是不同实例。

作用域单例靠的是作用域的容器实例——每次请求响应时,容器会 CreateScope,然后一直用这个容器。跟多线程没关系,是跟请求绑定的。

你在项目中用的是内置 IOC 还是 Autofac?为什么选择它?

有没有尝试过自己实现一个简易 IOC 容器?遇到了什么坑?

欢迎在评论区留言讨论,点赞最高的评论将获得下期源码专题的优先预告!

📢 下期预告ASP.NET Core 源码解读和进阶专题-4下期将深入拆解 配置文件系统(Configuration) 和 日志组件(Logging) 的源码实现,包括多数据源配置合并机制、日志 Provider 链式扩展、Log4Net/NLog 集成原理等,敬请关注!