Nguon: Microsoft Learn · .NET 8.0

Hỗ trợ Native AOT trong ASP.NET Core

Nguồn: ASP.NET Core support for Native AOT

Tác giả: Mitch Denny

.NET 8 giới thiệu hỗ trợ cho .NET native ahead-of-time (AOT).

Tại sao dùng Native AOT với ASP.NET Core

Việc publish và deploy (triển khai) một ứng dụng Native AOT mang lại các lợi ích sau:

Template ứng dụng đã được chạy trong phòng thí nghiệm benchmark (đánh giá hiệu năng) để so sánh hiệu suất của ứng dụng được publish bằng AOT, ứng dụng runtime đã được trimmed (cắt gọn), và ứng dụng runtime chưa được trimmed. Biểu đồ sau đây cho thấy kết quả benchmark:

Native AOT có kích thước ứng dụng, mức sử dụng bộ nhớ và thời gian khởi động thấp hơn.

Tính tương thích của ASP.NET Core với Native AOT

Không phải tất cả các tính năng trong ASP.NET Core đều tương thích với Native AOT. Bảng sau tóm tắt tính tương thích của các tính năng ASP.NET Core với Native AOT:

Tính năngHỗ trợ đầy đủHỗ trợ một phầnKhông hỗ trợ
gRPC✔️ Hỗ trợ đầy đủ
Minimal APIs (API tối giản)✔️ Hỗ trợ một phần
MVC❌ Không hỗ trợ
Blazor Server❌ Không hỗ trợ
SignalR❌ Không hỗ trợ
JWT Authentication (Xác thực JWT)✔️ Hỗ trợ đầy đủ
Other Authentication (Xác thực khác)❌ Không hỗ trợ
CORS✔️ Hỗ trợ đầy đủ
HealthChecks (Kiểm tra sức khỏe)✔️ Hỗ trợ đầy đủ
HttpLogging (Ghi log HTTP)✔️ Hỗ trợ đầy đủ
Localization (Bản địa hóa)✔️ Hỗ trợ đầy đủ
OutputCaching (Cache đầu ra)✔️ Hỗ trợ đầy đủ
RateLimiting (Giới hạn tốc độ)✔️ Hỗ trợ đầy đủ
RequestDecompression (Giải nén request)✔️ Hỗ trợ đầy đủ
ResponseCaching (Cache phản hồi)✔️ Hỗ trợ đầy đủ
ResponseCompression (Nén phản hồi)✔️ Hỗ trợ đầy đủ
Rewrite (Viết lại URL)✔️ Hỗ trợ đầy đủ
Session (Phiên)❌ Không hỗ trợ
Spa❌ Không hỗ trợ
StaticFiles (File tĩnh)✔️ Hỗ trợ đầy đủ
WebSockets✔️ Hỗ trợ đầy đủ

Để biết thêm thông tin về các giới hạn, xem:

Điều quan trọng là phải kiểm tra kỹ lưỡng ứng dụng khi chuyển sang mô hình triển khai Native AOT. Ứng dụng được triển khai bằng AOT phải được kiểm tra để xác minh rằng chức năng không thay đổi so với ứng dụng chưa được trimmed và biên dịch JIT (Just-In-Time). Khi build ứng dụng, hãy xem xét và sửa các cảnh báo AOT. Ứng dụng phát ra cảnh báo AOT trong quá trình publish có thể không hoạt động đúng. Nếu không có cảnh báo AOT nào được phát ra tại thời điểm publish, ứng dụng AOT đã publish sẽ hoạt động giống như ứng dụng chưa được trimmed và biên dịch JIT.

Publish Native AOT

Native AOT được kích hoạt bằng thuộc tính MSBuild PublishAot. Ví dụ sau đây cho thấy cách kích hoạt Native AOT trong project file (file dự án):

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

Cài đặt này kích hoạt biên dịch Native AOT trong quá trình publish và kích hoạt phân tích sử dụng code động trong quá trình build và chỉnh sửa. Project sử dụng publish Native AOT sẽ dùng biên dịch JIT khi chạy cục bộ. Ứng dụng AOT có các điểm khác biệt sau so với ứng dụng biên dịch JIT:

Phân tích Native AOT bao gồm tất cả code của ứng dụng và các thư viện mà ứng dụng phụ thuộc vào. Xem xét các cảnh báo Native AOT và thực hiện các bước khắc phục. Nên publish ứng dụng thường xuyên để phát hiện sớm các vấn đề trong vòng đời phát triển.

Trong .NET 8, Native AOT được hỗ trợ bởi các loại ứng dụng ASP.NET Core sau:

Template Web API (Native AOT)

Template ASP.NET Core Web API (Native AOT) (tên ngắn webapiaot) tạo một project với AOT được kích hoạt. Template này khác với template Web API thông thường ở các điểm sau:

Thay đổi để hỗ trợ source generation (tạo source)

Đoạn code sau đây cho thấy code được thêm vào file Program.cs để hỗ trợ source generation cho JSON serialization:

diff
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
+{
+
+}

Nếu không có code được thêm vào này, System.Text.Json sẽ sử dụng reflection (phản chiếu) để serialize và deserialize JSON. Reflection không được hỗ trợ trong Native AOT.

Để biết thêm thông tin, xem:

Thay đổi trong launchSettings.json

File launchSettings.json được tạo bởi template Web API (Native AOT) đã bỏ phần iisSettings và profile IIS Express:

diff
{
  "$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"
-      }
-    }
  }
}

Phương thức CreateSlimBuilder

Template sử dụng phương thức CreateSlimBuilder() thay vì phương thức CreateBuilder().

csharp
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
{
}

Phương thức CreateSlimBuilder khởi tạo WebApplicationBuilder với các tính năng ASP.NET Core tối thiểu cần thiết để chạy ứng dụng.

Như đã lưu ý trước đó, phương thức CreateSlimBuilder không bao gồm hỗ trợ cho HTTPS hoặc HTTP/3. Các giao thức này thường không cần thiết cho các ứng dụng chạy sau một TLS termination proxy (proxy kết thúc TLS). HTTPS có thể được kích hoạt bằng cách gọi builder.WebHost.UseKestrelHttpsConfiguration; HTTP/3 có thể được kích hoạt bằng cách gọi builder.WebHost.UseQuic.

So sánh CreateSlimBuilderCreateBuilder

Phương thức CreateSlimBuilder không hỗ trợ các tính năng sau mà phương thức CreateBuilder hỗ trợ:

Phương thức CreateSlimBuilder bao gồm các tính năng cần thiết cho trải nghiệm phát triển hiệu quả:

Để biết thêm thông tin chi tiết, xem Comparing WebApplication.CreateBuilder to CreateSlimBuilder.

Source generators (Bộ tạo source)

Vì code không sử dụng bị loại bỏ trong quá trình publish với Native AOT, ứng dụng không thể sử dụng reflection không giới hạn tại runtime. Source generators được dùng để tạo ra code tránh cần dùng reflection. Trong một số trường hợp, source generators tạo ra code được tối ưu hóa cho AOT ngay cả khi generator không được yêu cầu.

Để xem source code được tạo ra, thêm thuộc tính EmitCompilerGeneratedFiles vào file .csproj của ứng dụng, như trong ví dụ sau:

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

  <PropertyGroup>
    <!-- Các thuộc tính khác đã bỏ qua cho ngắn gọn -->
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  </PropertyGroup>

</Project>

Chạy lệnh dotnet build để xem code được tạo ra. Output bao gồm thư mục obj/Debug/net8.0/generated/ chứa tất cả các file được tạo ra cho project.

Lệnh dotnet publish cũng biên dịch các file source và tạo ra các file được biên dịch. Ngoài ra, dotnet publish truyền các assembly được tạo ra đến một native IL compiler (trình biên dịch IL native). IL compiler tạo ra file thực thi native. File thực thi native chứa machine code (mã máy) native.

Sử dụng thư viện với Native AOT

Nhiều thư viện phổ biến được dùng trong các project ASP.NET Core hiện có một số vấn đề tương thích khi được tích hợp vào các project nhắm mục tiêu Native AOT, chẳng hạn như:

Các thư viện sử dụng các tính năng động này cần được cập nhật để hoạt động với Native AOT. Nhiều công cụ có sẵn để áp dụng các cập nhật cần thiết, chẳng hạn như Roslyn source generators.

Các tác giả thư viện muốn hỗ trợ Native AOT được khuyến khích xem xét các bài viết sau:

Minimal APIs và JSON payloads

Framework Minimal API được tối ưu hóa cho việc nhận và trả về các JSON payloads (tải trọng JSON) sử dụng System.Text.Json. System.Text.Json:

Tất cả các type được truyền như một phần của HTTP body hoặc được trả về từ các request delegate trong các ứng dụng Minimal APIs phải được cấu hình trên một JsonSerializerContext được đăng ký thông qua dependency injection (tiêm phụ thuộc) của ASP.NET Core:

csharp
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
{
}

Trong code trên:

Một tham số trên delegate không được ràng buộc với body không cần phải có khả năng serialize. Ví dụ: một tham số query string là một object type phong phú và implement (triển khai) IParsable<T>.

csharp
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" })
        };
    // Code còn lại đã bỏ qua cho ngắn gọn.

Các vấn đề đã biết

Xem GitHub issue này để báo cáo hoặc xem xét các vấn đề với hỗ trợ Native AOT trong ASP.NET Core.