【.Net技术栈梳理】10-.NET Core 程序的执行

文章目录

  • [1. .NET Core 程序的执行顺序与运行过程](#1. .NET Core 程序的执行顺序与运行过程)
    • [1.1 阶段 1:构建主机(配置和服务的准备)](#1.1 阶段 1:构建主机(配置和服务的准备))
    • [1.2 阶段 2:运行主机(中间件管道的构建与请求处理)](#1.2 阶段 2:运行主机(中间件管道的构建与请求处理))
  • [2. 中间件的加载与运作机制](#2. 中间件的加载与运作机制)
    • [2.1 什么是中间件?](#2.1 什么是中间件?)
    • [2.2 如何添加和配置中间件?](#2.2 如何添加和配置中间件?)
    • [2.3 中间件的标准顺序("官方配方")](#2.3 中间件的标准顺序(“官方配方”))
    • [2.4 中间件的运作模式:Request Delegate 和 next](#2.4 中间件的运作模式:Request Delegate 和 next)
    • [2.5 短路(Short-Circuiting)](#2.5 短路(Short-Circuiting))
    • [2.6 创建自定义中间件](#2.6 创建自定义中间件)

理解 .NET Core 程序的执行顺序和中间件模型,是构建高效、可定制 Web 应用程序的关键。

这里将从程序的启动入口开始,详细讲解整个执行过程,并深入剖析中间件的加载和运作机制。

1. .NET Core 程序的执行顺序与运行过程

整个过程可以清晰地分为构建(Build)运行(Run) 两大阶段。

自 .NET 6 引入的"最小托管模型"使得这一过程更加简洁明了。

1.1 阶段 1:构建主机(配置和服务的准备)

一切始于 Program.cs 中的 WebApplication.CreateBuilder(args) 方法。

csharp 复制代码
// Program.cs (.NET 6+)
var builder = WebApplication.CreateBuilder(args);

这行代码在背后做了大量工作:

  1. 初始化配置(Configuration):

    • 创建了一个 ConfigurationManager 对象。
    • 按照预定的顺序(后添加的源会覆盖先添加的)从各种配置源加载配置:
      • appsettings.json 和 appsettings.{Environment}.json(如 appsettings.Development.json)
      • 环境变量
      • 命令行参数
      • 用户机密(仅在开发环境)
    • 最终形成一个统一的配置根,可以通过 builder.Configuration 访问。
  2. 配置依赖注入(Dependency Injection, DI)容器:

    • 创建了一个 IServiceCollection 的实例。
    • 自动添加框架基础服务:如日志(ILogging)、配置(IConfiguration)、WebHost 环境(IWebHostEnvironment)等最核心的服务。
    • 此时,我们可以通过 builder.Services 来注册我们应用自己的服务(如 AddControllers, AddDbContext, AddScoped, AddSingleton 等)。
  3. 创建主机基础结构:

    • 配置 Kestrel Web 服务器(默认的、跨平台的高性能服务器)。
    • 配置日志记录提供程序。

此阶段的总结:WebApplicationBuilder 就像一个"总工程师",它按照蓝图(各种配置源)准备好了所有原材料(配置)和工具(服务),并搭建好了工厂(主机)的基础设施。

1.2 阶段 2:运行主机(中间件管道的构建与请求处理)

接下来是 var app = builder.Build(); 和后续的配置。

csharp 复制代码
var app = builder.Build();
  1. 构建服务容器:

    • builder.Build() 方法使用之前注册的所有服务(builder.Services)来构建最终的 IServiceProvider(即依赖注入容器)。
    • 此后,无法再注册新的服务。
  2. 配置中间件管道(Middleware Pipeline):

    • 这是最核心、最能体现执行顺序的部分。app 对象(WebApplication 类型)提供了配置请求管道的方法。
    • 管道是一个请求委托(Request Delegate) 的链表,每个委托都可以对传入的 HTTP 请求进行操作,然后选择将其传递给下一个委托,或者直接终止管道(短路)。
    • 中间件的添加顺序决定了它们的执行顺序
  3. 运行应用程序:

    • app.Run(); 启动应用程序,开始监听配置的 URL(如 http://localhost:5000https://localhost:7001)。
    • Kestrel 开始接收传入的 HTTP 请求。
    • 对于每个请求,Kestrel 会将其包装成一个 HttpContext 对象(包含了 HttpRequest 和 HttpResponse),然后将这个上下文对象送入中间件管道进行处理。

2. 中间件的加载与运作机制

2.1 什么是中间件?

中间件是组装成应用程序管道来处理请求和响应的软件组件。每个中间件组件:

  • 选择是否将请求传递给管道中的下一个组件。
  • 可以在调用下一个组件之前和之后执行工作。

2.2 如何添加和配置中间件?

在 app.Build() 之后,我们使用 WebApplication 上的方法来配置管道:

  • UseMiddleware< T >() / Use(...): 添加一个自定义的中间件类或内联中间件。

  • UseRouting(): 添加路由中间件,负责将请求匹配到端点(Endpoint)。

  • UseAuthentication(): 添加认证中间件。

  • UseAuthorization(): 添加授权中间件。

  • UseEndpoints(...): 添加端点中间件,用于执行匹配到的端点(如 MapControllers, MapRazorPages)。

  • Run(...): 添加一个终止中间件(管道末端,不会调用 next)。

  • Map(...): 创建管道分支(基于路径匹配)。

2.3 中间件的标准顺序("官方配方")

一个典型的、功能完整的中间件管道顺序如下,其结构可以通过以下流程图清晰展示:
HTTP Request 进入 异常/错误处理中间件
(UseExceptionHandler/UseDeveloperExceptionPage) HTTPS 重定向中间件
(UseHttpsRedirection) 静态文件中间件
(UseStaticFiles) 路由中间件
(UseRouting) 认证中间件
(UseAuthentication) 授权中间件
(UseAuthorization) 端点中间件
(UseEndpoints) 终端中间件
(Run) 返回响应

为什么顺序如此重要?

  • 异常处理必须在最外层,以捕获管道中后续任何地方抛出的异常。

  • 静态文件放在路由之前,因为对于像 css、js、image 这样的文件请求,不需要经过认证、授权等复杂逻辑,直接返回即可,性能最高。如果先进了路由,就找不到对应的 Controller 和 Action 了。

  • 认证/授权 必须在路由之后、端点之前。因为路由中间件已经确定了请求要访问哪个端点(Endpoint),而授权策略([Authorize] 特性)是附加在端点(Controller/Action)上的。授权中间件需要知道目标端点是什么,才能决定应用哪种授权策略。

2.4 中间件的运作模式:Request Delegate 和 next

每个中间件本质上都是一个委托,其签名是 Task RequestDelegate(HttpContext context)。

管道中的每个中间件都可以通过调用 next(context) 将请求传递给下一个中间件。

经典的模式: "环绕" 或 "洋葱" 模型

csharp 复制代码
app.Use(async (context, next) =>
{
    // 1. 在调用下一个中间件之前执行的逻辑 (传入请求)
    Log.Information("Request starting...");
    await context.Response.WriteAsync("First Middleware Says Hello!<br>");

    await next.Invoke(); // 将请求传递给管道中的下一个中间件

    // 2. 在下一个中间件执行完毕回来后执行的逻辑 (传出响应)
    Log.Information("Request finished.");
    await context.Response.WriteAsync("First Middleware Says Goodbye!<br>");
});

app.Run(async (context) =>
{
    await context.Response.WriteAsync("Terminal Middleware Handled the Request!<br>");
});

对于上述管道,请求/响应的流程和输出将是

csharp 复制代码
Request -> First Middleware (写 "Hello") -> Terminal Middleware (写 "Handled") -> First Middleware (写 "Goodbye") -> Response

2.5 短路(Short-Circuiting)

中间件可以选择不调用 next(),从而直接终止管道,处理请求并返回响应。这称为"短路"。

  • 静态文件中间件:如果请求匹配到一个物理文件(如 site.css),它会直接返回该文件并短路管道。

  • 身份认证中间件:如果请求未认证且访问的是需要认证的资源,它可以重定向到登录页或返回 401 状态码。

  • 自定义中间件:例如,一个请求日志中间件发现 404 错误,可以直接返回一个自定义的 404 页面,而无需经过后续昂贵的 MVC 路由系统。

csharp 复制代码
// 一个短路示例:健康检查端点
app.Use(async (context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/health"))
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync("Healthy");
        return; // 短路,不调用 next
    }
    await next();
});

2.6 创建自定义中间件

方法一:约定式中间件类

csharp 复制代码
public class RequestLoggerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    // 约定:必须包含RequestDelegate参数和可选的后续参数
    public RequestLoggerMiddleware(RequestDelegate next, ILogger<RequestLoggerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    // 约定:必须叫Invoke或InvokeAsync,接收HttpContext参数
    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation("Handling request: " + context.Request.Path);
        await _next(context); // 调用管道中的下一个组件
        _logger.LogInformation("Finished handling request.");
    }
}

// 扩展方法,用于优雅注册
public static class RequestLoggerMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggerMiddleware>();
    }
}

// 在Program.cs中使用
app.UseRequestLogger(); // 非常简洁

方法二:实现 IMiddleware 接口

csharp 复制代码
public class CustomMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        // 前置逻辑
        await next(context); // 传递上下文
        // 后置逻辑
    }
}

// 注册(需要在DI容器中注册)
builder.Services.AddTransient<CustomMiddleware>();
app.UseMiddleware<CustomMiddleware>();

总结

  1. 执行顺序 :.NET Core 程序启动遵循 构建配置与服务注册 -> 构建容器与中间件管道 -> 运行监听 的清晰流程。

  2. 运行过程:每个 HTTP 请求都被包装为 HttpContext,并流经预先构建好的中间件管道。

  3. 中间件本质:是处理 HttpContext 的委托链,通过 next 串联。

  4. 核心原则顺序至关重要。中间件的添加顺序决定了它们处理请求和响应的顺序,直接影响应用的行为、性能和安全性。

  5. 设计模式:采用"洋葱模型",请求先逐层深入,响应再逐层返回。中间件有权决定是否传递请求(短路)。

理解了这个流程和中间件模型,就能非常灵活地定制 ASP.NET Core 应用程序的行为,例如添加全局异常处理、自定义认证、日志记录、性能监控等组件,并将它们精确地插入到管道的合适位置。

相关推荐
薄荷撞~可乐3 小时前
C#高并发与并行理解处理
开发语言·c#
孤廖3 小时前
【算法磨剑:用 C++ 思考的艺术・Dijkstra 实战】弱化版 vs 标准版模板,洛谷 P3371/P4779 双题精讲
java·开发语言·c++·程序人生·算法·贪心算法·启发式算法
sali-tec3 小时前
C# 基于halcon的视觉工作流-章33-矩状测量
开发语言·人工智能·算法·计算机视觉·c#
酷炫码神4 小时前
第 2 篇:Java 入门实战(JDK8 版)—— 编写第一个 Java 程序,理解基础运行逻辑
java·开发语言·策略模式
像风一样自由20204 小时前
Go语言详细指南:特点、应用场景与开发工具
开发语言·后端·golang
半夏知半秋4 小时前
基于跳跃表的zset实现解析(lua版)
服务器·开发语言·redis·学习·lua
Wyc724094 小时前
Lua语言基础笔记
开发语言·笔记·lua
编码浪子8 小时前
趣味学RUST基础篇(智能指针_结束)
开发语言·算法·rust
CVer儿10 小时前
qt资料2025
开发语言·qt