惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
Cloudbric
Cloudbric
云风的 BLOG
云风的 BLOG
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
IT之家
IT之家
F
Full Disclosure
B
Blog RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
B
Blog
H
Help Net Security
The Cloudflare Blog
Recorded Future
Recorded Future
P
Proofpoint News Feed
P
Proofpoint News Feed
C
Cisco Blogs
T
Tailwind CSS Blog
P
Palo Alto Networks Blog
D
Docker
爱范儿
爱范儿
Know Your Adversary
Know Your Adversary
博客园 - 聂微东
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Y
Y Combinator Blog
雷峰网
雷峰网
AWS News Blog
AWS News Blog
D
DataBreaches.Net
博客园 - 司徒正美
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - Franky
C
Cybersecurity and Infrastructure Security Agency CISA
Blog — PlanetScale
Blog — PlanetScale
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
CXSECURITY Database RSS Feed - CXSecurity.com
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
腾讯CDC
小众软件
小众软件
G
Google Developers Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Scott Helme
Scott Helme
O
OpenAI News

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12) Snack JSONPath 项目架构分析 Claude Code Buddy 小析:一个非核心功能,如何体现产品的细节完成度 AI新时代下的图床管理方案-Cloudflare图床+MCP+Skills方案指南 化繁为简:顺丰速运App如何通过 HarmonyOS SDK实现专业级空间测量 从零实现富文本编辑器#13-React非编辑节点的内容渲染 AI开发-python-langchain框架(3-23-OpenAI Functions风格Tool Calling智能助手) .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut PbootCMS 网站内容数量多导致访问慢?这些实用优化方案帮你提速! - 家兴网络技术工作室 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 网站漏洞怎么发现并修复?一篇实用指南(附完整流程) - 家兴网络技术工作室 开了 TUN 模式还是直连?90% 的人都踩过这个坑 Github日报|2026年04月12日 - AI一族 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术 Etsy 把 1000 个 MySQL 分片迁进 Vitess:425TB 数据背后的真正问题不是性能,而是运维规模 MicroPython LVGL基础知识和概念:底层渲染与性能优化 - FreakStudio 数据库草图算法 Python 潮流周刊#146:CPython 引入 Rust 的进展 - 豌豆花下猫 最小生成树 - mofei1116 红日靶场七:从外网入口、容器逃逸到 AD 接管的完整利用链复盘 - YouDiscovered1t 分享四款开源且实用的 Kafka 管理工具 - 追逐时光者 vLLM 权重加载机制全解析:从挑战到理想架构 LCT 学习笔记 - ACehomoxue Avalonia UI 12.0.0 正式发布:架构演进和性能飞跃 - 张善友 当 AI Agent 把调用链拉长,延迟开始成为一门生意 conhost.exe 无法显示 U+2717 - 145a 太秀了,我把自己蒸馏成了 Skill!已开源 - 程序员鱼皮 ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑 基于 Ghostty 带有分割标签页和为 Claude 编程设计的通知终端 - BugShare AI 焊死入口:教育的“操作系统级”重塑 - 郝hai 初级Java开发工程师使用sql脚本编写代码的过程是简单而且不糊涂 - CoderOilStation Claude Code通关手册(六):MCP协议完全指南 - 暮色之狐 边框灯光环绕动画特效实现指南 - Newbe36524 开源:子木蒸馏版的 SEO 审计工具 seo-audit-skill v1.0 我所理解的Python元模型 【从0到1构建一个ClaudeAgent】规划与协调-TodoWrite - 程序员Seven Claude 和 Codex 在审计 Skill 上性能差异探究 - ACai_sec AScript如何实现中文脚本引擎 - rockey627 【渗透测试】HTB Season10 Garfield 全过程wp - dynasty_chenzi Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革 树状数组正确性证明 - AC-wyr 你的 AI 焦虑,可能比 AI 本身更危险——ATM 机没有消灭银行柜员,但恐慌消灭了你的判断力 - 我没有三颗心脏 一个拉胯的分库分表方案有多绝望?整个部门都在救火! - 冰河团队 动态规划入门必学之走方格问题 - Ofnoname PostgREST 与 PostgreSQL 角色权限配置全解析(生产级实践) - SheepDog1998 使用 UEFI 图形输出协议 GOP 在屏幕上显示图像的方法 - 阿源- Claude Code通关手册(五):组建你的AI专家团队,子代理系统 - 暮色之狐 一个程序员到架构师的催婚路之感悟(整整10年后的催婚相亲感悟) - MisterLip 用 Agent Skill 自动生成工作周报 - 赵康
.NET 8 Web开发入门(三):解构引擎——依赖注入(DI)与中间件管道
码农刚子 · 2026-05-11 · via 博客园_首页

大家好,我是码农刚子。感谢各位朋友对我前两篇入门教程文章的热烈反馈和宝贵支持!🙏 看到评论区里说“通俗易懂、很容易理解”,“很详细”,“写的很好,继续努力”。以及给我的一些建议非常专业,我会认真消化,尽量在后面独立成章来回应你的期待。同时也要感谢所有默默点赞(支持(1)背后的你们)和持续关注的读者。

好了,闲言少叙,进入正题——第三篇:依赖注入与中间件。这篇会带大家彻底搞懂DI容器的作用域、服务生命周期,以及请求管道的核心中间件原理。希望继续保持前两篇的“通俗详细”风格,不辜负大家的每一份支持。上干货!👇

一、前言:从“作坊”到“工厂”

在上一篇文章中,我们学会了C#的现代语法,就像掌握了制造精密零件的技术。现在,我们需要把这些零件组装成一台能运转的发动机。

在ASP.NET Core中,有两样东西构成了这台发动机的骨架:依赖注入(DI)中间件

如果你不理解它们,你写的代码可能会变成紧紧缠绕的一团乱麻(我们称之为“面条代码”),难以测试、难以修改。理解了它们,你就掌握了现代Web开发的“设计模式之钥”。

二、灵魂机制:依赖注入(DI)

2.1 为什么要“注入”?——解决紧耦合

假设你需要在一个API中记录日志。最直观的写法可能是直接在代码里 new 一个对象:

app.MapGet("/bad", () =>
{
    var logger = new FileLogger(); // 直接依赖具体的实现类
    logger.Log("这是一条日志");
    return "日志已记录";
});

这种写法看似简单,实则隐患重重:

  1. 紧耦合:你的API代码死死地绑定了 FileLogger。如果哪天老板说“改成存数据库”,你得修改每一处 new FileLogger()
  2. 难以测试:做单元测试时,你不想真的去写文件,想用一个假的记录器,但现在你无法替换。

依赖注入的核心思想是:“不要自己new,需要什么向容器要”(控制反转,IoC)。

2.2 服务的三生三世:生命周期

在.NET的DI容器中,注册的服务有三种主要生命周期。这是新手最容易踩坑的地方,请务必理解:

  1. Transient(瞬态)用完即弃。每次请求该服务,容器都会给你一个全新的实例。适合轻量级、无状态的服务(如简单的计算器、格式化工具)。
  2. Scoped(范围)一次请求一生。在一次HTTP请求范围内,无论你在多少个地方请求它,拿到的都是同一个实例。这是Web开发中最常用的模式,特别是用于数据库上下文(DbContext)
  3. Singleton(单例)万世一系。整个应用程序生命周期内,只存在一个实例。适合全局缓存、全局配置。注意:单例服务必须是线程安全的!

.NET依赖注入服务生命周期示意图,展示Transient、Scoped、Singleton的区别

2.3 实战:构建一个性能监控服务

我们来写一个真实的案例:统计API的执行耗时。

第一步:定义契约(接口) 良好的架构总是面向接口编程。

// IPerformanceTracker.cs
public interface IPerformanceTracker
{
    void Start();
    void Stop();
    long GetElapsedTime();
}

第二步:实现服务

// PerformanceTracker.cs
public class PerformanceTracker : IPerformanceTracker
{
    private Stopwatch _stopwatch = new Stopwatch();

    public void Start() => _stopwatch.Restart();
    
    public void Stop() => _stopwatch.Stop();

    public long GetElapsedTime() => _stopwatch.ElapsedMilliseconds;
}

第三步:在Program.cs中注册服务

var builder = WebApplication.CreateBuilder(args);

// --- 注册服务 ---
// 这里我们使用 Scoped,因为耗时统计通常是针对单个请求的
builder.Services.AddScoped<IPerformanceTracker, PerformanceTracker>();

// 添加Swagger等基础服务
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// ... 中间件配置 ...

第四步:在API中注入并使用 在Minimal API中,我们通过方法参数注入服务。

app.MapGet("/test-performance", (IPerformanceTracker tracker) =>
{
    tracker.Start();
    
    // 模拟耗时操作
    Thread.Sleep(500); 
    
    tracker.Stop();
    
    return $"接口执行耗时: {tracker.GetElapsedTime()} ms";
});

架构师视角的深意: 注意看,我们的API代码里完全没有 new PerformanceTracker()。这意味着,如果明天我们需要升级监控逻辑(比如加上日志记录),我们只需要修改 PerformanceTracker.cs 类,而API接口的代码一行都不用动。这就是解耦带来的维护性提升。

三、传动装置:中间件管道

如果说DI是提供动力的气缸,那么中间件就是负责传递动力的齿轮和传送带。

3.1 管道模型:俄罗斯套娃

ASP.NET Core 处理HTTP请求的方式,就像水流通过一系列过滤层。

  1. 请求进入管道。
  2. 经过一个个中间件。
  3. 中间件可以在处理做事(如记录请求日志)。
  4. 中间件调用 next() 将请求传给下一个中间件。
  5. 到达最终处理逻辑(你的API代码)。
  6. 响应沿着管道反向流出。
  7. 中间件可以在处理做事(如记录响应日志、处理异常)。

ASP.NET Core中间件请求管道流向示意图,展示请求与响应的双向流动

3.2 编写你的第一个自定义中间件

我们来写一个最简单的中间件:请求计时器。它将在控制台打印每个请求的耗时。

var app = builder.Build();

// --- 自定义中间件 ---
app.Use(async (context, next) =>
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    
    Console.WriteLine($"[中间件] 请求开始: {context.Request.Path}");

    // 关键步骤:调用下一个中间件
    // 这里使用 await 等待后续管道全部执行完毕
    await next(context); 

    stopwatch.Stop();
    
    Console.WriteLine($"[中间件] 请求结束: {context.Request.Path}, 耗时: {stopwatch.ElapsedMilliseconds}ms");
});

// 确保有Swagger中间件
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapGet("/", () => "Hello World!");

app.Run();

运行这段代码,并在浏览器访问 http://localhost:5000/,你会看到控制台输出了耗时信息。

3.3 “短路”机制:权限守门员

中间件有一个极其重要的能力:短路。如果中间件决定不调用 next(),管道就会直接折返,后续的逻辑(如你的API代码)将不会执行。

这非常适合做权限验证。

app.Use(async (context, next) =>
{
    // 模拟:检查Header里是否有密码
    if (!context.Request.Headers.ContainsKey("X-Secret-Key"))
    {
        // 没有密钥,直接返回401,不调用 next()
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("抱歉,你无权访问!");
        return; // 结束处理,管道短路
    }

    // 有密钥,放行
    await next(context);
});

这个特性让我们可以把横切关注点(如日志、权限、异常处理)从业务代码中剥离出来,放在管道的最外层统一处理。

四、DI与中间件的完美结合

作为本篇的压轴,我们将展示如何在一个中间件中使用依赖注入的服务。这是架构设计中非常常见的模式:在中间件里实现全局的异常捕获或性能监控

让我们把刚才的 IPerformanceTracker 服务集成到中间件里。

// 注册服务
builder.Services.AddScoped<IPerformanceTracker, PerformanceTracker>();

var app = builder.Build();

// 注册一个使用了DI的中间件
// 注意:这里我们不能直接在 Use 方法里通过参数注入 Scoped 服务,
// 因为中间件构造函数是在应用启动时执行的(Singleton行为),
// 但我们需要在请求上下文中获取 Scoped 服务。
// 以下是正确的写法:

app.Use(async (context, next) =>
{
    // 1. 从 HttpContext.RequestServices 中获取当前请求的服务容器
    var tracker = context.RequestServices.GetRequiredService<IPerformanceTracker>();
    
    tracker.Start();
    
    await next(context); // 执行后续管道
    
    tracker.Stop();
    
    // 假设我们想把耗时加到响应头里
    context.Response.Headers.Append("X-Response-Time", $"{tracker.GetElapsedTime()}ms");
});

app.MapGet("/heavy-task", async (IPerformanceTracker tracker) => 
{
    // 在API内部也可以再次注入使用,且因为是 Scoped,拿到的是同一个实例
    await Task.Delay(1000);
    return "任务完成";
});

app.Run();

关键知识点context.RequestServices 是访问当前请求作用域内服务的入口。虽然Minimal API支持直接在参数里注入,但在中间件这种早期阶段,我们必须手动从 HttpContext 中拉取服务。

五、常见误区与架构师建议

在多年的架构生涯中,我见过很多新手在使用DI和中间件时犯过以下错误,这里逐一提醒:

5.1 服务生命周期陷阱:Capturing Dependencies(依赖捕获)

错误做法:在一个 Singleton 服务中注入了一个 Scoped 服务。 后果:Scoped 服务本该在一次请求后销毁,但因为被 Singleton 服务长期持有,它变成了事实上的 Singleton。这会导致你的 DbContext 无法正确释放,内存泄漏,甚至并发错误。 原则:服务依赖的方向应该是:Transient -> Scoped -> Singleton。或者 Scoped -> Scoped。永远不要让生命周期长的服务依赖生命周期短的服务。

5.2 中间件顺序很重要

中间件的注册顺序直接决定了执行顺序。

  • UseExceptionHandler / UseDeveloperExceptionPage 应该放在最前面,这样才能捕获后续所有中间件的异常。
  • UseStaticFiles 应该放在 UseAuthorization 之前,否则静态文件(如图片、CSS)也需要权限验证,这通常是不必要的性能损耗。
  • UseSwagger 通常放在开发环境判断内部。

六、总结与下篇预告

恭喜你!读到这里,你已经触摸到了ASP.NET Core的骨架。

  • 依赖注入(DI):解耦的神器,让代码结构清晰,易于测试。记住 Transient、Scoped、Singleton 三种生命周期的区别。
  • 中间件:请求的筛子和过滤器,用于处理横切逻辑。记住 next() 是通往下一关的钥匙。

现在,我们的“引擎”已经组装完毕,具备了处理请求的核心能力。但是,引擎需要“燃料”才能源源不断地输出动力。在Web开发中,最核心的燃料就是数据

下一篇预告

在第四篇文章中,我们将连接数据库,引入 Entity Framework Core (EF Core)。你将学会如何用 C# 代码定义数据库结构(Code First),如何进行数据迁移,以及如何通过 EF Core 进行高效的增删改查。数据持久化的大门即将打开,敬请期待!