ASP.NET Core源码解读 - 应用启动

1,456 阅读4分钟
  • 持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情
  • Visual Studio(2022) 创建的 ASP.NET Core 或者使用 SDK(.NET6.0) 创建的应用使用顶级语句。

C# 9 之前,程序的入口点为 Main 的静态方法。

static void Main(string[] args) 
{ 
    Console.WriteLine("Hello world"); 
}

C# 9 开始,无需在控制台应用程序项目中显式包含 Main 方法。

Console.WriteLine("Hello World");

前言

如上是.NET 6 / C# 10 创建项目程序结构的一些变化,不要觉得陌生,个人觉得这也是一大优化点。使用顶级语句功能最大程度地减少必须编写的代码,但是值得注意的有几点:

  • 只能有一个顶级文件:即一个程序只能存在一个入口
  • 顶级语句可以引用 args 变量来访问输入的任何命令行参数(永远不会为 null),但如果未提供任何命令行参数,则其 Length 将为零。
  • 顶级语句的文件还可以包含命名空间和类型定义,但它们必须位于顶级语句之后
  • 可以直接使用await调用异步方法
  • 如果包含 using 指令,则它们必须首先出现在文件中

了解了这些变化之后就让我们一起来探索我们ASP.NET Core是如何启动的,并且启动过程中都做了哪些事情吧。

Program 类

Program.cs 为模板创建.NET Core应用程序的启动代码

image.png

  • WebApplication.CreateBuilder(args)
/// <summary>
/// Initializes a new instance of the class with preconfigured defaults.
/// </summary>
public static WebApplicationBuilder CreateBuilder(string[] args) =>
    new(new() { Args = args });

简单点说就是创建一个WebApplicationBuilder实例

  • build.Services

主要做的就是将服务添加到 DI 容器中,在创建WebApplicationBuilder实例的时候会添加许多框架提供的服务。

// 以AddControllers为例,为指定的控制器添加服务至容器中
builder.Services.AddControllers();

// 此处为AddControllers扩展方法源码
public static IMvcBuilder AddControllers(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }
    // 添加Controllers核心服务
    var builder = AddControllersCore(services);
    // return MvcBuilder实例
    return new MvcBuilder(builder.Services, builder.PartManager);
}

// AddControllersCore 源码
private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
{
    // This method excludes all of the view-related services by default.
    var builder = services
        .AddMvcCore() //将最小基本 MVC 服务添加到 IServiceCollection
        .AddApiExplorer() // 配置为使用 ApiExplorer。
        .AddAuthorization() // 配置认证授权服务
        .AddCors() // 配置跨域
        .AddDataAnnotations() // 注册MVC数据注释。
        .AddFormatterMappings(); // 添加服务以支持 FormatterMappings(用于指定 URL 格式与相应媒体类型之间的映射)

    if (MetadataUpdater.IsSupported)
    {
        // 注册支持同一服务类型的多个注册的服务类型的服务
        services.TryAddEnumerable(
            ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>());
    }

    return builder;
}
  • builder.Build() 这一段源码太长就不一一拉出来看,大家如果感兴趣也可以去研究研究

构造一个 WebApplication 实例。

  • 设置构造主机的配置。
  • 将已经构建的配置提供程序复制到最终的配置生成器,连接应用程序配置。
  • 为构建过程和应用程序的其余部分设置配置。
  • 复制WebApplicationBuilder添加的服务
  • 官方

WebApplicationBuilder.Build 方法使用一组默认选项配置主机,例如:

  • 将 Kestrel 用作 Web 服务器并启用 IIS 集成。
  • 从 appsettings.json、环境变量、命令行参数和其他配置源中加载配置
  • 将日志记录输出发送到控制台并调试提供程序。

image.png

  • app.Environment.IsDevelopment() 用于判断运行环境(例如 DevelopmentStaging 和 Production

ASP.NET Core 在应用启动时读取该环境变量,并将该值存储在 IWebHostEnvironment 实现中

// 如果是Develop环境 则配置Swagger中间件
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
  • 请求处理管道由一系列中间件组件组成,通过调用 Use{Feature} 扩展方法,向管道添加中间件组件

    • 什么是中间件:

    中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

    • 选择是否将请求传递到管道中的下一个组件。
    • 可在管道中的下一个组件前后执行工作。
    • app.UseAuthorization() 添加授权中间件

image.png

  • app.Run

运行应用程序并阻塞调用线程,直到主机关闭。
注意:Run() 方法被称为终端中间件,要放在所有中间件的最后面,否则在Run()方法后面的中间件将不会被执行。

结语

至此就是整个ASP.NET Core运行的整个流程,当然这其中也包括了许多ASP.NET Core 应用的基础知识,包括依赖关系注入 (DI)、配置、中间件,要想真正了解,还得多看看源码实现,只有了解了基础你在自己研究框架的时候才能更加的轻松、游刃有余。

  • 文末彩蛋 .NET Core 为什么能跨平台?

CreateBuilder 内部调用了 UseKestrel(IWebHostBuilder)Kestrel 服务器 是默认跨平台 HTTP 服务器实现。 Kestrel 提供了最佳性能和内存利用率,但它没有 HTTP.sys 中的某些高级功能。

  • 本身作为边缘服务器,处理直接来自网络(包括 Internet)的请求。

image.png

  • 与反向代理服务器结合使用。 反向代理服务器接收来自 Internet 的 HTTP 请求,并将这些请求转发到 Kestrel。

image.png