Nguon: Microsoft Learn · .NET 8.0

Những điểm mới trong ASP.NET Core trong .NET 10

Nguồn: What's new in ASP.NET Core in .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:

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:

razor
<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:

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

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:

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:

csharp
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:

razor
<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:

xml
<WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>

Hoặc đặt biến môi trường: DOTNET_WASM_ENABLE_STREAMING_RESPONSE=false

Để tắt cho từng request:

csharp
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:

html
<head>
    ...
    <script type="importmap"></script>
</head>

<body>
    ...
    <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>

Trong file dự án:

xml
<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-EnvironmentlaunchSettings.json không còn được sử dụng. Đặt environment bằng:

xml
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>

Môi trường mặc định:

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:

razor
@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 IJSRuntimeIJSObjectReference:

csharp
// 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 IJSInProcessRuntimeIJSInProcessObjectReference:

csharp
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:

razor
<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:

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:

  1. Gọi AddValidation trong file Program:
csharp
builder.Services.AddValidation();
  1. Khai báo các kiểu form model trong file C# class (không trong file .razor)
  2. Chú thích kiểu form model gốc với [ValidatableType]

Ví dụ:

Order.cs:

csharp
[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:

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:

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 WasmEnableHotReloadtrue mặc định cho cấu hình Debug.

Để tắt Hot Reload cho cấu hình Debug:

xml
<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:

csharp
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:

csharp
builder.Services.AddValidation();

Tắt xác thực cho các endpoint cụ thể:

csharp
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):

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

csharp
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:

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:

csharp
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":

csharp
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:

csharp
[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:

xml
<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:

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

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:

csharp
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:

Chứng chỉ phát triển:

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:

code
$ 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 IMemoryPoolFactoryMemoryPoolFactory tích hợp sẵn qua dependency injection (tiêm phụ thuộc):

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