Nguon: Microsoft Learn · .NET 8.0

Có gì mới trong ASP.NET Core ở .NET 9

Nguồn: What's new in ASP.NET Core in .NET 9

Bài viết này nêu bật những thay đổi quan trọng nhất trong ASP.NET Core ở .NET 9 kèm theo các liên kết tới tài liệu liên quan.

Tối ưu hóa phân phối tài nguyên tĩnh (Static asset delivery optimization)

MapStaticAssets routing endpoint conventions là tính năng mới tối ưu hóa việc phân phối tài nguyên tĩnh (static assets) trong các ứng dụng ASP.NET Core.

Việc áp dụng các phương pháp production tốt nhất để phục vụ tài nguyên tĩnh đòi hỏi nhiều công sức và chuyên môn kỹ thuật. Nếu không có các tối ưu hóa như nén (compression), bộ nhớ đệm (caching), và fingerprints:

Tạo các ứng dụng web hiệu suất cao đòi hỏi phải tối ưu hóa việc phân phối tài nguyên đến trình duyệt. Các tối ưu hóa có thể bao gồm:

MapStaticAssets là tính năng mới tối ưu hóa việc phân phối tài nguyên tĩnh trong ứng dụng. Nó được thiết kế để hoạt động với tất cả các UI framework (khung giao diện người dùng), bao gồm Blazor, Razor Pages và MVC. Thông thường đây là sự thay thế tương đương cho UseStaticFiles:

diff
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

+app.MapStaticAssets();
-app.UseStaticFiles();
app.MapRazorPages();

app.Run();

MapStaticAssets hoạt động bằng cách kết hợp các quy trình tại thời điểm build và publish để thu thập thông tin về tất cả các tài nguyên tĩnh trong ứng dụng. Thông tin này sau đó được thư viện runtime sử dụng để phục vụ các tệp này một cách hiệu quả cho trình duyệt.

MapStaticAssets có thể thay thế UseStaticFiles trong hầu hết các trường hợp, tuy nhiên, nó được tối ưu hóa cho việc phục vụ các tài nguyên mà ứng dụng đã biết tại thời điểm build và publish. Nếu ứng dụng phục vụ tài nguyên từ các vị trí khác, chẳng hạn như đĩa hoặc embedded resources (tài nguyên nhúng), nên sử dụng UseStaticFiles.

MapStaticAssets cung cấp các lợi ích sau mà UseStaticFiles không có:

Bảng sau đây cho thấy kích thước gốc và sau khi nén của các tệp CSS và JS trong template Razor Pages mặc định:

TệpGốcSau nén% Giảm
bootstrap.min.css16317.589.26%
jquery.js89.62868.75%
bootstrap.min.js78.52074.52%
Tổng331.165.580.20%

Bảng sau đây cho thấy kích thước gốc và sau khi nén khi sử dụng thư viện Fluent UI Blazor components:

TệpGốcSau nén% Giảm
fluent.js3847380.99%
fluent.css941188.30%
Tổng4788482.43%

Tổng cộng từ 478 KB chưa nén xuống còn 84 KB sau nén.

Tối ưu hóa diễn ra tự động khi sử dụng MapStaticAssets. Khi một thư viện được thêm vào hoặc cập nhật, chẳng hạn với JavaScript hoặc CSS mới, các tài nguyên được tối ưu hóa như một phần của quá trình build. Tối ưu hóa đặc biệt có lợi cho các môi trường di động có thể có băng thông thấp hơn hoặc kết nối không ổn định.

Bật nén động trên server so với sử dụng MapStaticAssets

MapStaticAssets có những ưu điểm sau so với nén động trên server:

Blazor

Phần này mô tả các tính năng mới cho Blazor.

Template giải pháp .NET MAUI Blazor Hybrid và Web App

Một template giải pháp mới giúp dễ dàng tạo các ứng dụng client .NET MAUI native và Blazor web dùng chung UI. Template này cho thấy cách tạo các ứng dụng client tối đa hóa việc tái sử dụng code và hướng tới Android, iOS, Mac, Windows và Web.

Các tính năng chính của template này bao gồm:

Để bắt đầu, hãy cài đặt .NET 9 SDK và cài đặt .NET MAUI workload, chứa template:

dotnetcli
dotnet workload install maui

Tạo giải pháp từ template dự án trong command shell bằng lệnh sau:

dotnetcli
dotnet new maui-blazor-web

Template cũng có sẵn trong Visual Studio.

Phát hiện vị trí render, tính tương tác và chế độ render được chỉ định trong thời gian chạy

Chúng tôi đã giới thiệu một API mới được thiết kế để đơn giản hóa quá trình truy vấn trạng thái component trong thời gian chạy. API này cung cấp các khả năng sau:

Cải thiện trải nghiệm kết nối lại phía server:

Những cải tiến sau đã được thực hiện cho trải nghiệm kết nối lại phía server mặc định:

javascript
Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
        previousAttempts >= maxRetries ? null : previousAttempts * 1000
    },
  },
});

Đơn giản hóa tuần tự hóa trạng thái xác thực cho Blazor Web Apps

Các API mới giúp dễ dàng hơn khi thêm xác thực vào Blazor Web App hiện có. Khi bạn tạo Blazor Web App mới với xác thực sử dụng Individual Accounts (tài khoản cá nhân) và bạn bật khả năng tương tác dựa trên WebAssembly, dự án bao gồm một AuthenticationStateProvider tùy chỉnh trong cả dự án server và client.

Bây giờ có các API, là một phần của template dự án Blazor Web App, có thể được gọi trong các dự án server và client để thêm chức năng này:

Thêm các trang static server-side rendering (SSR) vào Blazor Web App tương tác toàn cục

Với .NET 9, giờ đây đơn giản hơn để thêm các trang static SSR vào các ứng dụng áp dụng tính tương tác toàn cục.

Đánh dấu bất kỳ trang Razor component nào với thuộc tính [ExcludeFromInteractiveRouting] được gán bằng chỉ thị @attribute của Razor:

razor
@attribute [ExcludeFromInteractiveRouting]

Constructor injection (tiêm thông qua constructor)

Razor component hỗ trợ constructor injection.

csharp
public partial class ConstructorInjection(NavigationManager navigation)
{
    private void HandleClick()
    {
        navigation.NavigateTo("/counter");
    }
}

Nén WebSocket cho các Interactive Server component

Theo mặc định, Interactive Server component bật nén cho WebSocket connections và đặt chỉ thị frame-ancestors Content Security Policy (CSP - Chính sách bảo mật nội dung) thành 'self'.

Có thể tắt nén bằng cách đặt ConfigureWebSocketOptions thành null:

csharp
.AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

Xử lý sự kiện keyboard composition trong Blazor

Thuộc tính KeyboardEventArgs.IsComposing mới cho biết liệu sự kiện bàn phím có phải là một phần của phiên composition hay không.

Thêm tham số OverscanCount vào QuickGrid

Component QuickGrid hiện hiển thị thuộc tính OverscanCount cho biết có bao nhiêu hàng bổ sung được render trước và sau vùng hiển thị khi bật ảo hóa (virtualization).

razor
<QuickGrid ItemsProvider="itemsProvider" Virtualize="true" OverscanCount="4">
    ...
</QuickGrid>

Component InputNumber hỗ trợ thuộc tính type="range"

Component InputNumber&lt;TValue&gt; hiện hỗ trợ thuộc tính type="range", tạo ra một range input hỗ trợ model binding và form validation.

Các sự kiện enhanced navigation mới

Kích hoạt callback JavaScript trước hoặc sau enhanced navigation với các event listener mới:

SignalR

Phần này mô tả các tính năng mới cho SignalR.

Hỗ trợ kiểu đa hình (Polymorphic type) trong SignalR Hubs

Các phương thức Hub giờ đây có thể chấp nhận lớp cơ sở thay vì lớp dẫn xuất để cho phép các kịch bản đa hình. Kiểu cơ sở cần được chú thích để cho phép đa hình.

csharp
public class MyHub : Hub
{
    public void Method(JsonPerson person)
    {
        if (person is JsonPersonExtended)
        {
        }
        else if (person is JsonPersonExtended2)
        {
        }
        else
        {
        }
    }
}

[JsonPolymorphic]
[JsonDerivedType(typeof(JsonPersonExtended), nameof(JsonPersonExtended))]
[JsonDerivedType(typeof(JsonPersonExtended2), nameof(JsonPersonExtended2))]
private class JsonPerson
{
    public string Name { get; set; }
    public Person Child { get; set; }
    public Person Parent { get; set; }
}

private class JsonPersonExtended : JsonPerson
{
    public int Age { get; set; }
}

private class JsonPersonExtended2 : JsonPerson
{
    public string Location { get; set; }
}

Cải tiến Activities cho SignalR

SignalR giờ đây có ActivitySource cho cả hub server và client.

.NET SignalR server ActivitySource

ActivitySource SignalR có tên Microsoft.AspNetCore.SignalR.Server phát ra các sự kiện cho các lời gọi phương thức hub.

.NET SignalR client ActivitySource

ActivitySource SignalR có tên Microsoft.AspNetCore.SignalR.Client phát ra các sự kiện cho SignalR client.

Hub invocations trên client và server hỗ trợ context propagation. Việc truyền trace context cho phép distributed tracing (theo dõi phân tán) thực sự.

SignalR hỗ trợ trimming và Native AOT

Tiếp tục hành trình Native AOT bắt đầu từ .NET 8, chúng tôi đã bật hỗ trợ trimming (cắt tỉa) và native ahead-of-time (AOT) compilation cho cả các kịch bản SignalR client và server.

csharp
using Microsoft.AspNetCore.SignalR;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.AddSignalR();
builder.Services.Configure<JsonHubProtocolOptions>(o =>
{
    o.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapHub<ChatHub>("/chatHub");
// ...

[JsonSerializable(typeof(string))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Giới hạn

Minimal APIs

Phần này mô tả các tính năng mới cho Minimal APIs.

Thêm InternalServerErrorInternalServerError<TValue> vào TypedResults

Lớp TypedResults hiện bao gồm các factory method và kiểu để trả về phản hồi "500 Internal Server Error":

csharp
var app = WebApplication.Create();

app.MapGet("/", () => TypedResults.InternalServerError("Something went wrong!"));

app.Run();

Gọi ProducesProblemProducesValidationProblem trên các route group

csharp
var app = WebApplication.Create();

var todos = app.MapGroup("/todos")
    .ProducesProblem();

todos.MapGet("/", () => new Todo(1, "Create sample app", false));
todos.MapPost("/", (Todo todo) => Results.Ok(todo));

app.Run();

record Todo(int Id, string Title, boolean IsCompleted);

Kiểu kết quả ProblemValidationProblem hỗ trợ khởi tạo với giá trị IEnumerable<KeyValuePair<string, object?>>

csharp
var app = WebApplication.Create();

app.MapGet("/", () =>
{
    var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
    return TypedResults.Problem("This is an error with extensions",
                                                   extensions: extensions);
});

OpenAPI

Phần này mô tả các tính năng mới cho OpenAPI.

Hỗ trợ tích hợp để tạo tài liệu OpenAPI

Đặc tả OpenAPI là một tiêu chuẩn để mô tả các HTTP API. Trong .NET 9, ASP.NET Core cung cấp hỗ trợ tích hợp để tạo tài liệu OpenAPI thông qua gói Microsoft.AspNetCore.OpenApi.

csharp
var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/hello/{name}", (string name) => $"Hello {name}"!);

app.Run();

Microsoft.AspNetCore.OpenApi hỗ trợ trimming và Native AOT

OpenAPI trong ASP.NET Core hỗ trợ trimming và native AOT.

Xác thực và phân quyền (Authentication and authorization)

Phần này mô tả các tính năng mới cho xác thực và phân quyền.

OpenIdConnectHandler thêm hỗ trợ Pushed Authorization Requests (PAR)

Pushed Authorization Requests (PAR) là một tiêu chuẩn OAuth tương đối mới cải thiện bảo mật của các luồng OAuth và OIDC bằng cách chuyển các tham số ủy quyền từ front channel sang back channel.

Đối với .NET 9, PAR được bật theo mặc định nếu tài liệu discovery của identity provider quảng cáo hỗ trợ PAR. Nếu điều này gây ra sự cố, bạn có thể tắt PAR qua OpenIdConnectOptions.PushedAuthorizationBehavior:

csharp
builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", oidcOptions =>
    {
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });

Tùy chỉnh tham số OIDC và OAuth

Các handler xác thực OAuth và OIDC giờ đây có tùy chọn AdditionalAuthorizationParameters để dễ dàng tùy chỉnh các tham số tin nhắn ủy quyền:

csharp
builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.AdditionalAuthorizationParameters.Add("prompt", "login");
    options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});

Cấu hình cờ xác thực mở rộng HTTP.sys

Bây giờ bạn có thể cấu hình các cờ HTTP.sys HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHINGHTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL bằng cách sử dụng các thuộc tính EnableKerberosCredentialCachingCaptureCredentials mới:

csharp
webBuilder.UseHttpSys(options =>
{
    options.Authentication.Schemes = AuthenticationSchemes.Negotiate;
    options.Authentication.EnableKerberosCredentialCaching = true;
    options.Authentication.CaptureCredentials = true;
});

Các tính năng khác

Thư viện HybridCache mới

HybridCache hiện vẫn đang trong preview nhưng sẽ được phát hành đầy đủ sau .NET 9 trong một bản phát hành minor tương lai của .NET Extensions.

API HybridCache lấp đầy một số khoảng trống trong các API IDistributedCacheIMemoryCache hiện có. Nó cũng thêm các khả năng mới, chẳng hạn như:

csharp
public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync
        (string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}",
            async cancel => await SomeExpensiveOperationAsync(name, id, cancel),
            token: token
        );
    }
}

Cải tiến trang developer exception (trang ngoại lệ nhà phát triển)

Trang developer exception của ASP.NET Core được hiển thị khi ứng dụng ném ra ngoại lệ chưa được xử lý trong quá trình phát triển.

Preview 3 đã thêm endpoint metadata vào trang developer exception. Các cải tiến nhỏ về chất lượng cuộc sống được phát hiện trong Preview 4:

Cải tiến gỡ lỗi Dictionary

Màn hình gỡ lỗi của dictionary (từ điển) và các collection key-value khác có bố cục được cải thiện. Key được hiển thị trong cột key của debugger thay vì được nối với value.

Sửa lỗi 503 trong quá trình recycle ứng dụng trong IIS

Theo mặc định giờ có độ trễ 1 giây giữa khi IIS được thông báo về recycle hoặc shutdown và khi ANCM yêu cầu managed server bắt đầu shutdown.

xml
<aspNetCore processPath="dotnet" arguments="myapp.dll" stdoutLogEnabled="false" stdoutLogFile=".logsstdout">
  <handlerSettings>
    <handlerSetting name="shutdownDelay" value="5000" />
  </handlerSettings>
</aspNetCore>

ASP0026: Analyzer cảnh báo khi [Authorize] bị ghi đè bởi [AllowAnonymous] từ "xa hơn"

Trong .NET 9 Preview 6, chúng tôi đã giới thiệu một analyzer sẽ đánh dấu các trường hợp trong đó một thuộc tính [Authorize] gần hơn bị ghi đè bởi thuộc tính [AllowAnonymous] xa hơn từ một MVC action.

code
ASP0026 [Authorize] overridden by [AllowAnonymous] from farther away

Cải thiện Kestrel connection metrics

Chúng tôi đã thực hiện cải tiến đáng kể cho connection metrics của Kestrel bằng cách bao gồm metadata về lý do kết nối thất bại. Metric kestrel.connection.duration giờ đây bao gồm lý do đóng kết nối trong thuộc tính error.type.

Tùy chỉnh Kestrel named pipe endpoints

Hỗ trợ named pipe (đường ống có tên) của Kestrel đã được cải thiện với các tùy chọn tùy chỉnh nâng cao. Phương thức CreateNamedPipeServerStream mới trên named pipe options cho phép pipe được tùy chỉnh theo từng endpoint.

Tùy chọn ExceptionHandlerMiddleware để chọn status code dựa trên kiểu ngoại lệ

csharp
app.UseExceptionHandler(new ExceptionHandlerOptions
{
    StatusCodeSelector = ex => ex is TimeoutException
        ? StatusCodes.Status503ServiceUnavailable
        : StatusCodes.Status500InternalServerError,
});

Opt-out khỏi HTTP metrics trên các endpoint và yêu cầu nhất định

.NET 9 giới thiệu khả năng opt-out (không tham gia) khỏi HTTP metrics cho các endpoint và yêu cầu cụ thể:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz").DisableHttpMetrics();
app.Run();

Data Protection hỗ trợ xóa key (khóa)

Trước .NET 9, data protection keys (khóa bảo vệ dữ liệu) không thể xóa theo thiết kế, để ngăn mất dữ liệu. Trong .NET 9, chúng tôi đã giới thiệu tùy chọn để xóa keys.

csharp
if (keyManager is IDeletableKeyManager deletableKeyManager)
{
    var utcNow = DateTimeOffset.UtcNow;
    var yearAgo = utcNow.AddYears(-1);

    if (!deletableKeyManager.DeleteKeys(key => key.ExpirationDate < yearAgo))
    {
        Console.WriteLine("Failed to delete keys.");
    }
    else
    {
        Console.WriteLine("Old keys deleted successfully.");
    }
}

Middleware hỗ trợ Keyed DI

Middleware giờ đây hỗ trợ Keyed DI trong cả constructor và phương thức Invoke/InvokeAsync:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");

var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("test")] MySingletonClass service)
    {
        _next = next;
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("test2")]
            MyScopedClass scopedService) => _next(context);
}

Tin tưởng chứng chỉ HTTPS phát triển ASP.NET Core trên Linux

Trên các distro Linux dựa trên Ubuntu và Fedora, dotnet dev-certs https --trust giờ đây cấu hình chứng chỉ HTTPS phát triển ASP.NET Core là chứng chỉ đáng tin cậy cho:

Templates được cập nhật lên Bootstrap, jQuery và jQuery Validation phiên bản mới nhất

Các template dự án ASP.NET Core và thư viện đã được cập nhật để sử dụng các phiên bản mới nhất:

Breaking changes (Thay đổi vi phạm)

Sử dụng các bài viết trong Breaking changes in .NET để tìm các thay đổi vi phạm có thể áp dụng khi nâng cấp ứng dụng lên phiên bản .NET mới hơn.