Bagikan melalui


Apa yang baru dalam ASP.NET Core 9.0

Artikel ini menyoroti perubahan paling signifikan dalam ASP.NET Core 9.0 dengan tautan ke dokumentasi yang relevan.

Artikel ini telah diperbarui untuk Pratinjau .NET 9 7.

Blazor

Bagian ini menjelaskan fitur baru untuk Blazor.

.NET MAUIBlazor Hybrid dan templat solusi Aplikasi Web

Templat solusi baru memudahkan untuk membuat .NET MAUI aplikasi klien asli dan Blazor web yang memiliki UI yang sama. Templat ini menunjukkan cara membuat aplikasi klien yang memaksimalkan penggunaan kembali kode dan target Android, iOS, Mac, Windows, dan Web.

Fitur utama templat ini meliputi:

  • Kemampuan untuk memilih Blazor mode render interaktif untuk aplikasi web.
  • Pembuatan otomatis proyek yang sesuai, termasuk Blazor Web App (rendering Otomatis Interaktif global) dan aplikasi .NET MAUIBlazor Hybrid .
  • Proyek yang dibuat menggunakan pustaka kelas bersama Razor (RCL) untuk mempertahankan komponen UI Razor .
  • Kode sampel disertakan yang menunjukkan cara menggunakan injeksi dependensi untuk menyediakan implementasi antarmuka yang berbeda untuk Blazor Hybrid aplikasi dan Blazor Web App.

Untuk memulai, instal .NET 9 SDK dan instal .NET MAUI beban kerja, yang berisi templat:

dotnet workload install maui

Buat solusi dari templat proyek di shell perintah menggunakan perintah berikut:

dotnet new maui-blazor-web

Templat juga tersedia di Visual Studio.

Catatan

Saat ini, pengecualian terjadi jika Blazor mode penyajian ditentukan pada tingkat per halaman/komponen. Untuk informasi selengkapnya, lihat BlazorWebView memerlukan cara untuk mengaktifkan penggantian ResolveComponentForRenderMode (dotnet/aspnetcore #51235).

Untuk informasi selengkapnya, lihat Membangun .NET MAUIBlazor Hybrid aplikasi dengan .Blazor Web App

Pengoptimalan pengiriman aset statis

MapStaticAssets adalah middleware baru yang membantu mengoptimalkan pengiriman aset statis di aplikasi ASP.NET Core apa pun, termasuk Blazor aplikasi.

Untuk informasi selengkapnya, lihat salah satu sumber daya berikut ini:

Mendeteksi lokasi penyajian, interaktivitas, dan mode render yang ditetapkan saat runtime

Kami telah memperkenalkan API baru yang dirancang untuk menyederhanakan proses kueri status komponen saat runtime. API ini menyediakan kemampuan berikut:

  • Tentukan lokasi eksekusi komponen saat ini: Ini dapat sangat berguna untuk penelusuran kesalahan dan mengoptimalkan performa komponen.
  • Periksa apakah komponen berjalan di lingkungan interaktif: Ini dapat membantu komponen yang memiliki perilaku yang berbeda berdasarkan interaktivitas lingkungan mereka.
  • Ambil mode render yang ditetapkan untuk komponen: Memahami mode render dapat membantu mengoptimalkan proses penyajian dan meningkatkan performa keseluruhan komponen.

Untuk informasi selengkapnya, lihat mode render ASP.NET CoreBlazor.

Pengalaman koneksi ulang sisi server yang disempurnakan:

Penyempurnaan berikut telah dilakukan pada pengalaman koneksi ulang sisi server default:

  • Saat pengguna menavigasi kembali ke aplikasi dengan sirkuit yang terputus, koneksi ulang dicoba segera daripada menunggu durasi interval koneksi ulang berikutnya. Ini meningkatkan pengalaman pengguna saat menavigasi ke aplikasi di tab browser yang telah tidur.

  • Ketika upaya koneksi ulang mencapai server tetapi server telah merilis sirkuit, refresh halaman terjadi secara otomatis. Ini mencegah pengguna harus menyegarkan halaman secara manual jika kemungkinan akan menghasilkan koneksi ulang yang berhasil.

  • Waktu koneksi ulang menggunakan strategi backoff komputasi. Secara default, beberapa upaya koneksi ulang pertama terjadi berturut-turut dengan cepat tanpa interval coba lagi sebelum penundaan komputasi diperkenalkan di antara upaya. Anda dapat menyesuaikan perilaku interval coba lagi dengan menentukan fungsi untuk menghitung interval coba lagi, seperti yang ditunjukkan contoh backoff eksponensial berikut:

    Blazor.start({
      circuit: {
        reconnectionOptions: {
          retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
            previousAttempts >= maxRetries ? null : previousAttempts * 1000
        },
      },
    });
    
  • Gaya antarmuka pengguna koneksi ulang default telah dimodernisasi.

Untuk informasi selengkapnya, lihat panduan ASP.NET CoreBlazorSignalR.

Serialisasi status autentikasi yang disederhanakan untuk Blazor Web Apps

API baru memudahkan untuk menambahkan autentikasi ke Blazor Web App. Saat Anda membuat baru Blazor Web App dengan autentikasi menggunakan Akun Individual dan Anda mengaktifkan interaktivitas berbasis WebAssembly, proyek menyertakan kustom AuthenticationStateProvider di proyek server dan klien.

Penyedia ini mengalirkan status autentikasi pengguna ke browser. Mengautentikasi di server daripada klien memungkinkan aplikasi mengakses status autentikasi selama pra-penyajian dan sebelum runtime .NET WebAssembly diinisialisasi.

Implementasi kustom menggunakan layanan Status Komponen Persisten (PersistentComponentState) untuk membuat serialisasi status autentikasi ke dalam komentar HTML dan membacanya kembali dari WebAssembly untuk membuat instans baruAuthenticationState. AuthenticationStateProvider

Ini berfungsi dengan baik jika Anda telah memulai dari Blazor Web App templat proyek dan memilih opsi Akun Individual, tetapi banyak kode untuk mengimplementasikan diri Anda atau menyalin jika Anda mencoba menambahkan autentikasi ke proyek yang ada. Sekarang ada API, yang sekarang menjadi bagian Blazor Web App dari templat proyek, yang dapat dipanggil di server dan proyek klien untuk menambahkan fungsionalitas ini:

  • AddAuthenticationStateSerialization: Menambahkan layanan yang diperlukan untuk membuat serialisasi status autentikasi di server.
  • AddAuthenticationStateDeserialization: Menambahkan layanan yang diperlukan untuk mendeserialisasi status autentikasi di browser.

Secara default, API hanya menserialisasikan nama sisi server dan klaim peran untuk akses di browser. Opsi dapat diteruskan ke AddAuthenticationStateSerialization untuk menyertakan semua klaim.

Untuk informasi selengkapnya, lihat bagian berikut dari aplikasi sisi Blazor server Secure ASP.NET Core:

Menambahkan halaman penyajian sisi server statis (SSR) ke interaktif global Blazor Web App

Dengan rilis .NET 9, sekarang lebih sederhana untuk menambahkan halaman SSR statis ke aplikasi yang mengadopsi interaktivitas global.

Pendekatan ini hanya berguna ketika aplikasi memiliki halaman tertentu yang tidak dapat bekerja dengan Server interaktif atau penyajian WebAssembly. Misalnya, adopsi pendekatan ini untuk halaman yang bergantung pada membaca/menulis cookie HTTP dan hanya dapat berfungsi dalam siklus permintaan/respons alih-alih penyajian interaktif. Untuk halaman yang berfungsi dengan penyajian interaktif, Anda tidak boleh memaksanya untuk menggunakan penyajian SSR statis, karena kurang efisien dan kurang responsif bagi pengguna akhir.

Tandai halaman komponen apa pun Razor dengan atribut baru [ExcludeFromInteractiveRouting] yang ditetapkan dengan direktif @attributeRazor :

@attribute [ExcludeFromInteractiveRouting]

Menerapkan atribut menyebabkan navigasi ke halaman keluar dari perutean interaktif. Navigasi masuk dipaksa untuk melakukan pemuatan ulang halaman penuh sebagai gantinya menyelesaikan halaman melalui perutean interaktif. Pemuatan ulang halaman penuh memaksa komponen akar tingkat atas, biasanya App komponen (App.razor), untuk merender dari server, memungkinkan aplikasi beralih ke mode render tingkat atas yang berbeda.

Metode HttpContext.AcceptsInteractiveRouting ekstensi memungkinkan komponen mendeteksi apakah [ExcludeFromInteractiveRouting] diterapkan ke halaman saat ini.

App Dalam komponen, gunakan pola dalam contoh berikut:

  • Halaman yang tidak dianotasi dengan [ExcludeFromInteractiveRouting] default ke InteractiveServer mode render dengan interaktivitas global. Anda dapat mengganti InteractiveServer dengan InteractiveWebAssembly atau InteractiveAuto untuk menentukan mode render global default yang berbeda.
  • Halaman yang dianotasi dengan [ExcludeFromInteractiveRouting] mengadopsi SSR statis (PageRenderMode ditetapkan null).
<!DOCTYPE html>
<html>
<head>
    ...
    <HeadOutlet @rendermode="@PageRenderMode" />
</head>
<body>
    <Routes @rendermode="@PageRenderMode" />
    ...
</body>
</html>

@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? PageRenderMode
        => HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;
}

Alternatif untuk menggunakan HttpContext.AcceptsInteractiveRouting metode ekstensi adalah membaca metadata titik akhir secara manual menggunakan HttpContext.GetEndpoint()?.Metadata.

Fitur ini dicakup oleh dokumentasi referensi dalam mode render ASP.NET CoreBlazor.

Injeksi konstruktor

Razor komponen mendukung injeksi konstruktor.

Dalam contoh berikut, kelas parsial (code-behind) menyuntikkan NavigationManager layanan menggunakan konstruktor utama:

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

Untuk informasi selengkapnya, lihat Injeksi dependensi ASP.NET Core Blazor.

Kompresi Websocket untuk komponen Server Interaktif

Secara default, komponen Server Interaktif memungkinkan pemadatan untuk koneksi WebSocket dan mengatur frame-ancestors direktif Kebijakan Keamanan Konten (CSP) yang diatur ke 'self', yang hanya mengizinkan penyematan aplikasi di <iframe> asal tempat aplikasi dilayani saat pemadatan diaktifkan atau saat konfigurasi untuk konteks WebSocket disediakan.

Pemadatan dapat dinonaktifkan dengan mengatur ConfigureWebSocketOptions ke null, yang mengurangi kerentanan aplikasi untuk menyerang tetapi dapat mengakibatkan penurunan performa:

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

Konfigurasikan CSP yang lebih frame-ancestors ketat dengan nilai 'none' (tanda kutip tunggal diperlukan), yang memungkinkan kompresi WebSocket tetapi mencegah browser menyematkan aplikasi ke dalam :<iframe>

.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

Untuk informasi selengkapnya, lihat sumber daya berikut:

Menangani peristiwa komposisi keyboard di Blazor

Properti baru KeyboardEventArgs.IsComposing menunjukkan apakah peristiwa keyboard adalah bagian dari sesi komposisi. Melacak status komposisi peristiwa keyboard sangat penting untuk menangani metode input karakter internasional.

Menambahkan parameter OverscanCount ke QuickGrid

Komponen QuickGrid sekarang mengekspos OverscanCount properti yang menentukan berapa banyak baris tambahan yang dirender sebelum dan sesudah wilayah yang terlihat saat virtualisasi diaktifkan.

OverscanCount Defaultnya adalah 3. Contoh berikut meningkatkan menjadi OverscanCount 4:

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

InputNumber komponen mendukung type="range" atribut

Komponen InputNumber<TValue> sekarang mendukung type="range" atribut , yang membuat input rentang yang mendukung pengikatan model dan validasi formulir, biasanya dirender sebagai penggeser atau kontrol dial daripada kotak teks:

<EditForm Model="Model" OnSubmit="Submit" FormName="EngineForm">
    <div>
        <label>
            Nacelle Count (2-6): 
            <InputNumber @bind-Value="Model!.NacelleCount" max="6" min="2" 
                step="1" type="range" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private EngineSpecifications? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit() {}

    public class EngineSpecifications
    {
        [Required, Range(minimum: 2, maximum: 6)]
        public int NacelleCount { get; set; }
    }
}

Beberapa Blazor Web Apps per proyek server

Dukungan untuk beberapa Blazor Web Apps per proyek server akan dipertimbangkan untuk .NET 10 (November, 2025).

Untuk informasi selengkapnya, lihat Dukungan untuk beberapa Blazor aplikasi Web per proyek server (dotnet/aspnetcore #52216).

SignalR

Bagian ini menjelaskan fitur baru untuk SignalR.

Dukungan jenis polimorfik di SignalR Hub

Metode hub sekarang dapat menerima kelas dasar alih-alih kelas turunan untuk mengaktifkan skenario polimorfik. Jenis dasar perlu diannotasi untuk memungkinkan polimorfisme.

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; }
}

Aktivitas yang Ditingkatkan untuk SignalR

SignalR sekarang memiliki ActivitySource bernama Microsoft.AspNetCore.SignalR.Server yang memancarkan peristiwa untuk panggilan metode hub:

  • Setiap metode adalah aktivitasnya sendiri, sehingga apa pun yang memancarkan aktivitas selama panggilan metode hub berada di bawah aktivitas metode hub.
  • Aktivitas metode hub tidak memiliki induk. Ini berarti mereka tidak dibundel di bawah koneksi yang berjalan SignalR lama.

Contoh berikut menggunakan dasbor .NET Aspire dan paket OpenTelemetry:

<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />

Tambahkan kode startup berikut ke Program.cs file:

// Set OTEL_EXPORTER_OTLP_ENDPOINT environment variable depending on where your OTEL endpoint is
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        if (builder.Environment.IsDevelopment())
        {
            // We want to view all traces in development
            tracing.SetSampler(new AlwaysOnSampler());
        }

        tracing.AddAspNetCoreInstrumentation();
        tracing.AddSource("Microsoft.AspNetCore.SignalR.Server");
    });

builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());

Berikut ini adalah contoh output dari Dasbor Aspire:

Daftar aktivitas untuk SignalR peristiwa panggilan metode Hub

SignalR mendukung pemangkasan dan AOT Asli

Melanjutkan perjalanan AOT Asli yang dimulai di .NET 8, kami telah mengaktifkan pemangkasan dan dukungan kompilasi native ahead-of-time (AOT) untuk SignalR skenario klien dan server. Anda sekarang dapat memanfaatkan manfaat performa menggunakan AOT Asli dalam aplikasi yang digunakan SignalR untuk komunikasi web real time.

Memulai

Instal .NET 9 SDK terbaru.

Buat solusi dari webapiaot templat di shell perintah menggunakan perintah berikut:

dotnet new webapiaot -o SignalRChatAOTExample

Ganti konten Program.cs file dengan kode berikut SignalR :

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");
app.MapGet("/", () => Results.Content("""
<!DOCTYPE html>
<html>
<head>
    <title>SignalR Chat</title>
</head>
<body>
    <input id="userInput" placeholder="Enter your name" />
    <input id="messageInput" placeholder="Type a message" />
    <button onclick="sendMessage()">Send</button>
    <ul id="messages"></ul>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chatHub")
            .build();

        connection.on("ReceiveMessage", (user, message) => {
            const li = document.createElement("li");
            li.textContent = `${user}: ${message}`;
            document.getElementById("messages").appendChild(li);
        });

        async function sendMessage() {
            const user = document.getElementById("userInput").value;
            const message = document.getElementById("messageInput").value;
            await connection.invoke("SendMessage", user, message);
        }

        connection.start().catch(err => console.error(err));
    </script>
</body>
</html>
""", "text/html"));

app.Run();

[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);
    }
}

Contoh sebelumnya menghasilkan eksekusi Windows asli 10 MB dan Linux yang dapat dieksekusi sebesar 10,9 MB.

Batasan

  • Hanya protokol JSON yang saat ini didukung:
    • Seperti yang ditunjukkan dalam kode sebelumnya, aplikasi yang menggunakan serialisasi JSON dan AOT Asli harus menggunakan System.Text.Json Generator Sumber.
    • Ini mengikuti pendekatan yang sama dengan API minimal.
  • SignalR Di server, parameter metode hub jenis IAsyncEnumerable<T> dan ChannelReader<T> di mana T valueType (struct) tidak didukung. Menggunakan jenis ini menghasilkan pengecualian runtime saat startup dalam pengembangan dan di aplikasi yang diterbitkan. Untuk informasi selengkapnya, lihat SignalR: Menggunakan IAsyncEnumerable<T> dan ChannelReader<T> dengan ValueTypes di AOT asli (dotnet/aspnetcore #56179).
  • Hub yang sangat ditik tidak didukung dengan Native AOT (PublishAot). Menggunakan hub yang sangat diketik dengan AOT Asli akan menghasilkan peringatan selama build dan penerbitan, dan pengecualian runtime. Menggunakan hub yang sangat ditik dengan pemangkasan (PublishedTrimmed) didukung.
  • Hanya Task, Task<T>, ValueTask, atau ValueTask<T> didukung untuk jenis pengembalian asinkron.

Minimal API

Bagian ini menjelaskan fitur baru untuk API minimal.

Menambahkan InternalServerError dan InternalServerError<TValue> ke TypedResults

Kelas TypedResults ini adalah kendaraan bermanfaat untuk mengembalikan respons berbasis kode status HTTP yang sangat ditik dari API minimal. TypedResults sekarang termasuk metode dan jenis pabrik untuk mengembalikan respons "500 Kesalahan Server Internal" dari titik akhir. Berikut adalah contoh yang mengembalikan respons 500:

var app = WebApplication.Create();

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

app.Run();

Memanggil ProducesProblem dan ProducesValidationProblem pada grup rute

Metode ProducesProblem ekstensi dan ProducesValidationProblem telah diperbarui untuk mendukung penggunaannya pada grup rute. Metode ini menunjukkan bahwa semua titik akhir dalam grup rute dapat mengembalikan ProblemDetails atau ValidationProblemDetails merespons untuk tujuan metadata OpenAPI.

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);

OpenAPI

Bagian ini menjelaskan fitur baru untuk OpenAPI

Dukungan bawaan untuk pembuatan dokumen OpenAPI

Spesifikasi OpenAPI adalah standar untuk menjelaskan API HTTP. Standar ini memungkinkan pengembang untuk menentukan bentuk API yang dapat dicolokkan ke generator klien, generator server, alat pengujian, dokumentasi, dan banyak lagi. Dalam Pratinjau .NET 9, ASP.NET Core menyediakan dukungan bawaan untuk menghasilkan dokumen OpenAPI yang mewakili API berbasis pengontrol atau minimal melalui paket Microsoft.AspNetCore.OpenApi .

Panggilan kode yang disorot berikut:

  • AddOpenApi untuk mendaftarkan dependensi yang diperlukan ke dalam kontainer DI aplikasi.
  • MapOpenApi untuk mendaftarkan titik akhir OpenAPI yang diperlukan dalam rute aplikasi.
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 Instal paket dalam proyek menggunakan perintah berikut:

dotnet add package Microsoft.AspNetCore.OpenApi --prerelease

Jalankan aplikasi dan navigasikan ke untuk openapi/v1.json melihat dokumen OpenAPI yang dihasilkan:

Dokumen OpenAPI

Dokumen OpenAPI juga dapat dihasilkan pada waktu build dengan menambahkan Microsoft.Extensions.ApiDescription.Server paket:

dotnet add package Microsoft.Extensions.ApiDescription.Server --prerelease

Dalam file proyek aplikasi, tambahkan yang berikut ini:

<PropertyGroup>
  <OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)</OpenApiDocumentsDirectory>
  <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
</PropertyGroup>

Jalankan dotnet build dan periksa file JSON yang dihasilkan di direktori proyek.

Pembuatan dokumen OpenAPI saat build-time

ASP.NET pembuatan dokumen OpenAPI bawaan Core menyediakan dukungan untuk berbagai penyesuaian dan opsi. Ini menyediakan transformator dokumen dan operasi dan memiliki kemampuan untuk mengelola beberapa dokumen OpenAPI untuk aplikasi yang sama.

Untuk mempelajari selengkapnya tentang kemampuan dokumen OpenAPI baru ASP.NET Core, lihat dokumen Microsoft.AspNetCore.OpenApi baru.

Peningkatan penyelesaian Intellisense untuk paket OpenAPI

ASP.NET dukungan OpenAPI Core sekarang lebih mudah diakses dan ramah pengguna. API OpenAPI dikirim sebagai paket independen, terpisah dari kerangka kerja bersama. Hingga saat ini, ini berarti bahwa pengembang tidak memiliki kenyamanan fitur penyelesaian kode seperti Intellisense untuk API OpenAPI.

Mengenali kesenjangan ini, kami telah memperkenalkan penyedia penyelesaian baru dan perbaikan kode. Alat-alat ini dirancang untuk memfasilitasi penemuan dan penggunaan API OpenAPI, sehingga memudahkan pengembang untuk mengintegrasikan OpenAPI ke dalam proyek mereka. Penyedia penyelesaian menawarkan saran kode real-time, sementara perbaikan kode membantu memperbaiki kesalahan umum dan meningkatkan penggunaan API. Peningkatan ini adalah bagian dari komitmen berkelanjutan kami untuk meningkatkan pengalaman pengembang dan menyederhanakan alur kerja terkait API.

Saat pengguna mengetik pernyataan di mana API terkait OpenAPI tersedia, penyedia penyelesaian menampilkan rekomendasi untuk API. Misalnya, dalam cuplikan layar berikut, penyelesaian untuk AddOpenApi dan MapOpenApi disediakan saat pengguna memasukkan pernyataan pemanggilan pada jenis yang didukung, seperti IEndpointConventionBuilder:

Penyelesaian OpenAPI

Ketika penyelesaian diterima dan Microsoft.AspNetCore.OpenApi paket tidak diinstal, codefixer menyediakan pintasan untuk menginstal dependensi secara otomatis dalam proyek.

Penginstalan paket otomatis

Dukungan untuk [Required] atribut dan [DefaultValue] pada parameter dan properti

Ketika [Required] atribut dan [DefaultValue] diterapkan pada parameter atau properti dalam jenis kompleks, implementasi OpenAPI memetakan ini ke required properti dan default dalam dokumen OpenAPI yang terkait dengan parameter atau skema jenis.

Misalnya, API berikut menghasilkan skema yang menyertainya untuk jenis tersebut Todo .

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapPost("/todos", (Todo todo) => { });

app.Run();

class Todo
{
	public int Id { get; init; }
	public required string Title { get; init; }
	[DefaultValue("A new todo")]
	public required string Description { get; init; }
	[Required]
	public DateTime CreatedOn { get; init; }
}
{
	"required": [
	  "title",
	  "description",
	  "createdOn"
	],
	"type": "object",
	"properties": {
	  "id": {
	    "type": "integer",
	    "format": "int32"
	  },
	  "title": {
	    "type": "string"
	  },
	  "description": {
	    "type": "string",
	    "default": "A new todo"
	  },
	  "createdOn": {
	    "type": "string",
	    "format": "date-time"
	  }
	}
}

Dukungan untuk transformator skema pada dokumen OpenAPI

Dukungan OpenAPI bawaan sekarang dikirim dengan dukungan untuk transformator skema yang dapat digunakan untuk memodifikasi skema yang dihasilkan oleh System.Text.Json dan implementasi OpenAPI. Seperti transformator dokumen dan operasi, transformator skema dapat didaftarkan pada objek OpenApiOptions . Misalnya, sampel kode berikut menunjukkan menggunakan transformator skema untuk menambahkan contoh ke skema jenis.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.OpenApi.Any;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.UseSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.Type == typeof(Todo))
        {
            schema.Example = new OpenApiObject
            {
                ["id"] = new OpenApiInteger(1),
                ["title"] = new OpenApiString("A short title"),
                ["description"] = new OpenApiString("A long description"),
                ["createdOn"] = new OpenApiDateTime(DateTime.Now)
            };
        }
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapPost("/todos", (Todo todo) => { });

app.Run();

class Todo
{
	public int Id { get; init; }
	public required string Title { get; init; }
	[DefaultValue("A new todo")]
	public required string Description { get; init; }
	[Required]
	public DateTime CreatedOn { get; init; }
}

Penyempurnaan API pendaftaran transformator di Microsoft.AspNetCore.OpenApi

Transformator OpenAPI mendukung modifikasi dokumen OpenAPI, operasi dalam dokumen, atau skema yang terkait dengan jenis di API. API untuk mendaftarkan transformator pada dokumen OpenAPI menyediakan berbagai opsi untuk mendaftarkan transformator.

Sebelumnya, API berikut tersedia untuk mendaftarkan transformator:

OpenApiOptions UseTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions UseTransformer(IOpenApiDocumentTransformer transformer)
OpenApiOptions UseTransformer<IOpenApiDocumentTransformer>()
OpenApiOptions UseSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>)
OpenApiOptions UseOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task>)

Set API baru adalah sebagai berikut:

OpenApiOptions AddDocumentTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddDocumentTransformer(IOpenApiDocumentTransformer transformer)
OpenApiOptions AddDocumentTransformer<IOpenApiDocumentTransformer>()

OpenApiOptions AddSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddSchemaTransformer(IOpenApiSchemaTransformer transformer)
OpenApiOptions AddSchemaTransformer<IOpenApiSchemaTransformer>()

OpenApiOptions AddOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddOperationTransformer(IOpenApiOperationTransformer transformer)
OpenApiOptions AddOperationTransformer<IOpenApiOperationTransformer>()

Microsoft.AspNetCore.OpenApi mendukung pemangkasan dan AOT Asli

Dukungan OpenAPI bawaan baru di ASP.NET Core sekarang juga mendukung pemangkasan dan AOT Asli.

Memulai

Buat proyek ASP.NET Core Web API (Native AOT) baru.

dotnet new webapiaot

Tambahkan paket Microsoft.AspNetCore.OpenAPI.

dotnet add package Microsoft.AspNetCore.OpenApi --prerelease

Untuk pratinjau ini, Anda juga perlu menambahkan paket Microsoft.OpenAPI terbaru untuk menghindari peringatan pemangkasan.

dotnet add package Microsoft.OpenApi

Perbarui Program.cs untuk mengaktifkan pembuatan dokumen OpenAPI.

+ builder.Services.AddOpenApi();

var app = builder.Build();

+ app.MapOpenApi();

Memublikasikan aplikasi.

dotnet publish

Aplikasi ini menerbitkan menggunakan Native AOT tanpa peringatan.

Mendukung panggilan ProducesProblem dan ProducesValidationProblem pada grup rute

Metode ekstensi ProducesProblem dan ProducesValidationProblem telah diperbarui untuk grup rute. Metode ini dapat digunakan untuk menunjukkan bahwa semua titik akhir dalam grup rute dapat mengembalikan ProblemDetails atau ValidationProblemDetails merespons untuk tujuan metadata OpenAPI.

var app = WebApplication.Create();

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

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

app.Run();

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

Problem dan ValidationProblem jenis hasil mendukung konstruksi dengan IEnumerable<KeyValuePair<string, object?>> nilai

Sebelum .NET 9, membuat jenis hasil Masalah dan ValidasiProblem dalam API minimal yang errors mengharuskan properti dan extensions diinisialisasi dengan implementasi IDictionary<string, object?>. Dalam rilis ini, API konstruksi ini mendukung kelebihan beban yang mengonsumsi IEnumerable<KeyValuePair<string, object?>>.

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);
});

Terima kasih kepada pengguna GitHub joegoldman2 untuk kontribusi ini!

Autentikasi dan otorisasi

Bagian ini menjelaskan fitur baru untuk autentikasi dan otorisasi.

OpenIdConnectHandler menambahkan dukungan untuk Permintaan Otorisasi Yang Didorong (PAR)

Kami ingin mengucapkan terima kasih kepada Joe DeCock dari Duende Software karena telah menambahkan Permintaan Otorisasi Yang Didorong (PAR) ke OpenIdConnectHandler ASP.NET Core. Joe menjelaskan latar belakang dan motivasi untuk mengaktifkan PAR dalam proposal API-nya sebagai berikut:

Permintaan Otorisasi yang Didorong (PAR) adalah standar OAuth yang relatif baru yang meningkatkan keamanan alur OAuth dan OIDC dengan memindahkan parameter otorisasi dari saluran depan ke saluran belakang. Artinya, memindahkan parameter otorisasi dari URL pengalihan di browser ke komputer langsung ke panggilan http komputer di ujung belakang.

Ini mencegah serangan cyber di browser dari:

  • Melihat parameter otorisasi, yang dapat membocorkan PII.
  • Merusak parameter tersebut. Misalnya, cyberattacker dapat mengubah cakupan akses yang diminta.

Mendorong parameter otorisasi juga membuat URL permintaan tetap pendek. Parameter otorisasi bisa menjadi sangat panjang saat menggunakan fitur OAuth dan OIDC yang lebih kompleks seperti Permintaan Otorisasi Kaya. URL yang merupakan masalah penyebab panjang di banyak browser dan infrastruktur jaringan.

Penggunaan PAR didorong oleh kelompok kerja FAPI dalam OpenID Foundation. Misalnya, Profil Keamanan FAPI2.0 memerlukan penggunaan PAR. Profil keamanan ini digunakan oleh banyak kelompok yang bekerja pada perbankan terbuka (terutama di Eropa), dalam perawatan kesehatan, dan di industri lain dengan persyaratan keamanan yang tinggi.

PAR didukung oleh sejumlah identity penyedia, termasuk

Untuk .NET 9, kami telah memutuskan untuk mengaktifkan PAR secara default jika identity dokumen penemuan penyedia mengiklankan dukungan untuk PAR, karena harus memberikan keamanan yang ditingkatkan untuk penyedia yang mendukungnya. Dokumen identity penemuan penyedia biasanya ditemukan di .well-known/openid-configuration. Jika ini menyebabkan masalah, Anda dapat menonaktifkan PAR melalui OpenIdConnectOptions.PushedAuthorizationBehavior sebagai berikut:

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", oidcOptions =>
    {
        // Other provider-specific configuration goes here.

        // The default value is PushedAuthorizationBehavior.UseIfAvailable.

        // 'OpenIdConnectOptions' does not contain a definition for 'PushedAuthorizationBehavior'
        // and no accessible extension method 'PushedAuthorizationBehavior' accepting a first argument
        // of type 'OpenIdConnectOptions' could be found
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });

Untuk memastikan bahwa autentikasi hanya berhasil jika PAR digunakan, gunakan PushedAuthorizationBehavior.Require sebagai gantinya. Perubahan ini juga memperkenalkan peristiwa OnPushAuthorization baru ke OpenIdConnectEvents yang dapat digunakan menyesuaikan permintaan otorisasi yang didorong atau menanganinya secara manual. Lihat proposal API untuk detail selengkapnya.

Kustomisasi Parameter OIDC dan OAuth

Handler autentikasi OAuth dan OIDC sekarang memiliki AdditionalAuthorizationParameters opsi untuk mempermudah kustomisasi parameter pesan otorisasi yang biasanya disertakan sebagai bagian dari string kueri pengalihan. Di .NET 8 dan yang lebih lama, ini memerlukan metode panggilan balik kustom OnRedirectToIdentityProvider atau ditimpa BuildChallengeUrl dalam handler kustom. Berikut adalah contoh kode .NET 8:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.Events.OnRedirectToIdentityProvider = context =>
    {
        context.ProtocolMessage.SetParameter("prompt", "login");
        context.ProtocolMessage.SetParameter("audience", "https://api.example.com");
        return Task.CompletedTask;
    };
});

Contoh sebelumnya sekarang dapat disederhanakan ke kode berikut:

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

Mengonfigurasi HTTP.sys bendera autentikasi yang diperluas

Anda sekarang dapat mengonfigurasi HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING bendera dan HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL HTTP.sys dengan menggunakan properti baru EnableKerberosCredentialCaching dan CaptureCredentials pada HTTP.sys AuthenticationManager untuk mengoptimalkan cara autentikasi Windows ditangani. Contohnya:

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

Lain-lain

Bagian berikut menjelaskan fitur baru lainnya.

Pustaka baru HybridCache

HybridCache API menjenjang beberapa celah di API dan IMemoryCache yang adaIDistributedCache. Ini juga menambahkan kemampuan baru, seperti:

  • Perlindungan "Stampede" untuk mencegah pengambilan paralel dari pekerjaan yang sama.
  • Serialisasi yang dapat dikonfigurasi.

HybridCache dirancang untuk menjadi pengganti drop-in untuk yang ada IDistributedCache dan IMemoryCache penggunaan, dan menyediakan API sederhana untuk menambahkan kode penembolokan baru. Ini menyediakan API terpadu untuk penembolokan dalam proses dan di luar proses.

Untuk melihat bagaimana API disederhanakan HybridCache IDistributedCache, bandingkan dengan kode yang menggunakan . Berikut adalah contoh tampilan penggunaan IDistributedCache :

public class SomeService(IDistributedCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync
        (string name, int id, CancellationToken token = default)
    {
        var key = $"someinfo:{name}:{id}"; // Unique key for this combination.
        var bytes = await cache.GetAsync(key, token); // Try to get from cache.
        SomeInformation info;
        if (bytes is null)
        {
            // Cache miss; get the data from the real source.
            info = await SomeExpensiveOperationAsync(name, id, token);

            // Serialize and cache it.
            bytes = SomeSerializer.Serialize(info);
            await cache.SetAsync(key, bytes, token);
        }
        else
        {
            // Cache hit; deserialize it.
            info = SomeSerializer.Deserialize<SomeInformation>(bytes);
        }
        return info;
    }

    // This is the work we're trying to cache.
    private async Task<SomeInformation> SomeExpensiveOperationAsync(string name, int id,
        CancellationToken token = default)
    { /* ... */ }
}

Itu banyak pekerjaan yang harus disempurnakan setiap kali, termasuk hal-hal seperti serialisasi. Dan dalam skenario cache miss, Anda bisa berakhir dengan beberapa utas bersamaan, semua mendapatkan cache miss, semua mengambil data yang mendasarinya, semua menserialisasikannya, dan semua mengirim data tersebut ke cache.

Untuk menyederhanakan dan meningkatkan kode ini dengan HybridCache, pertama-tama kita perlu menambahkan pustaka Microsoft.Extensions.Caching.Hybridbaru :

<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0" />

Daftarkan HybridCache layanan, seperti anda akan mendaftarkan implementasi IDistributedCache :

builder.Services.AddHybridCache(); // Not shown: optional configuration API.

Sekarang sebagian besar masalah penembolokan dapat dilepaskan ke HybridCache:

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

Kami menyediakan implementasi konkret dari HybridCache kelas abstrak melalui injeksi dependensi, tetapi dimaksudkan agar pengembang dapat memberikan implementasi kustom API. Implementasi HybridCache ini berkaitan dengan segala sesuatu yang terkait dengan penembolokan, termasuk penanganan operasi bersamaan. Token cancel di sini mewakili pembatalan gabungan semua pemanggil bersamaan—bukan hanya pembatalan pemanggil yang dapat kita lihat (yaitu, token).

Skenario throughput tinggi dapat dioptimalkan lebih lanjut dengan menggunakan TState pola, untuk menghindari beberapa overhead dari variabel yang ditangkap dan panggilan balik per instans:

public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync(string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}", // unique key for this combination
            (name, id), // all of the state we need for the final call, if needed
            static async (state, token) =>
                await SomeExpensiveOperationAsync(state.name, state.id, token),
            token: token
        );
    }
}

HybridCache menggunakan implementasi yang dikonfigurasi IDistributedCache , jika ada, untuk penembolokan di luar proses sekunder, misalnya, menggunakan Redis. Tetapi bahkan tanpa IDistributedCache, HybridCache layanan masih akan memberikan perlindungan penembolokan dalam proses dan "stempel".

Catatan tentang penggunaan kembali objek

Dalam kode umum yang ada yang menggunakan IDistributedCache, setiap pengambilan objek dari cache menghasilkan deserialisasi. Perilaku ini berarti bahwa setiap pemanggil bersamaan mendapatkan instans terpisah dari objek, yang tidak dapat berinteraksi dengan instans lain. Hasilnya adalah keamanan utas, karena tidak ada risiko modifikasi bersamaan pada instans objek yang sama.

Karena banyak HybridCache penggunaan akan diadaptasi dari kode yang ada IDistributedCache , HybridCache mempertahankan perilaku ini secara default untuk menghindari memperkenalkan bug konkurensi. Namun, kasus penggunaan tertentu secara inheren aman utas:

  • Jika jenis yang di-cache tidak dapat diubah.
  • Jika kode tidak mengubahnya.

Dalam kasus seperti itu, informasikan HybridCache bahwa aman untuk menggunakan kembali instans dengan:

  • Menandai jenis sebagai sealed. Kata sealed kunci dalam C# berarti bahwa kelas tidak dapat diwariskan.
  • Menerapkan atribut ke [ImmutableObject(true)] atribut tersebut. Atribut [ImmutableObject(true)] menunjukkan bahwa status objek tidak dapat diubah setelah dibuat.

Dengan menggunakan kembali instans, HybridCache dapat mengurangi overhead CPU dan alokasi objek yang terkait dengan deserialisasi per panggilan. Hal ini dapat menyebabkan peningkatan performa dalam skenario di mana objek yang di-cache besar atau sering diakses.

Fitur lain HybridCache

Seperti IDistributedCache, HybridCache mendukung penghapusan dengan kunci dengan RemoveKeyAsync metode .

HybridCache juga menyediakan API opsional untuk IDistributedCache implementasi, untuk menghindari byte[] alokasi. Fitur ini diimplementasikan oleh versi pratinjau paket Microsoft.Extensions.Caching.StackExchangeRedis dan Microsoft.Extensions.Caching.SqlServer .

Serialisasi dikonfigurasi sebagai bagian dari mendaftarkan layanan, dengan dukungan untuk serializer khusus jenis dan umum melalui WithSerializer metode dan .WithSerializerFactory , ditautkan dari AddHybridCache panggilan. Secara default, pustaka menangani string dan byte[] secara internal, dan menggunakan System.Text.Json untuk yang lain, tetapi Anda dapat menggunakan protobuf, xml, atau apa pun.

HybridCache mendukung runtime .NET yang lebih lama, hingga .NET Framework 4.7.2 dan .NET Standard 2.0.

Untuk informasi selengkapnya tentang HybridCache, lihat Pustaka HybridCache di ASP.NET Core

Penyempurnaan halaman pengecualian pengembang

Halaman pengecualian pengembang ASP.NET Core ditampilkan saat aplikasi melemparkan pengecualian yang tidak tertangani selama pengembangan. Halaman pengecualian pengembang menyediakan informasi terperinci tentang pengecualian dan permintaan.

Pratinjau 3 menambahkan metadata titik akhir ke halaman pengecualian pengembang. ASP.NET Core menggunakan metadata titik akhir untuk mengontrol perilaku titik akhir, seperti perutean, penembolokan respons, pembatasan laju, pembuatan OpenAPI, dan banyak lagi. Gambar berikut menunjukkan informasi metadata baru di bagian Routing halaman pengecualian pengembang:

Informasi metadata baru di halaman pengecualian pengembang

Saat menguji halaman pengecualian pengembang, peningkatan kualitas hidup yang kecil diidentifikasi. Mereka dikirim dalam Pratinjau 4:

  • Pembungkusan teks yang lebih baik. Cookie panjang, nilai string kueri, dan nama metode tidak lagi menambahkan bilah gulir browser horizontal.
  • Teks yang lebih besar yang ditemukan dalam desain modern.
  • Ukuran tabel yang lebih konsisten.

Gambar animasi berikut menunjukkan halaman pengecualian pengembang baru:

Halaman pengecualian pengembang baru

Peningkatan penelusuran kesalahan kamus

Tampilan debugging kamus dan koleksi nilai kunci lainnya memiliki tata letak yang ditingkatkan. Kunci ditampilkan di kolom kunci debugger alih-alih digabungkan dengan nilai . Gambar berikut menunjukkan tampilan lama dan baru kamus di debugger.

Sebelum:

Pengalaman debugger sebelumnya

Setelah:

Pengalaman debugger baru

ASP.NET Core memiliki banyak koleksi kunci-nilai. Pengalaman penelusuran kesalahan yang ditingkatkan ini berlaku untuk:

  • Header HTTP
  • String kueri
  • Formulir
  • Cookie
  • Menampilkan data
  • Merutekan data
  • Fitur

Perbaikan untuk 503 selama daur ulang aplikasi di IIS

Secara default sekarang ada penundaan 1 detik antara ketika IIS diberi tahu tentang daur ulang atau matikan dan ketika ANCM memberi tahu server terkelola untuk mulai dimatikan. Penundaan dapat dikonfigurasi melalui ANCM_shutdownDelay variabel lingkungan atau dengan mengatur shutdownDelay pengaturan handler. Kedua nilai berada dalam milidetik. Penundaan ini terutama untuk mengurangi kemungkinan perlombaan di mana:

  • IIS belum mulai mengantre permintaan untuk masuk ke aplikasi baru.
  • ANCM mulai menolak permintaan baru yang masuk ke aplikasi lama.

Komputer atau mesin yang lebih lambat dengan penggunaan CPU yang lebih berat mungkin ingin menyesuaikan nilai ini untuk mengurangi kemungkinan 503.

Contoh pengaturan shutdownDelay:

<aspNetCore processPath="dotnet" arguments="myapp.dll" stdoutLogEnabled="false" stdoutLogFile=".logsstdout">
  <handlerSettings>
    <!-- Milliseconds to delay shutdown by.
    this doesn't mean incoming requests will be delayed by this amount,
    but the old app instance will start shutting down after this timeout occurs -->
    <handlerSetting name="shutdownDelay" value="5000" />
  </handlerSettings>
</aspNetCore>

Perbaikannya ada dalam modul ANCM yang diinstal secara global yang berasal dari bundel hosting.

Mengoptimalkan pengiriman aset web statis

Mengikuti praktik terbaik produksi untuk melayani aset statis membutuhkan sejumlah besar keahlian kerja dan teknis. Tanpa pengoptimalan seperti kompresi, penembolokan, dan sidik jari:

  • Browser harus membuat permintaan tambahan pada setiap pemuatan halaman.
  • Lebih banyak byte daripada yang diperlukan ditransfer melalui jaringan.
  • Terkadang versi file kedaluarsa disajikan kepada klien.

Membuat aplikasi web berkinerja memerlukan pengoptimalan pengiriman aset ke browser. Kemungkinan pengoptimalan meliputi:

  • Layani aset tertentu sekali sampai file berubah atau browser menghapus cache-nya. Atur header ETag .
  • Cegah browser menggunakan aset lama atau kedaluarsa setelah aplikasi diperbarui. Atur header Terakhir Diubah .
  • Siapkan header penembolokan yang tepat.
  • Gunakan middleware penembolokan.
  • Menyajikan versi aset yang dikompresi jika memungkinkan.
  • Gunakan CDN untuk melayani aset yang lebih dekat dengan pengguna.
  • Minimalkan ukuran aset yang disajikan ke browser. Pengoptimalan ini tidak termasuk minifikasi.

MapStaticAssets adalah middleware baru yang membantu mengoptimalkan pengiriman aset statis dalam aplikasi. Ini dirancang untuk bekerja dengan semua kerangka kerja UI, termasuk Blazor, Razor Pages, dan MVC. Ini biasanya merupakan pengganti drop-in untuk UseStaticFiles:

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 beroperasi dengan menggabungkan proses build dan publish-time untuk mengumpulkan informasi tentang semua sumber daya statis dalam aplikasi. Informasi ini kemudian digunakan oleh pustaka runtime untuk melayani file-file ini secara efisien ke browser.

MapStaticAssets dapat menggantikan UseStaticFiles dalam sebagian besar situasi, namun, hal ini dioptimalkan untuk melayani aset yang memiliki pengetahuan tentang aplikasi saat membangun dan menerbitkan waktu. Jika aplikasi melayani aset dari lokasi lain, seperti disk atau sumber daya yang disematkan, UseStaticFiles harus digunakan.

MapStaticAssets memberikan manfaat berikut yang tidak ditemukan dengan UseStaticFiles:

  • Kompresi waktu build untuk semua aset di aplikasi:
    • gzip selama pengembangan dan gzip + brotli selama penerbitan.
    • Semua aset dikompresi dengan tujuan mengurangi ukuran aset menjadi minimum.
  • ETagsBerbasis konten : Etags untuk setiap sumber daya adalah string yang dikodekan Base64 dari hash SHA-256 konten. Ini memastikan bahwa browser hanya mengunduh ulang file jika kontennya telah berubah.

Tabel berikut ini memperlihatkan ukuran asli dan terkompresi CSS dan JS file dalam templat Halaman default Razor :

File Asli Dikompresi % Pengurangan
bootstrap.min.css 163 17.5 89.26%
jquery.js 89.6 28 68.75%
bootstrap.min.js 78.5 20 74.52%
Total 331.1 65.5 80.20%

Tabel berikut ini memperlihatkan ukuran asli dan terkompresi menggunakan pustaka komponen UI Blazor Fasih:

File Asli Dikompresi % Pengurangan
fasih.js 384 73 80.99%
fluent.css 94 11 88.30%
Total 478 84 82.43%

Untuk total 478 KB yang tidak dikompresi menjadi 84 KB dikompresi.

Tabel berikut ini memperlihatkan ukuran asli dan terkompresi menggunakan pustaka komponen MudBlazorBlazor :

File Asli Dikompresi Pengurangan
MudBlazor.min.css 541 37,5 93.07%
MudBlazor.min.js 47.4 9.2 80.59%
Total 588.4 46.7 92.07%

Pengoptimalan terjadi secara otomatis saat menggunakan MapStaticAssets. Saat pustaka ditambahkan atau diperbarui, misalnya dengan JavaScript atau CSS baru, aset dioptimalkan sebagai bagian dari build. Pengoptimalan sangat bermanfaat bagi lingkungan seluler yang dapat memiliki bandwidth yang lebih rendah atau koneksi yang tidak dapat diandalkan.

Untuk informasi selengkapnya tentang fitur pengiriman file baru, lihat sumber daya berikut ini:

Mengaktifkan kompresi dinamis di server vs menggunakan MapStaticAssets

MapStaticAssets memiliki keuntungan berikut daripada kompresi dinamis di server:

  • Lebih sederhana karena tidak ada konfigurasi spesifik server.
  • Lebih berkinerja karena aset dikompresi pada waktu build.
  • Memungkinkan pengembang untuk menghabiskan waktu tambahan selama proses build untuk memastikan bahwa aset adalah ukuran minimum.

Pertimbangkan tabel berikut membandingkan kompresi MudBlazor dengan kompresi dinamis IIS dan MapStaticAssets:

IIS gzip MapStaticAssets Pengurangan MapStaticAssets
≅ 90 37,5 59%

ASP0026: Penganalisis untuk memperingatkan ketika [Otorisasi] ditimpa oleh [AllowAnonymous] dari "lebih jauh"

Tampaknya intuitif bahwa atribut ditempatkan [Authorize] "lebih dekat" ke tindakan MVC daripada [AllowAnonymous] atribut akan mengambil alih [AllowAnonymous] atribut dan memaksa otorisasi. Namun, ini belum tentu terjadi. Yang penting adalah urutan relatif atribut.

Kode berikut menunjukkan contoh di mana atribut yang lebih dekat [Authorize] ditimpa oleh [AllowAnonymous] atribut yang lebih jauh.

[AllowAnonymous]
public class MyController
{
    [Authorize] // Overridden by the [AllowAnonymous] attribute on the class
    public IActionResult Private() => null;
}
[AllowAnonymous]
public class MyControllerAnon : ControllerBase
{
}

[Authorize] // Overridden by the [AllowAnonymous] attribute on MyControllerAnon
public class MyControllerInherited : MyControllerAnon
{
}

public class MyControllerInherited2 : MyControllerAnon
{
    [Authorize] // Overridden by the [AllowAnonymous] attribute on MyControllerAnon
    public IActionResult Private() => null;
}
[AllowAnonymous]
[Authorize] // Overridden by the preceding [AllowAnonymous]
public class MyControllerMultiple : ControllerBase
{
}

Di .NET 9 Pratinjau 6, kami telah memperkenalkan penganalisis yang akan menyoroti instans seperti ini di mana atribut yang lebih dekat [Authorize] ditimpa oleh [AllowAnonymous] atribut yang lebih jauh dari tindakan MVC. Peringatan menunjuk ke atribut yang ditimpa [Authorize] dengan pesan berikut:

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

Tindakan yang benar untuk diambil jika Anda melihat peringatan ini tergantung pada niat di balik atribut. Atribut yang lebih [AllowAnonymous] jauh harus dihapus jika tidak sengaja mengekspos titik akhir ke pengguna anonim. [AllowAnonymous] Jika atribut dimaksudkan untuk mengambil alih atribut yang lebih dekat[Authorize], Anda dapat mengulangi [AllowAnonymous] atribut setelah [Authorize] atribut untuk mengklarifikasi niat.

[AllowAnonymous]
public class MyController
{
    // This produces no warning because the second, "closer" [AllowAnonymous]
    // clarifies that [Authorize] is intentionally overridden.
    // Specifying AuthenticationSchemes can still be useful
    // for endpoints that allow but don't require authenticated users.
    [Authorize(AuthenticationSchemes = "Cookies")]
    [AllowAnonymous]
    public IActionResult Privacy() => null;
}

Metrik koneksi yang disempurnakan Kestrel

Kami telah melakukan peningkatan signifikan pada Kestrelmetrik koneksi dengan menyertakan metadata tentang mengapa koneksi gagal. kestrel.connection.duration Metrik sekarang menyertakan alasan dekat koneksi dalam error.type atribut .

Berikut adalah sampel kecil nilai error.type :

  • tls_handshake_failed - Koneksi memerlukan TLS, dan jabat tangan TLS gagal.
  • connection_reset - Koneksi secara tak terduga ditutup oleh klien saat permintaan sedang berlangsung.
  • request_headers_timeout - Kestrel menutup koneksi karena tidak menerima header permintaan tepat waktu.
  • max_request_body_size_exceeded - Kestrel menutup koneksi karena data yang diunggah melebihi ukuran maksimum.

Sebelumnya, mendiagnosis Kestrel masalah koneksi memerlukan server untuk merekam pengelogan tingkat rendah yang terperinci. Namun, log bisa mahal untuk dihasilkan dan disimpan, dan mungkin sulit untuk menemukan informasi yang tepat di antara kebisingan.

Metrik adalah alternatif yang jauh lebih murah yang dapat dibiarkan di lingkungan produksi dengan dampak minimal. Metrik yang dikumpulkan dapat mendorong dasbor dan pemberitahuan. Setelah masalah diidentifikasi pada tingkat tinggi dengan metrik, penyelidikan lebih lanjut menggunakan pengelogan dan alat lainnya dapat dimulai.

Kami berharap metrik koneksi yang ditingkatkan berguna dalam banyak skenario:

  • Menyelidiki masalah performa yang disebabkan oleh masa pakai koneksi yang singkat.
  • Mengamati serangan eksternal yang sedang berlangsung yang berdampak pada Kestrel performa dan stabilitas.
  • Perekaman mencoba serangan eksternal pada Kestrel penguatan keamanan bawaan yang Kestreldicegah.

Untuk informasi selengkapnya, lihat metrik ASP.NET Core.

Menyesuaikan Kestrel titik akhir pipa bernama

KestrelDukungan pipa bernama telah ditingkatkan dengan opsi kustomisasi tingkat lanjut. Metode baru CreateNamedPipeServerStream pada opsi pipa bernama memungkinkan pipa disesuaikan per titik akhir.

Contoh di mana ini berguna adalah Kestrel aplikasi yang memerlukan dua titik akhir pipa dengan keamanan akses yang berbeda. Opsi CreateNamedPipeServerStream dapat digunakan untuk membuat pipa dengan pengaturan keamanan kustom, tergantung pada nama pipa.

var builder = WebApplication.CreateBuilder();

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenNamedPipe("pipe1");
    options.ListenNamedPipe("pipe2");
});

builder.WebHost.UseNamedPipes(options =>
{
    options.CreateNamedPipeServerStream = (context) =>
    {
        var pipeSecurity = CreatePipeSecurity(context.NamedPipeEndpoint.PipeName);

        return NamedPipeServerStreamAcl.Create(context.NamedPipeEndPoint.PipeName, PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte,
            context.PipeOptions, inBufferSize: 0, outBufferSize: 0, pipeSecurity);
    };
});

ExceptionHandlerMiddleware opsi untuk memilih kode status berdasarkan jenis pengecualian

Opsi baru saat mengonfigurasi ExceptionHandlerMiddleware memungkinkan pengembang aplikasi memilih kode status apa yang akan dikembalikan saat pengecualian terjadi selama penanganan permintaan. Opsi baru mengubah kode status yang ProblemDetails diatur dalam respons dari ExceptionHandlerMiddleware.

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

Menolak metrik HTTP pada titik akhir dan permintaan tertentu

.NET 9 memperkenalkan kemampuan untuk menolak metrik HTTP untuk titik akhir dan permintaan tertentu. Memilih keluar dari metrik perekaman bermanfaat untuk titik akhir yang sering dipanggil oleh sistem otomatis, seperti pemeriksaan kesehatan. Merekam metrik untuk permintaan ini umumnya tidak perlu.

Permintaan HTTP ke titik akhir dapat dikecualikan dari metrik dengan menambahkan metadata. Yaitu:

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

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

Properti MetricsDisabled telah ditambahkan ke IHttpMetricsTagsFeature untuk:

  • Skenario tingkat lanjut di mana permintaan tidak dipetakan ke titik akhir.
  • Menonaktifkan pengumpulan metrik secara dinamis untuk permintaan HTTP tertentu.
// Middleware that conditionally opts-out HTTP requests.
app.Use(async (context, next) =>
{
    var metricsFeature = context.Features.Get<IHttpMetricsTagsFeature>();
    if (metricsFeature != null &&
        context.Request.Headers.ContainsKey("x-disable-metrics"))
    {
        metricsFeature.MetricsDisabled = true;
    }

    await next(context);
});

Dukungan Perlindungan Data untuk menghapus kunci

Sebelum .NET 9, kunci perlindungan data tidak dapat dilepas berdasarkan desain, untuk mencegah kehilangan data. Menghapus kunci membuat data yang dilindungi tidak dapat diambil. Mengingat ukurannya yang kecil, akumulasi kunci ini umumnya menimbulkan dampak minimal. Namun, untuk mengakomodasi layanan yang sangat berjalan lama, kami telah memperkenalkan opsi untuk menghapus kunci. Umumnya, hanya kunci lama yang harus dihapus. Hanya hapus kunci saat Anda dapat menerima risiko kehilangan data dengan imbalan penghematan penyimpanan. Sebaiknya kunci perlindungan data tidak boleh dihapus.

using Microsoft.AspNetCore.DataProtection.KeyManagement;

var services = new ServiceCollection();
services.AddDataProtection();

var serviceProvider = services.BuildServiceProvider();

var keyManager = serviceProvider.GetService<IKeyManager>();

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.");
    }
}
else
{
    Console.WriteLine("Key manager does not support deletion.");
}

Middleware mendukung Keyed DI

Middleware sekarang mendukung Keyed DI di konstruktor dan Invoke/InvokeAsync metode :

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);
}

Percayai sertifikat pengembangan HTTPS inti ASP.NET di Linux

Pada distro Linux berbasis Ubuntu dan Fedora, dotnet dev-certs https --trust sekarang mengonfigurasi sertifikat pengembangan ASP.NET Core HTTPS sebagai sertifikat tepercaya untuk:

  • Browser Chromium, misalnya, Google Chrome, Microsoft Edge, dan Chromium.
  • Mozilla Firefox dan Mozilla memperoleh browser.
  • API .NET, misalnya, HttpClient

Sebelumnya, --trust hanya bekerja pada Windows dan macOS. Kepercayaan sertifikat diterapkan per pengguna.

Untuk membangun kepercayaan pada OpenSSL, alat ini dev-certs :

  • Memasukkan sertifikat ~/.aspnet/dev-certs/trust
  • Menjalankan versi alat c_rehash OpenSSL yang disederhanakan pada direktori.
  • Meminta pengguna untuk memperbarui SSL_CERT_DIR variabel lingkungan.

Untuk membangun kepercayaan pada dotnet, alat ini menempatkan sertifikat di penyimpanan My/Root sertifikat.

Untuk membangun kepercayaan dalam database NSS, jika ada, alat ini mencari home direktori untuk profil Firefox, ~/.pki/nssdb, dan ~/snap/chromium/current/.pki/nssdb. Untuk setiap direktori yang ditemukan, alat menambahkan entri ke nssdb.