Những điểm mới trong ASP.NET Core trong .NET 10
Bài viết này nêu bật những thay đổi quan trọng nhất trong ASP.NET Core trong .NET 10 kèm theo các liên kết đến tài liệu liên quan.
Blazor
Mẫu bảo mật Blazor Web App mới và cập nhật
Mẫu được cập nhật cho:
- Bảo mật ASP.NET Core Blazor Web App với OpenID Connect (OIDC)
- Bảo mật ASP.NET Core Blazor Web App với Microsoft Entra ID
- Bảo mật ASP.NET Core Blazor Web App với Windows Authentication
Tất cả các giải pháp mẫu OIDC và Entra giờ đây bao gồm một dự án web API riêng biệt (MinimalApiJwt) để minh họa cấu hình và gọi web API bên ngoài an toàn.
Tham số RowClass của QuickGrid
Áp dụng stylesheet class (lớp kiểu) cho các hàng lưới dựa trên mục hàng:
<QuickGrid ... RowClass="GetRowCssClass">
...
</QuickGrid>
@code {
private string GetRowCssClass(MyGridItem item) =>
item.IsArchived ? "row-archived" : null;
}Blazor script như static web asset (tài sản web tĩnh)
Blazor script giờ đây được phục vụ như static web asset với nén và fingerprinting (tạo dấu vân tay) tự động thay vì từ embedded resource (tài nguyên nhúng).
Nếu ứng dụng yêu cầu Blazor script nhưng không chứa component, thêm vào file dự án:
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
Route template highlights (làm nổi bật)
Thuộc tính [Route] giờ đây hỗ trợ route syntax highlighting (tô sáng cú pháp route) giúp trực quan hóa cấu trúc route template.
NavigateTo không còn cuộn lên đầu trang cho same-page navigations
NavigationManager.NavigateTo không còn cuộn lên đầu khi điều hướng đến cùng trang, giữ nguyên viewport khi cập nhật địa chỉ.
Thêm component ReconnectionUI vào Blazor Web App project template
Template giờ đây bao gồm component ReconnectModal với các tính năng reconnection UI mới:
- Sự kiện
components-reconnect-state-changedmới cho các thay đổi trạng thái kết nối lại - Trạng thái kết nối lại mới "
retrying" để phân biệt rõ hơn các giai đoạn kết nối lại
Bỏ qua query string và fragment khi dùng NavLinkMatch.All
Component NavLink giờ đây bỏ qua query string (chuỗi truy vấn) và fragment (đoạn) khi dùng NavLinkMatch.All, giữ lại class active nếu URL path khớp.
Ghi đè phương thức ShouldMatch để tùy chỉnh logic khớp:
public class CustomNavLink : NavLink
{
protected override bool ShouldMatch(string currentUriAbsolute)
{
// Logic khớp tùy chỉnh
}
}Đóng tùy chọn cột QuickGrid
Đóng column options UI bằng phương thức HideColumnOptionsAsync mới:
<QuickGrid @ref="movieGrid" Items="movies">
<PropertyColumn Property="@(m => m.Title)" Title="Title">
<ColumnOptions>
<input type="search" @bind="titleFilter" placeholder="Filter by title"
@bind:after="@(() => movieGrid.HideColumnOptionsAsync())" />
</ColumnOptions>
</PropertyColumn>
...
</QuickGrid>
@code {
private QuickGrid<Movie>? movieGrid;
private string titleFilter = string.Empty;
...
}Response streaming (truyền phát phản hồi) được bật mặc định cho HttpClient
Response streaming giờ đây được bật mặc định cho các yêu cầu HttpClient. Đây là breaking change (thay đổi đột phá).
HttpContent.ReadAsStreamAsync() giờ đây trả về BrowserHttpReadStream (không còn MemoryStream), không hỗ trợ các hoạt động đồng bộ.
Để tắt toàn cục, thêm vào file dự án:
<WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
Hoặc đặt biến môi trường: DOTNET_WASM_ENABLE_STREAMING_RESPONSE=false
Để tắt cho từng request:
requestMessage.SetBrowserResponseStreamingEnabled(false);
Client-side fingerprinting (tạo dấu vân tay phía client)
Cho standalone Blazor WebAssembly apps, kích hoạt client-side fingerprinting:
Trong wwwroot/index.html:
<head>
...
<script type="importmap"></script>
</head>
<body>
...
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>Trong file dự án:
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>Preloaded Blazor framework static assets (tài sản tĩnh)
Framework static assets được tự động preload (tải trước) bằng Link headers trong Blazor Web Apps.
Đặt environment (môi trường) trong standalone Blazor WebAssembly apps
Header Blazor-Environment và launchSettings.json không còn được sử dụng. Đặt environment bằng:
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
Môi trường mặc định:
Developmentcho build (xây dựng)Productioncho publish (xuất bản)
Boot configuration file được nhúng nội tuyến
Boot configuration (cấu hình khởi động) của Blazor giờ đây được nhúng nội tuyến vào dotnet.js (trước đây trong blazor.boot.json).
Mô hình khai báo để lưu trữ trạng thái từ component và service
Dùng thuộc tính [PersistentState] để tự động lưu trữ trạng thái component và service:
@page "/movies"
@inject IMovieService MovieService
@if (MoviesList == null)
{
<p><em>Loading...</em></p>
}
else
{
<QuickGrid Items="MoviesList.AsQueryable()">
...
</QuickGrid>
}
@code {
[PersistentState]
public List<Movie>? MoviesList { get; set; }
protected override async Task OnInitializedAsync()
{
MoviesList ??= await MovieService.GetMoviesAsync();
}
}Dùng các thuộc tính public cho các hoạt động framework dựa trên reflection (phản chiếu).
Các tính năng JavaScript interop (tương tác) mới
Tạo JS object instance (phiên bản đối tượng) và truy cập JS properties (thuộc tính):
Các phương thức bất đồng bộ trên IJSRuntime và IJSObjectReference:
// Tạo instance với constructor
var classRef = await JSRuntime.InvokeConstructorAsync("jsInterop.TestClass", "Blazor!");
// Đọc giá trị property
var text = await classRef.GetValueAsync<string>("text");
var valueFromDataProperty = await JSRuntime.GetValueAsync<int>("jsInterop.testObject.num");
// Đặt giá trị property
await JSRuntime.SetValueAsync("jsInterop.testObject.num", 30);Các phương thức đồng bộ trên IJSInProcessRuntime và IJSInProcessObjectReference:
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
// Tạo instance
var classRef = inProcRuntime.InvokeConstructor("jsInterop.TestClass", "Blazor!");
// Đọc giá trị property
var text = classRef.GetValue<string>("text");
var valueFromDataProperty = inProcRuntime.GetValue<int>("jsInterop.testObject.num");
// Đặt giá trị property
inProcRuntime.SetValue("jsInterop.testObject.num", 20);Router Blazor có tham số NotFoundPage
Chỉ định trang để render khi điều hướng đến các trang không tồn tại:
<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>Nội dung này bị bỏ qua vì NotFoundPage đã được xác định.</NotFound>
</Router>Phản hồi Not Found bằng NavigationManager cho static SSR và global interactive rendering
Phương thức NavigationManager.NotFound xử lý tài nguyên bị thiếu:
- Static SSR: Đặt HTTP status code (mã trạng thái) thành 404
- Interactive rendering (kết xuất tương tác): Báo hiệu router render nội dung Not Found
- Streaming rendering (kết xuất luồng): Render nội dung Not Found mà không tải lại trang
Cải thiện form validation (xác thực biểu mẫu)
Blazor giờ đây hỗ trợ xác thực các đối tượng lồng nhau và các mục trong collection (bộ sưu tập).
Để dùng xác thực mới:
- Gọi
AddValidationtrong fileProgram:
builder.Services.AddValidation();
- Khai báo các kiểu form model trong file C# class (không trong file
.razor) - Chú thích kiểu form model gốc với
[ValidatableType]
Ví dụ:
Order.cs:
[ValidatableType]
public class Order
{
public Customer Customer { get; set; } = new();
public List<OrderItem> OrderItems { get; set; } = [];
}
public class Customer
{
[Required(ErrorMessage = "Name is required.")]
public string? FullName { get; set; }
[Required(ErrorMessage = "Email is required.")]
public string? Email { get; set; }
public ShippingAddress ShippingAddress { get; set; } = new();
}Tính năng xác thực:
- Hỗ trợ xác thực các đối tượng phức tạp lồng nhau và collection
- Sử dụng triển khai dựa trên source generator (bộ tạo nguồn) để cải thiện hiệu suất và tương thích AOT (Ahead-of-Time compilation - biên dịch trước)
Loại trừ property hoặc kiểu khỏi xác thực bằng thuộc tính [SkipValidation].
Hỗ trợ Web Authentication API (API xác thực web - passkey) cho ASP.NET Core Identity
ASP.NET Core Identity giờ đây hỗ trợ xác thực passkey dựa trên tiêu chuẩn WebAuthn và FIDO2. Người dùng có thể đăng nhập không cần mật khẩu bằng xác thực dựa trên thiết bị (sinh trắc học hoặc security key).
Blazor Web App project template cung cấp passkey management (quản lý passkey) và login sẵn có.
Circuit state persistence (lưu trữ trạng thái circuit)
Trong quá trình server-side rendering, Blazor Web Apps có thể lưu trữ trạng thái phiên người dùng (circuit) khi mất kết nối, cho phép người dùng tiếp tục mà không mất công việc:
- Điều chỉnh tab trình duyệt
- Người dùng thiết bị di động chuyển đổi ứng dụng
- Gián đoạn mạng
- Quản lý tài nguyên chủ động
- Enhanced navigation (điều hướng nâng cao)
Hot Reload cho Blazor WebAssembly và .NET on WebAssembly
SDK đã chuyển sang Hot Reload (tải lại nhanh) đa năng cho các tình huống WebAssembly. Thuộc tính MSBuild WasmEnableHotReload là true mặc định cho cấu hình Debug.
Để tắt Hot Reload cho cấu hình Debug:
<PropertyGroup>
<WasmEnableHotReload>false</WasmEnableHotReload>
</PropertyGroup>Blazor Hybrid
.NET MAUI Blazor Hybrid với Blazor Web App và ASP.NET Core Identity - bài viết và mẫu mới
Bài viết và ứng dụng mẫu mới được thêm vào cho .NET MAUI Blazor Hybrid và Web App sử dụng ASP.NET Core Identity.
Minimal APIs
Xử lý empty string trong form post như null cho nullable value types
Khi dùng thuộc tính [FromForm] với đối tượng phức tạp trong Minimal APIs, các giá trị empty string (chuỗi rỗng) giờ đây được chuyển đổi thành null thay vì gây ra lỗi parse:
using Microsoft.AspNetCore.Http;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/todo", ([FromForm] Todo todo) => TypedResults.Ok(todo));
app.Run();
public class Todo
{
public int Id { get; set; }
public DateOnly? DueDate { get; set; } // Empty strings ánh xạ thành `null`
public string Title { get; set; }
public bool IsCompleted { get; set; }
}Hỗ trợ Validation (xác thực) trong Minimal APIs
Validation giờ đây khả dụng trong Minimal APIs. Kích hoạt bằng AddValidation:
builder.Services.AddValidation();
Tắt xác thực cho các endpoint cụ thể:
app.MapPost("/products",
([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
=> TypedResults.Ok(productId))
.DisableValidation();Xác thực với record types (kiểu record):
public record Product(
[Required] string Name,
[Range(1, 1000)] int Quantity);
app.MapPost("/products", (Product product) =>
{
return TypedResults.Ok(product);
});Hỗ trợ Server-Sent Events (SSE - Sự kiện được gửi từ server)
ASP.NET Core giờ đây hỗ trợ trả về kết quả ServerSentEvents bằng API TypedResults.ServerSentEvents trong cả Minimal APIs và controller-based apps (ứng dụng dựa trên controller).
Server-Sent Events là công nghệ server push cho phép server gửi luồng thông điệp sự kiện đến client qua một kết nối HTTP duy nhất.
app.MapGet("/json-item", (CancellationToken cancellationToken) =>
{
async IAsyncEnumerable<HeartRateRecord> GetHeartRate(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var heartRate = Random.Shared.Next(60, 100);
yield return HeartRateRecord.Create(heartRate);
await Task.Delay(2000, cancellationToken);
}
}
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken),
eventType: "heartRate");
});OpenAPI
Hỗ trợ OpenAPI 3.1
ASP.NET Core thêm hỗ trợ tạo tài liệu OpenAPI phiên bản 3.1 trong .NET 10, với hỗ trợ đầy đủ cho JSON Schema draft 2020-12.
Thay đổi trong tài liệu OpenAPI được tạo:
- Nullable types không còn có thuộc tính
nullable: true - Thay vào đó có từ khóa
typenhư mảng bao gồmnull
Phiên bản OpenAPI mặc định giờ đây là 3.1. Thay đổi bằng cách đặt thuộc tính OpenApiVersion:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});OpenAPI trong YAML
ASP.NET giờ đây hỗ trợ phục vụ tài liệu OpenAPI được tạo ở định dạng YAML, ngắn gọn hơn JSON và hỗ trợ chuỗi nhiều dòng.
Cấu hình trong lệnh gọi MapOpenApi với hậu tố ".yaml" hoặc ".yml":
if (app.Environment.IsDevelopment())
{
app.MapOpenApi("/openapi/{documentName}.yaml");
}Mô tả phản hồi trên ProducesResponseType cho API controllers
ProducesAttribute, ProducesResponseTypeAttribute, và ProducesDefaultResponseTypeAttribute giờ đây chấp nhận tham số chuỗi Description tùy chọn:
[HttpGet(Name = "GetWeatherForecast")]
[ProducesResponseType<IEnumerable<WeatherForecast>>(StatusCodes.Status200OK,
Description = "The weather forecast for the next 5 days.")]
public IEnumerable<WeatherForecast> Get()
{Điền XML doc comments (chú thích tài liệu XML) vào tài liệu OpenAPI
Việc tạo tài liệu OpenAPI của ASP.NET Core giờ đây bao gồm metadata từ XML doc comments trên các định nghĩa method (phương thức), class và member. Kích hoạt bằng cách thêm vào file dự án:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>Lưu ý quan trọng: Quy trình build C# không thu thập XML doc comments trên lambda expressions (biểu thức lambda). Định nghĩa endpoint handler như một method cho Minimal APIs.
Authentication and authorization (Xác thực và ủy quyền)
Metrics (chỉ số) cho xác thực và ủy quyền
Metrics được thêm vào cho các sự kiện xác thực và ủy quyền:
Xác thực:
- Thời gian request đã xác thực
- Số lần challenge (thử thách)
- Số lần forbid (từ chối)
- Số lần sign in (đăng nhập)
- Số lần sign out (đăng xuất)
Ủy quyền:
- Số lượng request yêu cầu ủy quyền
ASP.NET Core Identity metrics
Khả năng quan sát ASP.NET Core Identity được cải thiện với metrics cung cấp các phép đo theo chuỗi thời gian.
Tránh cookie login redirects (chuyển hướng đăng nhập cookie) cho các API endpoint đã biết
Các request chưa xác thực và chưa được ủy quyền đến các API endpoint đã biết được bảo vệ bởi cookie authentication (xác thực cookie) giờ đây trả về phản hồi 401/403 thay vì chuyển hướng đến URI đăng nhập/truy cập bị từ chối.
Miscellaneous (Các tính năng khác)
Cấu hình ngăn chặn exception handler diagnostics
Tùy chọn cấu hình ExceptionHandlerOptions.SuppressDiagnosticsCallback mới kiểm soát đầu ra diagnostic trong exception handler middleware (phần mềm trung gian xử lý ngoại lệ).
Khôi phục về hành vi trước đây:
app.UseExceptionHandler(new ExceptionHandlerOptions
{
SuppressDiagnosticsCallback = context => false;
});Hỗ trợ .localhost Top-Level Domain (tên miền cấp cao nhất)
.localhost TLD (được đặt trước theo RFC2606 và RFC6761) cho mục đích thử nghiệm. Các trình duyệt hiện đại tự động phân giải tên *.localhost thành địa chỉ loopback (127.0.0.1/::1).
ASP.NET Core được cập nhật để hỗ trợ tốt hơn .localhost TLD cho phát triển cục bộ.
Cải tiến Kestrel:
- Coi tên
*.localhostđược đặt qua cấu hình endpoint là địa chỉ loopback cục bộ - Bind (liên kết) đến
127.0.0.1/::1thay vì0.0.0.0/::
Chứng chỉ phát triển:
- Cập nhật
dotnet dev-certs httpsđể xác thực cho domain*.dev.localhost - Sau khi cài đặt .NET 10 SDK Preview 7, chạy
dotnet dev-certs https --trust
Project templates: Template ASP.NET Core Empty (web) và Blazor Web App (blazor) được cập nhật với tùy chọn --localhost-tld mới:
$ dotnet new web -n MyApp --localhost-tld
Tự động xóa khỏi memory pool (vùng bộ nhớ)
Memory pool (vùng bộ nhớ đệm) được dùng bởi Kestrel, IIS và HTTP.sys giờ đây tự động xóa các memory block (khối bộ nhớ) khi ứng dụng nhàn rỗi hoặc tải thấp hơn mà không cần kích hoạt thủ công.
Dùng metrics memory pool: Metrics được thêm vào default memory pool dưới tên "Microsoft.AspNetCore.MemoryPool".
.NET 10 cải thiện trải nghiệm memory pool với IMemoryPoolFactory và MemoryPoolFactory tích hợp sẵn qua dependency injection (tiêm phụ thuộc):
public class MyBackgroundService : BackgroundService
{
private readonly MemoryPool<byte> _memoryPool;
public MyBackgroundService(IMemoryPoolFactory<byte> factory)
{
_memoryPool = factory.Create();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(20, stoppingToken);
var rented = _memoryPool.Rent(100);
rented.Dispose();
}
catch (OperationCanceledException)
{
return;
}
}
}
}Security descriptor (bộ mô tả bảo mật) tùy chỉnh cho HTTP.sys
Thuộc tính RequestQueueSecurityDescriptor mới trên HttpSysOptions cho phép chỉ định security descriptor (bộ mô tả bảo mật) tùy chỉnh cho HTTP.sys request queues (hàng đợi yêu cầu), cho phép kiểm soát truy cập chi tiết.