ネイティブ AOT の ASP.NET Core サポート

ASP.NET Core 8.0 では、.NET ネイティブ Ahead-Of-Time (AOT) のサポートが導入されています。

ASP.NET Core でネイティブ AOT を使う理由

ネイティブ AOT アプリを発行およびデプロイすることには、次の利点があります。

  • ディスク占有領域を最小限に抑える: ネイティブ AOT を使って発行すると、プログラムをサポートするために必要な外部依存関係のコードのみを含む 1 つの実行可能ファイルが生成されます。 実行可能ファイルのサイズ削減は、次につながります。
    • より小さいコンテナー イメージ (たとえば、コンテナー化されたデプロイのシナリオで)。
    • より小さいイメージからデプロイ時間を短縮する。
  • 起動時間の短縮: ネイティブ AOT アプリケーションでは、起動時間が短縮される可能性があります。つまり、次のことが言えます。
    • より迅速にアプリが要求を処理する準備が整う。
    • アプリのあるバージョンから別のバージョンへの移行をコンテナー オーケストレーターで管理する必要がある場合、デプロイが改善される。
  • 必要なメモリの削減: ネイティブ AOT アプリでは、アプリによって実行される作業に応じて、必要なメモリを減らすことができます。 メモリ消費量が減ると、デプロイの密度が高くなり、スケーラビリティが向上する可能性があります。

テンプレート アプリは、AOT 公開アプリ、トリミングされたランタイム アプリ、トリミングされていないランタイム アプリのパフォーマンスを比較するために、Microsoft のベンチマーク ラボで実行されました。 次のグラフはベンチマークの結果を示しています。

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 完全にサポート
Session サポートされていません
スパ サポートされていません
StaticFiles 完全にサポート
WebSocket 完全にサポート

制限事項の詳細については、以下を参照してください:

ネイティブ 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 では、ネイティブ AOT は以下の ASP.NET Core アプリの種類でサポートされています。

Web API (ネイティブ AOT) テンプレート

ASP.NET Core Web API (ネイティブ AOT) テンプレート (短縮名 webapiaot) を使用して、AOT が有効なプロジェクトを作成します。 このテンプレートは、Web API プロジェクト テンプレートと次の点で異なります。

  • MVC はネイティブ AOT とまだ互換性がないため、最小限の API のみを使います。
  • CreateSlimBuilder() API を使って必要な機能のみを既定で有効にし、アプリのデプロイ サイズを最小限に抑えます。
  • クラウドネイティブ デプロイでは HTTPS トラフィックはイングレス サービスで処理されるのが一般的なので、HTTP のみをリッスンするように構成されています。
  • IIS または IIS Express で実行するための起動プロファイルは含まれません。
  • アプリのエンドポイントに送信できる、サンプル HTTP 要求を使って構成された .http ファイルを作成します。
  • 天気予報のサンプルではなく、サンプル Todo API が含まれています。
  • PublishAotこの記事で前述したように、プロジェクト ファイルに を追加します。
  • JSON シリアライザー ソース ジェネレーターを有効にします。 ソース ジェネレーターは、ネイティブ AOT コンパイルに必要なシリアル化コードをビルド時に生成するために使われます。

ソース生成をサポートするための変更

次の例は、JSON シリアル化ソース生成をサポートするために Program.cs ファイルに追加するコードを示しています。

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 メソッド

このテンプレートには、CreateBuilder() メソッドではなく CreateSlimBuilder() メソッドが使われています。

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 終端プロキシの背後で実行されるアプリには必要ありません。 たとえば、「Application Gateway での TLS 終端とエンド ツー エンド TLS の概要」を参照してください。 HTTPS を有効にするには、builder.WebHost.UseKestrelHttpsConfiguration を呼び出します。HTTP/3 を有効にするには、builder.WebHost.UseQuic を呼び出します。

CreateSlimBuilderCreateBuilder

CreateBuilder メソッドでサポートされている次の機能は、CreateSlimBuilder メソッドではサポートされていません。

CreateSlimBuilder メソッドには、効率的な開発エクスペリエンスに必要な以下の機能が含まれています。

  • appsettings.jsonappsettings.{EnvironmentName}.json の JSON ファイルの構成。
  • ユーザー シークレットの構成。
  • コンソールのログ。
  • ログの構成。

上述した機能を省略するビルダーについては、CreateEmptyBuilder メソッドを参照してください。

最小限の機能を備えているので、AOT だけでなくトリミングの点でもベネフィットがあります。 詳細については、「自己完結型の展開と実行可能ファイルのトリミング」を参照してください。

詳細については、「WebApplication.CreateBuilderCreateSlimBuilder を比較する」を参照してください

ソース ジェネレーター

使われないコードはネイティブ AOT の発行中にトリミングされるため、アプリで実行時に無制限のリフレクションを使うことはできません。 ソース ジェネレーターは、リフレクションの必要性を回避するコードを生成するために使われます。 場合によっては、ジェネレーターが必須ではないときでも、ソース ジェネレーターによって AOT 用に最適化されたコードが生成されます。

生成されたソース コードを表示するには、次の例に示すように、アプリの .csproj ファイルに EmitCompilerGeneratedFiles プロパティを追加します。

<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.

既知の問題

ASP.NET Core のネイティブ AOT サポートに関する問題の報告またはレビューについては、この GitHub issue を参照してください。

関連項目