Hỗ trợ Native AOT trong ASP.NET Core
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:
- Dung lượng đĩa tối thiểu: Khi publish bằng Native AOT, một file thực thi duy nhất được tạo ra, chỉ chứa code từ các dependency bên ngoài cần thiết cho chương trình. Kích thước file thực thi nhỏ hơn dẫn đến:
- Container image (hình ảnh container) nhỏ hơn, ví dụ trong các kịch bản triển khai containerized.
- Thời gian triển khai ngắn hơn nhờ image nhỏ hơn.
- Thời gian khởi động giảm: Ứng dụng Native AOT có thể có thời gian khởi động ngắn hơn, điều này có nghĩa là:
- Ứng dụng sẵn sàng xử lý request nhanh hơn.
- Triển khai được cải thiện khi container orchestrators (bộ điều phối container) cần quản lý việc chuyển đổi từ phiên bản ứng dụng này sang phiên bản khác.
- Nhu cầu bộ nhớ giảm: Ứng dụng Native AOT có thể có nhu cầu bộ nhớ thấp hơn, tùy thuộc vào công việc mà ứng dụng thực hiện. Giảm tiêu thụ bộ nhớ có thể dẫn đến mật độ triển khai cao hơn và khả năng mở rộng tốt hơn.
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ăng | Hỗ trợ đầy đủ | Hỗ trợ một phần | Khô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:
- Limitations of Native AOT deployment
- Introduction to AOT warnings
- Known trimming incompatibilities
- Introduction to trim warnings
- GitHub issue dotnet/core #8288
Đ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):
<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:
- Các tính năng không tương thích với Native AOT bị vô hiệu hóa và ném exception (ngoại lệ) tại runtime (thời gian chạy).
- Source analyzer (bộ phân tích source) được kích hoạt để làm nổi bật code không tương thích với Native AOT. Tại thời điểm publish, toàn bộ ứng dụng, bao gồm các package NuGet, được phân tích lại về tính tương thích.
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:
- Minimal APIs - Xem thêm phần Template Web API (Native AOT) bên dưới.
- gRPC - Xem thêm gRPC and Native AOT.
- Worker services (Dịch vụ nền) - Xem thêm AOT in Worker Service templates.
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:
- Chỉ sử dụng Minimal APIs vì MVC chưa tương thích với Native AOT.
- Sử dụng API CreateSlimBuilder() để đảm bảo chỉ các tính năng thiết yếu được kích hoạt theo mặc định, giảm thiểu kích thước triển khai của ứng dụng.
- Được cấu hình để chỉ lắng nghe trên HTTP, vì lưu lượng HTTPS thường được xử lý bởi một ingress service (dịch vụ vào) trong các triển khai cloud-native.
- Không bao gồm profile khởi chạy để chạy dưới IIS hoặc IIS Express.
- Tạo một file
.httpđược cấu hình với các HTTP request mẫu có thể được gửi đến các endpoint của ứng dụng. - Bao gồm một Todo API mẫu thay vì mẫu dự báo thời tiết.
- Thêm
PublishAotvào project file, như đã trình bày trước đó. - Kích hoạt JSON serializer source generators. Source generator (bộ tạo source) được dùng để tạo code serialization tại thời điểm build, điều này bắt buộc để biên dịch Native AOT.
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:
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:
{
"$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().
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 CreateSlimBuilder và CreateBuilder
Phương thức CreateSlimBuilder không hỗ trợ các tính năng sau mà phương thức CreateBuilder hỗ trợ:
- Hosting startup assemblies
- UseStartup
- Các logging provider (nhà cung cấp ghi log) sau:
- Windows EventLog
- Debug
- Event Source
- Các tính năng web hosting:
- UseStaticWebAssets
- IIS Integration
- Cấu hình Kestrel:
- HTTPS endpoints trong Kestrel
- Quic (HTTP/3)
- Ràng buộc Regex và alpha được dùng trong routing
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ả:
- Cấu hình JSON file cho
appsettings.jsonvàappsettings.{EnvironmentName}.json. - Cấu hình user secrets (bí mật người dùng).
- Console logging (ghi log console).
- Cấu hình logging.
Để 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:
<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ư:
- Sử dụng reflection để kiểm tra và khám phá các type
- Tải thư viện có điều kiện tại runtime
- Tạo code on the fly (ngay lúc đó) để thực hiện chức năng
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 ra các yêu cầu tương thích cho JSON và Native AOT.
- Yêu cầu sử dụng
System.Text.Jsonsource generator.
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:
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:
- JSON serializer context được đăng ký với DI container. Để biết thêm thông tin, xem:
- Combine source generators
- TypeInfoResolverChain
JsonSerializerContexttùy chỉnh được chú thích với thuộc tính[JsonSerializable]để kích hoạt code serializer JSON được tạo bởi source generator cho typeToDo.
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>.
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.