ASP.NET Core 对本机 AOT 的支持

ASP.NET Core 8.0 引入了对 .NET 本机预先 (AOT) 的支持。

为什么要将本机 AOT 与 ASP.NET Core 配合使用

发布和部署本机 AOT 应用可提供以下优势:

  • 最大程度减少磁盘占用:使用本机 AOT 发布时,将生成一个可执行文件,其中仅包含支持程序所需的外部依赖项的代码。 减小的可执行文件大小可能会导致:
    • 较小的容器映像,例如在容器化部署方案中。
    • 缩短了较小映像的部署时间。
  • 缩短启动时间:本机 AOT 应用程序可缩短启动时间,这意味着
    • 应用已准备好更快地为请求提供服务。
    • 改进了容器业务流程协调程序需要管理从应用的一个版本到另一个版本的转换的部署。
  • 减少内存需求:本机 AOT 应用可能会减少内存需求,具体由应用执行的工作决定。 减少内存消耗可以提高部署密度和可伸缩性。

模板应用在基准测试实验室中运行,来比较 AOT 已发布的应用、已修剪的运行时应用和未修剪的运行时应用的性能。 下图显示了基准测试的结果:

Chart showing comparison of application size, memory use, and startup time metrics of an AOT published app, a runtime app that is trimmed, and an untrimmed runtime app.

上图显示本机 AOT 降低了应用大小、内存使用量和启动时间。

ASP.NET Core 和本机 AOT 兼容性

目前,并非所有 ASP.NET Core 功能都与本机 AOT 兼容。 下表汇总了 ASP.NET Core 功能与本机 AOT 的兼容性:

功能 完全支持 部分支持 不支持
gRPC 完全支持
最小 API 部分支持
MVC 不支持
Blazor Server 不支持
SignalR 不支持
JWT 身份验证 完全支持
其他身份验证 不支持
CORS 完全支持
HealthChecks 完全支持
HttpLogging 完全支持
本地化 完全支持
OutputCaching 完全支持
RateLimiting 完全支持
RequestDecompression 完全支持
ResponseCaching 完全支持
ResponseCompression 完全支持
Rewrite 完全支持
会话 不支持
Spa 不支持
StaticFiles 完全支持
WebSockets 完全支持

有关限制的更多信息,请参阅:

在迁移到本机 AOT 部署模型时,务必全面测试应用。 应测试 AOT 部署的应用,以验证功能未从未修整和 JIT 编译的应用更改。 生成应用时,请查看并更正 AOT 警告。 在发布期间发出 AOT 警告的应用可能无法正常工作。 如果在发布时未发出 AOT 警告,则已发布的 AOT 应用的工作方式应与在未剪裁的 JIT 编译的应用中相同。

本机 AOT 发布

本机 AOT 通过 PublishAot MSBuild 属性启用。 以下示例演示如何在项目文件中启用本机 AOT:

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>

此设置将在发布期间启用本机 AOT 编译,并在生成和编辑期间启用动态代码使用情况分析。 在本地运行时,使用本机 AOT 发布的项目将使用 JIT 编译。 AOT 应用与 JIT 编译的应用有以下区别:

  • 与本机 AOT 不兼容的功能会被禁用,并在运行时引发异常。
  • 启用源分析器以突出显示与本机 AOT 不兼容的代码。 在发布时,将再次分析整个应用(包括 NuGet 包)是否兼容。

本机 AOT 分析包括应用的所有代码和应用依赖的库。 查看本机 AOT 警告并采取纠正措施。 建议经常发布应用,以在开发生命周期的早期发现问题。

在 .NET 8 中,以下 ASP.NET Core 应用类型支持本机 AOT:

Web API(本机 AOT)模板

ASP.NET Core Web API(本机 AOT)模板(简称为 webapiaot)会创建一个启用了 AOT 的项目。 此模板与 Web API 项目模板的不同之处如下所示:

  • 仅使用最小 API,因为 MVC 尚不兼容于本机 AOT。
  • 使用 CreateSlimBuilder() API 确保默认情况下仅启用基本功能,从而最大程度地减小应用的部署大小。
  • 配置为仅侦听 HTTP,因为 HTTPS 流量通常由云原生部署中的入口服务处理。
  • 不包括用于在 IIS 或 IIS Express 下运行的启动配置文件。
  • 创建一个 .http 文件,其中配置了可发送至应用终结点的示例 HTTP 请求。
  • 包含一个示例 Todo API,而不是天气预报示例。
  • PublishAot 添加到项目文件,如本文前面所示。
  • 启用 JSON 序列化程序源生成器。 源生成器用于在生成时生成序列化代码,这是本机 AOT 编译所必需的。

支持源生成的更改

以下示例演示了添加到 Program.cs 文件来支持 JSON 序列化源生成的代码:

using MyFirstAotWebApi;
+using System.Text.Json.Serialization;

-var builder = WebApplication.CreateBuilder();
+var builder = WebApplication.CreateSlimBuilder(args);

+builder.Services.ConfigureHttpJsonOptions(options =>
+{
+  options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
+});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
    sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
        ? Results.Ok(todo)
        : Results.NotFound());

app.Run();

+[JsonSerializable(typeof(Todo[]))]
+internal partial class AppJsonSerializerContext : JsonSerializerContext
+{
+
+}

如果未添加此代码,System.Text.Json 使用反射来序列化和反序列化 JSON。 本机 AOT 不支持反射。

有关详细信息,请参阅:

launchSettings.json 的更改

由 Web API(本机 AOT)模板创建的 launchSettings.json 文件具有 iisSettings 部分,并且移除了 IIS Express 配置文件:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
-  "iisSettings": {
-     "windowsAuthentication": false,
-     "anonymousAuthentication": true,
-     "iisExpress": {
-       "applicationUrl": "http://localhost:11152",
-       "sslPort": 0
-     }
-   },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "todos",
      "applicationUrl": "http://localhost:5102",
        "environmentVariables": {
          "ASPNETCORE_ENVIRONMENT": "Development"
        }
      },
-     "IIS Express": {
-       "commandName": "IISExpress",
-       "launchBrowser": true,
-       "launchUrl": "todos",
-      "environmentVariables": {
-       "ASPNETCORE_ENVIRONMENT": "Development"
-      }
-    }
  }
}

CreateSlimBuilder 方法

此模板使用 CreateSlimBuilder() 方法而不是 CreateBuilder() 方法。

using System.Text.Json.Serialization;
using MyFirstAotWebApi;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.AddConsole();

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
    sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
        ? Results.Ok(todo)
        : Results.NotFound());

app.Run();

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}

CreateSlimBuilder 方法使用运行应用所需的最少 ASP.NET Core 功能初始化 WebApplicationBuilder

如前所述,CreateSlimBuilder 方法不包含对 HTTPS 或 HTTP/3 的支持。 在 TLS 终止代理后面运行的应用通常不需要这些协议。 例如,请参阅将 TLS 终止和端到端 TLS 与应用程序网关结合使用。 可通过调用 builder.WebHost.UseKestrelHttpsConfiguration 来启用 HTTPS,可通过调用 builder.WebHost.UseQuic 来启用 HTTP/3。

CreateSlimBuilderCreateBuilder

CreateSlimBuilder 方法不支持 CreateBuilder 方法所支持的以下功能:

CreateSlimBuilder 方法包含以下高效开发体验所需的功能:

  • appsettings.jsonappsettings.{EnvironmentName}.json 的 JSON 文件配置。
  • 用户机密配置。
  • 控制台日志记录。
  • 日志记录配置。

如需省略上述功能的生成器,请参阅 CreateEmptyBuilder 方法

包括最小功能对修剪和 AOT 都有好处。 有关详细信息,请参阅剪裁自包含部署和可执行文件

有关详细信息,请参阅比较 WebApplication.CreateBuilderCreateSlimBuilder

源生成器

由于在发布本机 AOT 时会剪裁未使用的代码,因此应用无法在运行时使用不受限制的反射。 源生成器用于生成可消除反射需求的代码。 在某些情况下,即使不需要生成器,源生成器也会生成针对 AOT 优化的代码。

若要查看生成的源代码,请将 EmitCompilerGeneratedFiles 属性添加到应用的 .csproj 文件中,如以下示例所示:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <!-- Other properties omitted for brevity -->
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  </PropertyGroup>

</Project>

运行 dotnet build 命令来查看生成的代码。 输出包含一个 obj/Debug/net8.0/generated/ 目录,其中包含为项目生成的所有文件。

dotnet publish 命令还会编译源文件并生成已编译的文件。 此外,dotnet publish 将生成的程序集传递给本机 IL 编译器。 IL 编译器生成本机可执行文件。 本机可执行文件包含本机计算机代码。

库和本机 AOT

目前,在 ASP.NET Core 项目中使用的许多常用库在针对本机 AOT 的项目中使用时存在一些兼容性问题,例如:

  • 使用反射来检查和发现类型。
  • 在运行时有条件地加载库。
  • 动态生成代码以实现功能。

需要更新使用这些动态功能的库,才能与本机 AOT 配合使用。 可以使用 Roslyn 源生成器等工具更新它们。

建议希望支持本机 AOT 的库作者执行以下操作:

最小 API 和 JSON 有效负载

“最小 API”框架已针对使用 System.Text.Json 接收和返回 JSON 有效负载进行了优化。 System.Text.Json

“最小 API”应用中作为 HTTP 正文的一部分传输或者从请求委托返回的所有参数都必须在通过 ASP.NET Core 的依赖项注入注册的 JsonSerializerContext 上进行配置:

using System.Text.Json.Serialization;
using MyFirstAotWebApi;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.AddConsole();

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
    sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
        ? Results.Ok(todo)
        : Results.NotFound());

app.Run();

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}

在上述突出显示的代码中:

委托上未绑定到正文且不需要具有可序列化的参数。 例如,一个查询字符串参数,它是一个丰富的对象类型并实现 IParsable<T>

public class Todo
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public DateOnly? DueBy { get; set; }
    public bool IsComplete { get; set; }
}

static class TodoGenerator
{
    private static readonly (string[] Prefixes, string[] Suffixes)[] _parts = new[]
        {
            (new[] { "Walk the", "Feed the" }, new[] { "dog", "cat", "goat" }),
            (new[] { "Do the", "Put away the" }, new[] { "groceries", "dishes", "laundry" }),
            (new[] { "Clean the" }, new[] { "bathroom", "pool", "blinds", "car" })
        };
    // Remaining code omitted for brevity.

已知问题

请参阅此 GitHub 问题,以报告或查看有关 ASP.NET Core 中本机 AOT 支持的问题。

请参阅