Perutean di ASP.NET Core 3.1

Oleh Ryan Nowak, Kirk Larkin, dan Rick Anderson

Note

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 10 dari artikel ini.

Warning

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 10 dari artikel ini.

Perutean bertanggung jawab untuk mencocokkan permintaan HTTP masuk dan mengirimkan permintaan tersebut ke titik akhir aplikasi yang dapat dieksekusi. Titik akhir adalah unit kode penanganan permintaan yang dapat dieksekusi aplikasi. Titik akhir ditentukan dalam aplikasi dan dikonfigurasi saat aplikasi dimulai. Proses pencocokan titik akhir dapat mengekstrak nilai dari URL permintaan dan menyediakan nilai tersebut untuk pemrosesan permintaan. Dengan memanfaatkan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang menghubungkan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Controllers
  • Razor Halaman
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambdas yang terdaftar dalam perutean.

Artikel ini membahas detail tingkat rendah perutean ASP.NET Core. Untuk informasi tentang mengonfigurasi perutean:

Dasar-dasar perutean

Kode berikut menunjukkan contoh dasar perutean:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Contoh sebelumnya mencakup satu titik akhir menggunakan MapGet metode :

  • Ketika permintaan HTTP GET dikirim ke URL /akar :
    • Delegasi permintaan dijalankan.
    • Halo Dunia! dicatat dalam respons HTTP.
  • Jika metode permintaan tidak GET atau URL akar bukan /, tidak ada rute yang cocok dan HTTP 404 dikembalikan.

Perutean menggunakan sepasang middleware, yang didaftarkan oleh UseRouting dan UseEndpoints:

  • UseRouting menambahkan pencocokan rute ke alur middleware. Middleware ini melihat kumpulan titik akhir yang ditentukan dalam aplikasi, dan memilih kecocokan terbaik berdasarkan permintaan.
  • UseEndpoints menambahkan eksekusi titik akhir ke alur middleware. Ini menjalankan delegasi yang terkait dengan titik akhir yang dipilih.

Aplikasi biasanya tidak perlu memanggil UseRouting atau UseEndpoints. WebApplicationBuilder mengonfigurasi alur middleware yang membungkus middleware yang ditambahkan Program.cs dengan UseRouting dan UseEndpoints. Namun, aplikasi dapat mengubah urutan di mana UseRouting dan UseEndpoints berjalan dengan memanggil metode ini secara eksplisit. Misalnya, kode berikut melakukan panggilan eksplisit ke UseRouting:

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Dalam kode sebelumnya:

  • Panggilan ke app.Use mendaftarkan middleware kustom yang berjalan di awal jalur pemrosesan.
  • Panggilan untuk UseRouting mengonfigurasi middleware pencocokan rute untuk dijalankan setelah middleware kustom.
  • Titik akhir yang terdaftar dengan MapGet berjalan di akhir pipeline.

Jika contoh sebelumnya tidak menyertakan panggilan ke UseRouting, middleware kustom akan berjalan setelah middleware yang melakukan pencocokan rute.

Catatan: Rute yang ditambahkan langsung ke WebApplication dieksekusi di akhir dari alur.

Endpoints

Metode MapGet ini digunakan untuk menentukan titik akhir. Titik akhir adalah sesuatu yang dapat berupa:

  • Dipilih, dengan mencocokkan URL dan metode HTTP.
  • Dijalankan, dengan menjalankan delegasi.

Titik akhir yang dapat dicocokkan dan dijalankan oleh aplikasi dikonfigurasi di UseEndpoints. Misalnya, MapGet, MapPost, dan metode serupa menghubungkan delegasi permintaan dengan sistem routing. Metode tambahan dapat digunakan untuk menghubungkan fitur kerangka kerja ASP.NET Core ke sistem perutean:

Contoh berikut menunjukkan perutean dengan templat rute yang lebih canggih:

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

String /hello/{name:alpha} adalah templat rute. Templat rute digunakan untuk mengonfigurasi bagaimana titik akhir cocok. Dalam hal ini, templat cocok:

  • URL seperti /hello/Docs
  • Jalur URL apa pun yang dimulai dengan /hello/ diikuti dengan urutan karakter alfabet. :alpha menerapkan batasan rute yang hanya cocok dengan karakter alfabet. Batasan rute dijelaskan nanti dalam artikel ini.

Segmen kedua dari jalur URL, {name:alpha}:

Contoh berikut menunjukkan perutean dengan pemeriksaan status kesehatan dan otorisasi:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan penerusan jalur.
  • Titik akhir dapat digunakan untuk mengonfigurasi perilaku otorisasi.

Panggilan MapHealthChecks menambahkan titik akhir pemeriksaan kesehatan. Menautkan RequireAuthorization ke panggilan ini melampirkan kebijakan otorisasi ke titik akhir.

Memanggil UseAuthentication dan UseAuthorization menambahkan middleware autentikasi dan otorisasi. Middleware ini ditempatkan di antara UseRouting dan UseEndpoints sehingga mereka dapat:

  • Lihat titik akhir mana yang dipilih oleh UseRouting.
  • Terapkan kebijakan otorisasi sebelum UseEndpoints dikirim ke titik akhir.

Metadata titik akhir

Dalam contoh sebelumnya, ada dua titik akhir, tetapi hanya titik akhir pemeriksaan kesehatan yang memiliki kebijakan otorisasi yang terlampir. Jika permintaan cocok dengan titik akhir pemeriksaan kesehatan, /healthz, pemeriksaan otorisasi dilakukan. Ini menunjukkan bahwa titik akhir dapat memiliki data tambahan yang melekat padanya. Data tambahan ini disebut metadata titik akhir:

  • Metadata dapat diproses dengan middleware berkesadaran perutean.
  • Metadata dapat dari jenis .NET apa pun.

Konsep perutean

Sistem perutean dibangun di atas alur middleware dengan menambahkan konsep titik akhir yang kuat. Titik akhir mewakili unit fungsionalitas aplikasi yang berbeda satu sama lain dalam hal perutean, otorisasi, dan sejumlah sistem ASP.NET Core.

definisi titik akhir ASP.NET Core

Titik akhir ASP.NET Core adalah:

Kode berikut menunjukkan cara mengambil dan memeriksa titik akhir yang cocok dengan permintaan saat ini:

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

Titik akhir, jika dipilih, dapat diambil dari HttpContext. Sifat-sifatnya dapat diperiksa. Objek titik akhir bersifat tetap dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya diseleksi oleh sistem perutean.

Dalam kode sebelumnya, app.Use mengonfigurasi middleware inline.

Kode berikut menunjukkan bahwa, tergantung di mana app.Use dipanggil dalam alur, mungkin tidak ada titik akhir:

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

Sampel sebelumnya menambahkan Console.WriteLine pernyataan yang menampilkan apakah titik akhir telah dipilih atau tidak. Untuk kejelasan, sampel menetapkan nama tampilan ke endpoint yang diberikan /.

Sampel sebelumnya juga mencakup panggilan ke UseRouting dan UseEndpoints untuk mengontrol dengan tepat kapan middleware ini berjalan dalam alur.

Menjalankan kode ini dengan URL / menampilkan:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

Menjalankan kode ini dengan URL lain akan menampilkan:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Output ini menunjukkan bahwa:

  • Titik akhir selalu null sebelum UseRouting dipanggil.
  • Jika kecocokan ditemukan, titik akhir tidak null antara UseRouting dan UseEndpoints.
  • Middleware UseEndpoints mengakhiri proses ketika kecocokan ditemukan. Terminal middleware didefinisikan nanti dalam artikel ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan metode SetEndpoint untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan tetap mendapatkan manfaat dari penggunaan endpoint. Titik akhir adalah primitif tingkat rendah seperti middleware, dan tidak digabungkan dengan implementasi perutean. Sebagian besar aplikasi tidak perlu mengganti UseRouting dengan logika kustom.

Middleware UseEndpoints dirancang untuk digunakan bersama dengan UseRouting middleware. Logika inti untuk menjalankan titik akhir tidak rumit. Gunakan GetEndpoint untuk mengambil titik akhir, lalu panggil propertinya RequestDelegate .

Kode berikut memperlihatkan bagaimana middleware dapat memengaruhi atau bereaksi terhadap perutean:

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

Contoh sebelumnya menunjukkan dua konsep penting:

  • Middleware dapat berjalan sebelum UseRouting untuk memodifikasi data yang akan dioperasikan oleh perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil rute sebelum titik akhir dijalankan.
    • Middleware yang berjalan antara UseRouting dan UseEndpoints:
      • Biasanya memeriksa metadata untuk memahami titik akhir.
      • Sering membuat keputusan keamanan, seperti yang dilakukan oleh UseAuthorization dan UseCors.
    • Kombinasi middleware dan metadata memungkinkan konfigurasi kebijakan per titik akhir.

Kode sebelumnya menunjukkan contoh middleware kustom yang mendukung kebijakan per titik akhir. Middleware menulis log audit ke konsol mengenai akses ke data sensitif. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan RequiresAuditAttribute metadata. Sampel ini menunjukkan pola opt-in di mana hanya endpoint yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem titik akhir metadata itu fleksibel. Logika ini dapat dirancang dengan cara apa pun yang sesuai dengan kasus penggunaan.

Kode sampel sebelumnya dimaksudkan untuk menunjukkan konsep dasar titik akhir. Sampel tidak ditujukan untuk penggunaan produksi. Versi yang lebih lengkap dari middleware audit log akan:

  • Masuk ke file atau database.
  • Sertakan detail seperti pengguna, alamat IP, nama titik akhir sensitif, dan lainnya.

Metadata kebijakan audit RequiresAuditAttribute didefinisikan sebagai Attribute untuk memudahkan penggunaan dengan kerangka kerja berbasis kelas seperti pengontrol dan SignalR. Saat menggunakan rute ke kode:

  • Metadata dilampirkan dengan API penyusun.
  • Kerangka kerja berbasis kelas mencakup semua atribut pada metode dan kelas yang sesuai saat membuat titik akhir.

Praktik terbaik untuk jenis metadata adalah menentukannya sebagai antarmuka atau atribut. Antarmuka dan atribut memungkinkan penggunaan kembali kode. Sistem metadata fleksibel dan tidak memberlakukan batasan apa pun.

Membandingkan middleware terminal dengan perutean

Contoh berikut menunjukkan middleware terminal dan perutean:

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Gaya middleware yang ditampilkan dengan Approach 1: adalah middleware terminal. Ini disebut middleware terminal karena melakukan operasi yang cocok:

  • Operasi pencocokan dalam sampel sebelumnya menggunakan Path == "/" untuk middleware dan Path == "/Routing" untuk perutean.
  • Ketika kecocokan berhasil, sistem menjalankan beberapa fungsi, kemudian mengembalikan nilai, bukan memanggil next middleware.

Ini disebut middleware terminal karena mengakhiri pencarian, menjalankan beberapa fungsionalitas, lalu mengembalikan.

Daftar berikut membandingkan middleware terminal dengan perutean:

  • Kedua pendekatan memungkinkan penghentian alur pemrosesan:
    • Middleware mengakhiri alur dengan mengembalikan daripada memanggil next.
    • Titik akhir selalu terminal.
  • Middleware terminal memungkinkan penempatan middleware di posisi sembarang dalam pipeline:
  • Middleware terminal memungkinkan kode sebarang untuk menentukan kapan middleware tersebut sesuai:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Routing menyediakan solusi sederhana untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Titik akhir berinteraksi dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan penghubungan manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Petugas untuk memproses permintaan.
  • Kumpulan metadata acak. Metadata digunakan untuk menerapkan aspek lintas berdasarkan kebijakan dan konfigurasi yang diterapkan pada setiap titik akhir.

Middleware terminal dapat menjadi alat yang efektif, tetapi dapat memerlukan:

  • Sejumlah besar pengkodian dan pengujian.
  • Integrasi manual dengan sistem lain untuk mencapai tingkat fleksibilitas yang diinginkan.

Pertimbangkan untuk mengintegrasikan dengan routing sebelum menulis terminal middleware.

Middleware terminal yang ada yang dapat terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir yang sadar akan perutean. MapHealthChecks menunjukkan pola untuk router-ware:

Kode berikut menunjukkan penggunaan MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

Sampel sebelumnya menunjukkan mengapa mengembalikan objek pembangun penting. Mengembalikan objek penyusun memungkinkan pengembang aplikasi untuk mengonfigurasi kebijakan seperti otorisasi untuk titik akhir. Dalam contoh ini, middleware pemeriksaan kesehatan tidak memiliki integrasi langsung dengan sistem otorisasi.

Sistem metadata dibuat sebagai respons terhadap masalah yang dihadapi oleh penulis ekstensibilitas menggunakan middleware terminal. Sangat bermasalah bagi setiap middleware untuk menerapkan integrasinya sendiri dengan sistem otorisasi.

Pencocokan URL

  • Apakah proses perutean cocok dengan permintaan masuk ke titik akhir.
  • Didasarkan pada data di jalur URL dan header.
  • Dapat diperluas untuk mempertimbangkan data apa pun dalam permintaan.

Ketika middleware perutean dieksekusi, ia menetapkan nilai Endpoint dan nilai rute ke fitur permintaan pada HttpContext dari permintaan saat ini.

  • Memanggil HttpContext.GetEndpoint mendapatkan titik akhir.
  • HttpRequest.RouteValues mendapatkan kumpulan nilai rute.

Middleware yang berjalan setelah middleware perutean dapat memeriksa titik akhir dan mengambil tindakan. Misalnya, middleware otorisasi dapat memeriksa kumpulan metadata dari titik akhir untuk menemukan kebijakan akses otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi endpoint yang dipilih dipanggil.

Sistem routing dalam endpoint routing bertanggung jawab atas semua keputusan dispatching. Karena middleware menerapkan kebijakan berdasarkan titik akhir yang dipilih, penting bahwa:

  • Keputusan apa pun yang dapat memengaruhi pengiriman atau penerapan kebijakan keamanan dibuat di dalam sistem perutean.

Warning

Untuk kompatibilitas mundur, saat delegasi titik akhir Pengontrol atau Razor Halaman dijalankan, properti RouteContext.RouteData diatur ke nilai yang sesuai berdasarkan pemrosesan permintaan yang dilakukan sejauh ini.

Jenisnya RouteContext akan ditandai usang dalam rilis mendatang:

  • Migrasikan RouteData.Values ke HttpRequest.RouteValues.
  • Migrasikan RouteData.DataTokens untuk mengambil IDataTokensMetadata dari metadata titik akhir.

Pencocokan URL beroperasi dalam serangkaian fase yang dapat dikonfigurasi. Dalam setiap fase, output adalah sekumpulan kecocokan. Kumpulan pertandingan dapat disempitkan lebih lanjut pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk endpoint yang sesuai. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL berdasarkan sekumpulan titik akhir dan pola rutenya, mengumpulkan semua kecocokan.
  2. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dengan batasan rute yang diterapkan.
  3. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dalam rangkaian MatcherPolicy instans.
  4. Gunakan EndpointSelector untuk membuat keputusan akhir dari daftar sebelumnya.

Daftar titik akhir diprioritaskan sesuai dengan:

Semua titik akhir yang cocok diproses di setiap fase hingga tercapai EndpointSelector . EndpointSelector adalah fase akhir. Ini memilih titik akhir prioritas tertinggi dari kecocokan sebagai kecocokan terbaik. Jika ada kecocokan lain dengan prioritas yang sama dengan kecocokan terbaik, pengecualian pertandingan ambigu akan dilemparkan.

Prioritas rute dihitung berdasarkan templat rute yang lebih spesifik yang diberi prioritas yang lebih tinggi. Misalnya, pertimbangkan templat /hello dan /{message}:

  • Keduanya cocok dengan jalur URL/hello.
  • /hello lebih spesifik dan oleh karena itu prioritas yang lebih tinggi.

Secara umum, prioritas rute melakukan pekerjaan yang baik untuk memilih kecocokan terbaik untuk jenis skema URL yang digunakan dalam praktiknya. Gunakan Order hanya jika perlu untuk menghindari ambiguitas.

Karena jenis ekstensibilitas yang disediakan oleh perutean, sistem perutean tidak mungkin menghitung rute ambigu sebelumnya. Pertimbangkan contoh seperti templat rute /{message:alpha} dan /{message:int}:

  • Batasan alpha hanya cocok dengan karakter alfabet.
  • Batasan int hanya cocok dengan angka.
  • Templat ini memiliki prioritas rute yang sama, tetapi tidak ada satu URL pun yang cocok untuk keduanya.
  • Jika sistem routing melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir penggunaan valid ini.

Warning

Urutan pelaksanaan operasi di dalam UseEndpoints tidak memengaruhi perilaku perutean, dengan satu pengecualian. MapControllerRoute dan MapAreaRoute secara otomatis menetapkan nilai pesanan ke titik akhir mereka berdasarkan urutan yang dipanggil. Ini mensimulasikan perilaku jangka panjang pengontrol tanpa sistem perutean yang menyediakan jaminan yang sama seperti implementasi perutean lama.

Perutean titik akhir di ASP.NET Core:

  • Tidak memiliki konsep rute.
  • Tidak memberikan jaminan pemesanan. Semua titik akhir diproses sekaligus.

Urutan pemilihan prioritas templat dan titik akhir rute

Prioritas templat rute adalah sistem yang menetapkan setiap templat rute nilai berdasarkan seberapa spesifiknya. Prioritas templat rute:

  • Menghindari kebutuhan untuk menyesuaikan urutan titik akhir dalam kasus umum.
  • Upaya untuk memenuhi ekspektasi wajar terhadap perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa /Products/List adalah pilihan yang lebih baik daripada /Products/{id} untuk jalur /Products/List URL. Ini berfungsi karena segmen /List harfiah dianggap memiliki prioritas yang lebih baik daripada segmen /{id}parameter .

Detail cara kerja prioritas digabungkan dengan bagaimana templat rute ditentukan:

  • Templat dengan lebih banyak segmen dianggap lebih spesifik.
  • Segmen dengan teks harfiah dianggap lebih spesifik daripada segmen parameter.
  • Segmen parameter dengan batasan dianggap lebih spesifik daripada satu tanpanya.
  • Segmen kompleks dianggap spesifik sebagai segmen parameter dengan batasan.
  • Parameter yang bersifat mencakup semua adalah yang paling tidak spesifik. Lihat catch-all di bagian Templat rute untuk informasi penting tentang rute catch-all.

Konsep pembuatan URL

Pembuatan URL:

  • Adalah proses di mana perutean dapat membuat jalur URL berdasarkan serangkaian nilai rute.
  • Memungkinkan pemisahan logis antara titik akhir dan URL yang mengaksesnya.

Rute titik akhir meliputi penggunaan API LinkGenerator. LinkGenerator adalah layanan singleton yang tersedia dari DI. LinkGenerator API dapat digunakan di luar konteks permintaan yang dijalankan. Mvc.IUrlHelper dan skenario yang mengandalkan IUrlHelper, seperti Tag Helpers, HTML Helpers, dan Tindakan Hasil, menggunakan LinkGenerator API secara internal untuk menyediakan kemampuan untuk membuat tautan.

Generator tautan didukung oleh konsep alamat dan skema alamat. Skema alamat adalah cara menentukan titik akhir yang harus dipertimbangkan untuk pembuatan tautan. Misalnya, skenario nama rute dan nilai rute yang dikenal banyak pengguna dari pengontrol dan Razor Pages diimplementasikan sebagai skema alamat.

Generator tautan dapat ditautkan ke pengontrol dan Razor Halaman melalui metode ekstensi berikut:

Kelebihan beban metode ini menerima argumen yang menyertakan HttpContext. Metode ini secara fungsional setara dengan Url.Action dan Url.Page, tetapi menawarkan fleksibilitas dan opsi tambahan.

Metode GetPath* paling mirip dengan Url.Action dan Url.Page, karena itu menghasilkan URI yang berisi jalur absolut. Metode GetUri* tersebut selalu menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima HttpContext menghasilkan URI dalam konteks permintaan yang sedang dijalankan. Nilai rute lingkungan, jalur dasar URL, skema, dan host dari permintaan yang sedang dijalankan digunakan kecuali ditimpa.

LinkGenerator dipanggil dengan alamat. Menghasilkan URI terjadi dalam dua langkah:

  1. Alamat terikat ke daftar titik akhir yang sesuai dengan alamat tersebut.
  2. Setiap titik RoutePattern akhir dievaluasi hingga pola rute yang cocok dengan nilai yang disediakan ditemukan. Output yang dihasilkan dikombinasikan dengan bagian URI lain yang disediakan ke generator tautan dan dikembalikan.

Metode yang disediakan oleh LinkGenerator mendukung kemampuan pembuatan tautan standar untuk semua jenis alamat. Cara paling nyaman untuk menggunakan generator tautan adalah melalui metode ekstensi yang melakukan operasi untuk jenis alamat tertentu:

Metode Ekstensi Description
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Warning

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan metode ekstensi GetUri* dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi header Host dari permintaan yang masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak dipercaya dapat dikirim kembali ke klien dalam URI pada tampilan atau halaman. Kami merekomendasikan agar semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi header Host terhadap nilai valid yang diketahui.

  • Gunakan LinkGenerator dengan hati-hati dalam middleware dalam kombinasi dengan Map atau MapWhen. Map* mengubah jalur dasar permintaan eksekusi, yang memengaruhi output pembuatan tautan. LinkGenerator Semua API memungkinkan menentukan jalur dasar. Tentukan jalur dasar kosong untuk membatalkan Map* pengaruh pada pembuatan tautan.

Contoh middleware

Dalam contoh berikut, middleware menggunakan LinkGenerator API untuk membuat tautan ke metode tindakan yang mencantumkan produk penyimpanan. Menggunakan generator tautan dengan menyuntikkannya ke kelas dan panggilan GenerateLink tersedia untuk kelas apa pun di aplikasi:

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

Pola Rute

Token dalam {} menentukan parameter rute yang terikat jika rute cocok. Lebih dari satu parameter rute dapat didefinisikan dalam segmen rute, tetapi parameter rute harus dipisahkan oleh nilai harfiah. Contohnya:

{controller=Home}{action=Index}

bukan rute yang valid, karena tidak ada nilai harfiah antara {controller} dan {action}. Parameter rute harus memiliki nama dan mungkin memiliki atribut tambahan yang ditentukan.

Teks literal selain parameter rute (misalnya, {id}) dan pemisah / jalur harus cocok dengan teks di URL. Pencocokan teks tidak membedakan huruf besar atau kecil dan didasarkan pada representasi yang didekodekan dari jalur pada URL. Untuk mencocokkan pemisah parameter rute literal { atau }, hindari pemisah tersebut dengan mengulangi karakter tersebut. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

  • Dapat digunakan sebagai awalan untuk parameter rute untuk mengikat ke sisa URI.
  • Disebut sebagai parameter catch-all. Misalnya, blog/{**slug}:
    • Cocok dengan URI apa pun yang dimulai dengan blog/ dan memiliki nilai apa pun yang mengikutinya.
    • Nilai setelah blog/ ditetapkan ke nilai rute slug.

Warning

Parameter catch-all mungkin salah mencocokkan rute karena bug dalam perutean. Aplikasi yang terpengaruh oleh bug ini memiliki karakteristik berikut:

  • Rute tangkap-semua, misalnya, {**slug}"
  • Rute catch-all gagal mencocokkan permintaan yang seharusnya cocok.
  • Menghapus rute lain membuat rute catch-all mulai berfungsi.

Lihat bug GitHub 18677 dan 16579 misalnya kasus yang mengenai bug ini.

Perbaikan yang perlu diaktifkan untuk bug ini tersedia dalam .NET Core 3.1.301 atau SDK yang lebih baru. Kode program berikut menetapkan sakelar internal yang mengatasi bug ini:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Parameter catch-all juga dapat mencocokkan string kosong.

Parameter catch-all mengubah karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter-jalur pemisah /. Misalnya, rute foo/{*path} dengan nilai rute { path = "my/path" } menghasilkan foo/my%2Fpath. Perhatikan garis miring maju yang lolos. Untuk karakter pemisah jalur pulang-pergi, gunakan awalan ** parameter rute. Rute foo/{**path} dengan { path = "my/path" } menghasilkan foo/my/path.

Pola URL yang mencoba mengambil nama file dengan ekstensi file opsional memiliki pertimbangan tambahan. Misalnya, pertimbangkan templat files/{filename}.{ext?}. Ketika nilai untuk keduanya filename dan ext ada, kedua nilai diisi. Jika hanya nilai untuk filename yang ada di URL, rute cocok karena trailing . bersifat opsional. URL berikut cocok dengan rute ini:

  • /files/myFile.txt
  • /files/myFile

Parameter rute mungkin memiliki nilai default yang ditunjuk dengan menentukan nilai default setelah nama parameter dipisahkan oleh tanda sama dengan (=). Misalnya, {controller=Home} mendefinisikan Home sebagai nilai default untuk controller. Nilai default digunakan jika tidak ada nilai yang ada di URL untuk parameter . Parameter rute dibuat opsional dengan menambahkan tanda tanya (?) ke akhir nama parameter. Contohnya,id?. Perbedaan antara nilai opsional dan parameter rute default adalah:

  • Parameter rute dengan nilai default selalu menghasilkan nilai.
  • Parameter opsional hanya memiliki nilai saat nilai disediakan oleh URL permintaan.

Parameter rute mungkin memiliki batasan yang harus memenuhi nilai rute yang diperoleh dari URL. Menambahkan : dan nama batasan setelah nama parameter rute menentukan batasan sebaris pada parameter rute. Jika batasan memerlukan argumen, batasan tersebut diapit dalam tanda kurung (...) setelah nama batasan. Beberapa batasan sebaris dapat ditentukan dengan menambahkan satu : dan nama batasan lainnya.

Nama batasan dan argumen diteruskan ke layanan IInlineConstraintResolver untuk menciptakan instans IRouteConstraint yang akan digunakan dalam pemrosesan URL. Misalnya, templat blog/{article:minlength(10)} rute menentukan minlength batasan dengan argumen 10. Untuk informasi selengkapnya tentang batasan rute dan daftar batasan yang disediakan oleh kerangka kerja, lihat bagian Batasan rute.

Parameter rute juga dapat memiliki pengubah parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti pembatas, transformator parameter dapat ditambahkan secara sebaris ke parameter rute dengan menambahkan : dan nama transformator setelah nama parameter rute. Misalnya, templat rute blog/{article:slugify} menentukan sebuah transformer slugify. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Transformer parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Pola Rute Contoh Pencocokan URI Permintaan URI...
hello /hello Hanya cocok dengan jalur tunggal /hello.
{Page=Home} / Mencocokkan dan mengatur Page menjadi Home.
{Page=Home} /Contact Mencocokkan dan mengatur Page menjadi Contact.
{controller}/{action}/{id?} /Products/List Memetakan ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Memetakan ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Pemetaan ke controller Home dan metode Index. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Pemetaan ke controller Products dan metode Index. id diabaikan.

Menggunakan template umumnya adalah pendekatan paling sederhana untuk perutean. Batasan dan default juga dapat ditentukan di luar templat rute.

Segmen kompleks

Segmen kompleks diproses dengan mencocokkan pemisah harfiah dari kanan ke kiri dengan cara yang tidak serakah . Misalnya, [Route("/a{b}c{d}")] adalah segmen yang kompleks. Segmen kompleks bekerja dengan cara tertentu yang harus dipahami agar berhasil digunakan. Contoh di bagian ini menunjukkan mengapa segmen kompleks hanya benar-benar berfungsi dengan baik ketika teks pemisah tidak muncul di dalam nilai parameter. Menggunakan regex lalu mengekstrak nilai secara manual diperlukan untuk kasus yang lebih kompleks.

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ini adalah ringkasan langkah-langkah yang dilakukan oleh perutean dengan templat /a{b}c{d} dan jalur /abcd URL. | digunakan untuk membantu memvisualisasikan cara kerja algoritma:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /abcd juga dicari dari kanan dan menemukan /ab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Tidak ada teks yang tersisa dan tidak ada templat rute yang tersisa, jadi ini cocok.

Berikut adalah contoh kasus negatif menggunakan templat /a{b}c{d} yang sama dan jalur URL /aabcd. | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /aabcd juga dicari dari kanan dan menemukan /aab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Pada titik ini ada teks ayang tersisa , tetapi algoritma telah kehabisan templat rute untuk diurai, jadi ini bukan kecocokan.

Karena algoritma yang cocok tidak serakah:

  • Ini cocok dengan jumlah teks sekecil mungkin di setiap langkah.
  • Setiap kasus di mana nilai pemisah muncul di dalam nilai parameter menghasilkan tidak cocok.

Ekspresi reguler memberikan kendali yang jauh lebih besar terhadap perilaku pencocokannya.

Pencocokan serakah, juga dikenal sebagai pencocokan maksimal berupaya menemukan kecocokan terpanjang dalam teks input yang memenuhi pola regex. Pencocokan yang tidak serakah, juga dikenal sebagai pencocokan malas, mencari kecocokan sesingkat mungkin dalam teks input yang memenuhi pola regex.

Pengaturan Rute dengan Karakter Khusus

Pengarutan dengan karakter khusus dapat menyebabkan hasil yang tidak terduga. Misalnya, pertimbangkan pengontrol dengan metode tindakan berikut:

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Ketika string id berisi nilai yang dikodekan berikut, hasil yang tidak terduga mungkin terjadi:

ASCII Encoded
/ %2F
+

Parameter rute tidak selalu didekodekan URL. Masalah ini dapat diatasi di masa mendatang. Untuk informasi selengkapnya, lihat masalah GitHub ini;

Batasan rute

Batasan rute dijalankan ketika kecocokan telah terjadi pada URL masuk dan jalur URL ditokenisasi ke dalam nilai rute. Batasan rute umumnya memeriksa nilai rute yang terkait melalui templat rute dan membuat keputusan yang benar atau salah tentang apakah nilai tersebut dapat diterima. Beberapa batasan rute menggunakan data di luar nilai rute untuk mempertimbangkan apakah permintaan dapat dirutekan. Misalnya, HttpMethodRouteConstraint dapat menerima atau menolak permintaan berdasarkan kata kerja HTTP-nya. Batasan digunakan dalam permintaan perutean dan pembuatan tautan.

Warning

Jangan gunakan batasan untuk validasi input. Jika batasan digunakan untuk validasi input, input yang 404 tidak valid menghasilkan respons Tidak Ditemukan. Input yang tidak valid harus menghasilkan 400 Permintaan Buruk dengan pesan kesalahan yang sesuai. Batasan rute digunakan untuk memisahkan rute serupa, bukan untuk memvalidasi input untuk rute tertentu.

Tabel berikut menunjukkan batasan rute default dan perilaku yang diharapkan:

batasan Example Contoh Kecocokan Notes
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Sesuai dengan atau false. Case-insensitive
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Cocok dengan nilai yang valid DateTime dalam budaya invarian. Lihat peringatan sebelumnya
decimal {price:decimal} 49.99, -1,000.01 Cocok dengan nilai yang valid decimal dalam budaya invarian. Lihat peringatan sebelumnya
double {weight:double} 1.234, -1,001.01e8 Cocok dengan nilai yang valid double dalam budaya invarian. Lihat peringatan sebelumnya
float {weight:float} 1.234, -1,001.01e8 Cocok dengan nilai yang valid float dalam budaya invarian. Lihat peringatan sebelumnya
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Mencocokkan dengan nilai Guid yang valid
long {ticks:long} 123456789, -123456789 Mencocokkan dengan nilai long yang valid
minlength(value) {username:minlength(4)} Rick String harus minimal 4 karakter
maxlength(value) {filename:maxlength(8)} MyFile String tidak boleh lebih dari 8 karakter
length(length) {filename:length(12)} somefile.txt Panjang string harus persis 12 karakter
length(min,max) {filename:length(8,16)} somefile.txt Panjang string harus minimal 8 dan tidak lebih dari 16 karakter
min(value) {age:min(18)} 19 Nilai bilangan bulat harus minimal 18
max(value) {age:max(120)} 91 Nilai bilangan bulat tidak boleh lebih dari 120
range(min,max) {age:range(18,120)} 91 Nilai bilangan bulat harus setidaknya 18 tetapi tidak lebih dari 120
alpha {name:alpha} Rick String harus terdiri dari satu atau beberapa karakter alfabet, a-z dan tidak peka huruf besar/kecil
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 String harus cocok dengan ekspresi reguler. Lihat tips tentang menentukan ekspresi reguler
required {name:required} Rick Memberlakukan bahwa nilai eksplisit (bukan nilai sekitar) ada selama pembuatan URL.
file {filename:file} myfile.txt String dapat berisi segmen jalur, tetapi segmen terakhirnya harus memiliki titik (.) dan diikuti oleh satu atau beberapa karakter non-titik
nonfile {page:nonfile} PageName String tidak boleh memiliki titik di segmen jalur terakhirnya yang diikuti oleh satu atau beberapa karakter non-titik (.)

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Beberapa batasan yang dipisahkan dengan titik dua dapat diterapkan ke satu parameter. Misalnya, batasan berikut membatasi parameter ke nilai bilangan bulat 1 atau lebih besar:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke CLR jenis int atau DateTime. Batasan ini mengasumsikan bahwa URL tidak dapat dilokalkan. Batasan rute yang disediakan kerangka kerja tidak mengubah nilai yang disimpan dalam nilai rute. Semua nilai rute yang diurai dari URL disimpan sebagai string. Misalnya, float batasan mencoba mengonversi nilai rute menjadi float, tetapi nilai yang dikonversi hanya digunakan untuk memverifikasi bahwa nilai tersebut dapat dikonversi ke float.

Ekspresi reguler dalam batasan

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ekspresi reguler dapat ditentukan sebagai pembatasan sebaris menggunakan batasan regex(...) route. Metode dalam MapControllerRoute keluarga juga menerima objek harfiah batasan. Jika formulir tersebut digunakan, nilai string ditafsirkan sebagai ekspresi reguler.

Kode berikut menggunakan batasan regex sebaris:

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

Kode berikut menggunakan objek literal untuk menentukan batasan regex:

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

Kerangka kerja ASP.NET Core ditambahkan RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ke konstruktor ekspresi reguler. Lihat RegexOptions untuk deskripsi anggota ini.

Ekspresi reguler menggunakan pemisah dan token yang mirip dengan yang digunakan oleh perutean dan bahasa C#. Token ekspresi reguler harus diloloskan. Untuk menggunakan ekspresi ^\d{3}-\d{2}-\d{4}$ reguler dalam batasan sebaris, gunakan salah satu hal berikut ini:

  • Ganti \ karakter yang disediakan dalam string sebagai \\ karakter dalam file sumber C# untuk menghindari \ karakter escape string.
  • String literal verbatim.

Untuk menghindari karakter pemisah parameter perutean seperti {, }, [, ], gandakan karakter tersebut dalam ekspresi, misalnya, {{, }}, [[, ]]. Tabel berikut ini memperlihatkan ekspresi reguler dan versi escape-nya:

Ekspresi reguler Ekspresi reguler yang di-escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Ekspresi reguler yang digunakan dalam perutean sering dimulai dengan karakter ^ dan cocok dengan posisi awal string. Ekspresi sering diakhiri dengan karakter $ dan mengakhiri string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. Tanpa karakter ^ dan $, ekspresi reguler mencocokkan substrak apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Expression String Match Comment
[a-z]{2} hello Yes Kecocokan substring
[a-z]{2} 123abc456 Yes Kecocokan substring
[a-z]{2} mz Yes Ekspresi yang cocok
[a-z]{2} MZ Yes Tidak peka huruf besar/kecil
^[a-z]{2}$ hello No Lihat ^ dan $ di atas
^[a-z]{2}$ 123abc456 No Lihat ^ dan $ di atas

Untuk informasi selengkapnya tentang sintaks ekspresi reguler, lihat .NET Framework Regular Expressions.

Untuk membatasi parameter ke sekumpulan nilai yang mungkin diketahui, gunakan ekspresi reguler. Misalnya, {action:regex(^(list|get|create)$)} hanya menyamai nilai jalur action dengan list, get, atau create. Jika diteruskan ke kamus batasan, string ^(list|get|create)$ setara. Batasan yang diteruskan dalam kamus batasan yang tidak cocok dengan salah satu batasan yang diketahui juga diperlakukan sebagai ekspresi reguler. Batasan yang diteruskan dalam templat yang tidak cocok dengan salah satu batasan yang diketahui tidak diperlakukan sebagai ekspresi reguler.

Pembatas rute khusus

Batasan rute kustom dapat dibuat dengan mengimplementasikan IRouteConstraint antarmuka. Antarmuka IRouteConstraint berisi Match, yang mengembalikan true jika batasan terpenuhi dan false sebaliknya.

Batasan rute kustom jarang diperlukan. Sebelum menerapkan batasan rute kustom, pertimbangkan alternatif, seperti pengikatan model.

Folder ASP.NET Core Constraints memberikan contoh yang baik untuk membuat batasan. Misalnya, GuidRouteConstraint.

Untuk menggunakan kustom IRouteConstraint, jenis batasan rute harus didaftarkan dengan aplikasi ConstraintMap di kontainer layanan. ConstraintMap adalah kamus yang memetakan kunci batasan rute ke IRouteConstraint implementasi yang memvalidasi batasan tersebut. Aplikasi ConstraintMap dapat diperbarui sebagai bagian dari Program.cs panggilan AddRouting atau dengan mengonfigurasi RouteOptions langsung dengan builder.Services.Configure<RouteOptions>. Contohnya:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Batasan sebelumnya diterapkan dalam kode berikut:

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

Implementasi NoZeroesRouteConstraint mencegah 0 digunakan dalam parameter rute:

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Kode sebelumnya:

  • Mencegah 0 di segmen {id} dari rute.
  • Ditunjukkan untuk memberikan contoh dasar penerapan batasan kustom. Ini tidak boleh digunakan dalam aplikasi produksi.

Pendekatan yang lebih baik untuk mencegah id yang mengandung 0 dari pemrosesan adalah dengan menggunakan kode berikut:

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

Kode sebelumnya memiliki keuntungan berikut daripada NoZeroesRouteConstraint pendekatan:

  • Ini tidak memerlukan batasan kustom.
  • Ini mengembalikan kesalahan yang lebih deskriptif ketika parameter rute menyertakan 0.

Parameter transformator

Pengubah parameter

Misalnya, transformator parameter kustom slugify dalam pola rute blog\{article:slugify} dengan Url.Action(new { article = "MyTestArticle" }) menghasilkan blog\my-test-article.

Pertimbangkan implementasi berikut IOutboundParameterTransformer :

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is null)
        {
            return null;
        }

        return Regex.Replace(
            value.ToString()!,
                "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100))
            .ToLowerInvariant();
    }
}

Untuk menggunakan transformator parameter dalam pola rute, konfigurasikan dengan ConstraintMap pada Program.cs.

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

Kerangka kerja ASP.NET Core menggunakan transformator parameter untuk mengubah URI tempat titik akhir diselesaikan. Misalnya, transformator parameter mengubah nilai rute yang digunakan untuk mencocokkan area, , controlleraction, dan page:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

Dengan templat rute sebelumnya, tindakan SubscriptionManagementController.GetAll dicocokkan dengan URI /subscription-management/get-all. Transformator parameter tidak mengubah nilai rute yang digunakan untuk menghasilkan tautan. Misalnya, Url.Action("GetAll", "SubscriptionManagement") menghasilkan /subscription-management/get-all.

ASP.NET Core menyediakan konvensi API untuk menggunakan transformator parameter dengan rute yang dihasilkan:

Referensi pembuatan URL

Bagian ini berisi referensi untuk algoritma yang diterapkan oleh pembuatan URL. Dalam praktiknya, sebagian besar contoh kompleks pembuatan URL menggunakan pengontrol atau halaman Razor. Lihat perutean di pengontrol untuk keterangan lebih lanjut.

Proses pembuatan URL dimulai dengan panggilan ke LinkGenerator.GetPathByAddress atau metode serupa. Metode ini disediakan dengan alamat, sekumpulan nilai rute, dan informasi opsional tentang permintaan saat ini dari HttpContext.

Langkah pertama adalah menggunakan alamat untuk menyelesaikan sekumpulan titik akhir kandidat menggunakan IEndpointAddressScheme<TAddress> yang cocok dengan jenis alamat.

Setelah kumpulan kandidat ditemukan oleh skema alamat, titik akhir diurutkan dan diproses secara berulang sampai operasi pembuatan URL berhasil. Pembuatan URL tidak memeriksa ambiguitas, hasil pertama yang dikembalikan adalah hasil akhir.

Pemecahan masalah pembuatan URL dengan pengelogan

Langkah pertama dalam pemecahan masalah pembuatan URL adalah mengatur tingkat pengelogan Microsoft.AspNetCore.Routing ke TRACE. LinkGenerator mencatat banyak detail tentang pemrosesannya yang dapat berguna untuk memecahkan masalah.

Lihat Referensi pembuatan URL untuk detail tentang pembuatan URL.

Addresses

Alamat adalah konsep dalam pembuatan URL yang digunakan untuk mengikat panggilan ke generator tautan ke sekumpulan titik akhir kandidat.

Alamat adalah konsep yang dapat diperluas yang dilengkapi dengan dua implementasi secara default:

  • Menggunakan nama titik akhir (string) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan nama rute pada MVC.
    • Menggunakan jenis metadata IEndpointNameMetadata.
    • Menyelesaikan string yang disediakan terhadap metadata semua titik akhir terdaftar.
    • Memberikan pengecualian pada startup jika beberapa titik akhir menggunakan nama yang sama.
    • Direkomendasikan untuk penggunaan tujuan umum di luar pengontrol dan Razor Halaman.
  • Menggunakan nilai rute (RouteValuesAddress) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan pengontrol dan pembuatan URL lama untuk Pages.
    • Sangat kompleks untuk diperpanjang dan di-debug.
    • Menyediakan implementasi yang digunakan oleh IUrlHelper, Pembantu Tag, Pembantu HTML, Hasil Tindakan, dll.

Peran skema alamat adalah membuat hubungan antara alamat dan titik akhir yang cocok dengan kriteria semena-mena:

  • Skema nama titik akhir melakukan pencarian kamus dasar.
  • Skema nilai rute memiliki subset algoritma set terbaik yang kompleks.

Nilai sekitar dan nilai eksplisit

Dari permintaan saat ini, perutean mengakses nilai rute permintaan HttpContext.Request.RouteValuessaat ini . Nilai yang terkait dengan permintaan saat ini disebut sebagai nilai ambient. Untuk tujuan kejelasan, dokumentasi mengacu pada nilai rute yang diteruskan ke metode sebagai nilai eksplisit.

Contoh berikut menunjukkan nilai sekitar dan nilai eksplisit. Ini menyediakan nilai pengaturan lingkungan dari permintaan saat ini dan nilai eksplisit.

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

Kode sebelumnya:

Kode berikut hanya menyediakan nilai eksplisit dan tanpa nilai sekitar:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Metode sebelumnya mengembalikan /Home/Subscribe/17

Kode berikut mengembalikan WidgetController/Widget/Subscribe/17:

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

Kode berikut menyediakan pengontrol dari nilai sekitar dalam permintaan saat ini dan nilai eksplisit:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Dalam kode sebelumnya:

  • /Gadget/Edit/17 dikembalikan.
  • Url mendapatkan IUrlHelper.
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama action dan nilai route yang ditentukan.

Kode berikut menyediakan nilai sekitar dari permintaan saat ini dan nilai eksplisit:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Kode sebelumnya mengatur url ke /Edit/17 ketika Halaman Edit Razor memuat arahan halaman berikut ini:

@page "{id:int}"

Jika halaman Edit tidak berisi "{id:int}" templat rute, url adalah /Edit?id=17.

Perilaku MVC IUrlHelper menambahkan lapisan kompleksitas selain aturan yang dijelaskan di sini:

  • IUrlHelper selalu menyediakan nilai rute dari permintaan saat ini sebagai nilai ambien.
  • IUrlHelper.Action selalu menyalin nilai rute action dan controller saat ini sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali digantikan.
  • IUrlHelper.Page selalu menggantikan nilai rute saat ini handler dengan null sebagai nilai eksplisit, kecuali dikesampingkan.

Pengguna sering terkejut dengan detail perilaku nilai sekitar, karena MVC tampaknya tidak mengikuti aturannya sendiri. Untuk alasan historis dan kompatibilitas, nilai rute tertentu seperti action, , controllerpage, dan handler memiliki perilaku kasus khusus mereka sendiri.

Fungsionalitas yang setara yang disediakan oleh LinkGenerator.GetPathByAction dan LinkGenerator.GetPathByPage menduplikasi anomali IUrlHelper ini untuk kompatibilitas.

Proses pembuatan URL

Setelah kumpulan titik akhir kandidat ditemukan, algoritma pembuatan URL:

  • Memproses titik akhir secara berulang.
  • Mengembalikan hasil pertama yang berhasil.

Langkah pertama dalam proses ini disebut pembatalan nilai rute. Invalidasi nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai lingkungan yang digunakan dan mana yang diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai ambien adalah mereka berusaha mengurangi pengetikan yang perlu dilakukan pengembang aplikasi, dalam beberapa kasus umum. Secara tradisional, skenario di mana nilai sekitar bermanfaat terkait dengan MVC:

  • Saat menautkan ke tindakan lain di pengontrol yang sama, nama pengontrol tidak perlu ditentukan.
  • Saat menautkan ke pengontrol lain di area yang sama, nama area tidak perlu ditentukan.
  • Saat menautkan ke metode tindakan yang sama, nilai rute tidak perlu ditentukan.
  • Saat menautkan ke bagian lain aplikasi, Anda tidak ingin membawa nilai rute yang tidak memiliki arti di bagian aplikasi tersebut.

Panggilan ke LinkGenerator atau IUrlHelper yang mengembalikan null biasanya disebabkan oleh tidak memahami ketidakvalidan nilai rute. Memecahkan masalah pembatalan nilai rute dengan secara eksplisit menentukan lebih banyak nilai rute untuk melihat apakah itu menyelesaikan masalah.

Pembatalan nilai rute berfungsi dengan asumsi bahwa skema URL aplikasi bersifat hierarkis, dengan hierarki yang terbentuk dari kiri-ke-kanan. Pertimbangkan templat {controller}/{action}/{id?} rute pengontrol dasar untuk mendapatkan rasa intuitif tentang cara kerjanya dalam praktik. Perubahan pada nilai membatalkan semua nilai rute yang muncul di sebelah kanan. Ini mencerminkan asumsi tentang hierarki. Jika aplikasi memiliki nilai lingkungan untuk id, dan operasi menentukan nilai yang berbeda untuk controller:

  • id tidak akan digunakan kembali karena {controller} berada di sebelah kiri {id?}.

Beberapa contoh yang menunjukkan prinsip ini:

  • Jika nilai eksplisit berisi nilai untuk id, nilai sekitar untuk id diabaikan. Nilai lingkungan untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai ambien untuk controller dapat digunakan. Jika nilai eksplisit untuk action berbeda dari nilai sekitar untuk action, id nilai tidak akan digunakan. Jika nilai eksplisit untuk action sama dengan nilai sekitar untuk action, id nilai dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk controller, nilai sekitar apa pun untuk controller diabaikan. Jika nilai eksplisit untuk controller berbeda dari nilai lingkungan untuk controller, nilai action dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai ambien untuk controller, action dan id nilai dapat digunakan.

Proses ini semakin rumit dengan adanya rute atribut dan rute konvensional khusus. Rute konvensional pengontrol seperti {controller}/{action}/{id?} menentukan hierarki menggunakan parameter rute. Untuk rute konvensional dedikasi dan rute atribut ke pengontrol dan Razor Halaman:

  • Ada hierarki nilai rute.
  • Mereka tidak muncul di templat.

Untuk kasus ini, pembuatan URL menentukan konsep nilai yang diperlukan. Titik akhir yang dibuat oleh pengontrol dan Razor Halaman memiliki nilai yang telah ditetapkan yang memungkinkan pembatalan nilai rute berfungsi dengan baik.

Algoritma invalidasi nilai rute secara rinci:

  • Nama nilai yang diperlukan dikombinasikan dengan parameter rute, lalu diproses dari kiri ke kanan.
  • Untuk setiap parameter, nilai sekitar dan nilai eksplisit dibandingkan:
    • Jika nilai sekitar dan nilai eksplisit sama, proses berlanjut.
    • Jika nilai sekitar ada dan nilai eksplisit tidak, nilai sekitar digunakan saat membuat URL.
    • Jika nilai sekitar tidak ada dan hanya nilai eksplisit yang ada, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika terdapat nilai ambien dan nilai eksplisit, dan kedua nilai tersebut berbeda, abaikan nilai ambien dan semua nilai ambien berikutnya.

Pada titik ini, operasi pembuatan URL siap untuk mengevaluasi batasan rute. Kumpulan nilai yang diterima dikombinasikan dengan nilai default parameter, yang disediakan untuk batasan. Jika semua batasan lolos, operasi berlanjut.

Selanjutnya, nilai yang diterima dapat digunakan untuk memperluas templat rute. Templat rute diproses:

  • Dari kiri-ke-kanan.
  • Setiap parameter digantikan dengan nilai yang diterima.
  • Dengan kasus khusus berikut:
    • Jika nilai yang diterima kehilangan nilai dan parameter memiliki nilai default, nilai default akan digunakan.
    • Jika nilai yang diterima kehilangan nilai dan parameter bersifat opsional, pemrosesan berlanjut.
    • Jika ada parameter rute di sebelah kanan parameter opsional yang hilang memiliki nilai, operasi gagal.
    • Parameter bernilai default yang bersebelahan dan parameter opsional diciutkan jika memungkinkan.

Nilai yang disediakan secara eksplisit yang tidak cocok dengan segmen rute ditambahkan ke string kueri. Tabel berikut ini memperlihatkan hasilnya saat menggunakan template rute {controller}/{action}/{id?}.

Nilai Lingkungan Nilai Eksplisit Result
pengontrol = "Home" action = "Tentang" /Home/About
pengontrol = "Home" controller = "Pesanan", action = "Tentang" /Order/About
controller = "Home", warna = "Merah" action = "Tentang" /Home/About
pengontrol = "Home" action = "Tentang", color = "Merah" /Home/About?color=Red

Urutan parameter rute opsional

Parameter rute opsional harus datang setelah semua parameter rute dan literal yang diperlukan. Dalam kode berikut, parameter id dan name harus datang setelah parameter color:

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
    // GET /api/my/red/2/joe
    // GET /api/my/red/2
    // GET /api/my
    [HttpGet("{color}/{id:int?}/{name?}")]
    public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
    {
        return Ok($"{color} {id} {name ?? ""}");
    }
}

Masalah dengan pembatalan nilai rute

Kode berikut menunjukkan contoh skema generasi URL yang tidak didukung oleh rute:

app.MapControllerRoute(
    "default",
    "{culture}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

Dalam kode sebelumnya, culture parameter rute digunakan untuk pelokalan. Keinginannya adalah agar culture parameter selalu diterima sebagai nilai sekitar. Namun, culture parameter tidak diterima sebagai nilai sekitar karena cara kerja nilai yang diperlukan:

  • "default" Dalam templat rute, culture parameter rute berada di sebelah kiri controller, sehingga perubahan menjadi controller tidak akan membatalkan culture.
  • "blog" Dalam templat rute, culture parameter rute dianggap berada di sebelah kanan controller, yang muncul dalam nilai yang diperlukan.

Mengurai jalur URL dengan LinkParser

Kelas LinkParser menambahkan dukungan untuk mengurai jalur URL ke dalam sekumpulan nilai rute. Metode ini ParsePathByEndpointName mengambil nama titik akhir dan jalur URL, dan mengembalikan sekumpulan nilai rute yang diekstrak dari jalur URL.

Dalam contoh pengontrol berikut, GetProduct aksi menggunakan templat rute api/Products/{id} dan memiliki Name dari GetProduct.

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

Di kelas pengontrol yang sama, AddRelatedProduct tindakan mengharapkan jalur URL, pathToRelatedProduct, yang dapat disediakan sebagai parameter string kueri:

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

Dalam contoh sebelumnya, aksi AddRelatedProduct mengekstrak nilai rute id dari jalur URL. Misalnya, dengan jalur URL/api/Products/1, nilai relatedProductId ditetapkan ke 1. Pendekatan ini memungkinkan klien API untuk menggunakan jalur URL saat merujuk ke sumber daya, tanpa memerlukan pengetahuan tentang bagaimana URL tersebut disusun.

Mengonfigurasi metadata titik akhir

Tautan berikut ini menyediakan informasi tentang cara mengonfigurasi metadata titik akhir:

Pencocokan host dalam rute dengan RequireHost

RequireHost menerapkan batasan ke rute yang memerlukan host yang ditentukan. Parameter RequireHost atau [Host] dapat berupa:

  • Host: www.domain.com, cocok www.domain.com dengan port apa pun.
  • Host dengan wildcard: *.domain.com, cocok dengan www.domain.com, subdomain.domain.com, atau www.subdomain.domain.com pada port apa pun.
  • Port: *:5000, cocok dengan port 5000 dengan host apa pun.
  • Host dan port: www.domain.com:5000 atau *.domain.com:5000, cocok dengan host dan port.

Beberapa parameter dapat ditentukan menggunakan RequireHost atau [Host]. Batasan cocok dengan host yang valid untuk salah satu parameter. Misalnya, [Host("domain.com", "*.domain.com")] cocok dengan , domain.com, dan www.domain.comsubdomain.domain.com.

Kode berikut menggunakan RequireHost untuk memerlukan host yang ditentukan pada rute:

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Kode berikut menggunakan [Host] atribut pada pengontrol untuk memerlukan salah satu host yang ditentukan:

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Ketika atribut [Host] diterapkan ke pengendali dan metode aksi

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Warning

API yang bergantung pada Host header, seperti HttpRequest.Host dan RequireHost, dapat mengalami potensi spoofing oleh klien.

Untuk mencegah spoofing host dan port, gunakan salah satu pendekatan berikut:

Kelompok Rute

Metode MapGroup ekstensi membantu mengatur grup titik akhir dengan awalan umum dan mengurangi kode berulang. Gunakan metode ini untuk menyesuaikan seluruh grup titik akhir dengan satu panggilan ke metode seperti RequireAuthorization dan WithMetadata yang menambahkan metadata titik akhir.

Misalnya, kode berikut membuat dua grup titik akhir serupa:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

Dalam skenario ini, Anda dapat menggunakan alamat relatif untuk Location header dalam hasil 201 Created :

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

Grup titik akhir pertama cocok hanya dengan permintaan yang diawali dengan /public/todos dan dapat diakses tanpa autentikasi apa pun. Grup endpoint kedua hanya cocok dengan permintaan yang diawali dengan awalan /private/todos dan memerlukan autentikasi.

QueryPrivateTodos adalah fungsi lokal yang memodifikasi TodoDb parameter handler rute, untuk memungkinkan mereka mengakses dan menyimpan data todo privat. QueryPrivateTodos berfungsi sebagai pabrik pembuat filter titik akhir.

Grup rute juga mendukung grup berlapis dan pola awalan kompleks dengan parameter rute dan batasan. Dalam contoh berikut, handler rute yang dipetakan ke user grup dapat menangkap parameter rute {org} dan {group} yang ditentukan dalam prefiks grup luar.

Awalan juga dapat kosong. Pendekatan ini dapat berguna untuk menambahkan metadata titik akhir atau filter ke sekelompok titik akhir tanpa mengubah pola rute.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Menambahkan filter atau metadata ke grup menghasilkan perilaku yang sama seperti menambahkannya satu per satu ke setiap titik akhir (sebelum menambahkan filter atau metadata tambahan yang mungkin ada dalam grup dalam atau titik akhir tertentu).

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

Dalam contoh sebelumnya, filter luar mencatat permintaan masuk terlebih dahulu sebelum filter dalam, meskipun filter luar ditambahkan pada posisi kedua. Karena filter diterapkan ke grup yang berbeda, urutan penambahannya relatif satu sama lain tidak mempengaruhi hasilnya. Urutan di mana filter ditambahkan penting saat diterapkan ke grup yang sama atau titik akhir tertentu.

Permintaan untuk /outer/inner/ mencatat data berikut:

/outer group filter
/inner group filter
MapGet filter

Panduan performa untuk perutean

Ketika aplikasi mengalami masalah kinerja, perutean sering dicurigai sebagai penyebab masalah. Alasan perutean dicurigai adalah bahwa kerangka kerja seperti pengontrol dan Razor Pages melaporkan jumlah waktu yang dihabiskan di dalam kerangka kerja dalam pesan pengelogan mereka. Ketika ada perbedaan signifikan antara waktu yang dilaporkan oleh pengontrol dan total waktu permintaan:

  • Pengembang menghilangkan kode aplikasi mereka sebagai sumber masalah.
  • Hal yang umum untuk mengasumsikan perutean menjadi penyebabnya.

Routing diukur kinerjanya menggunakan ribuan titik akhir. Tidak mungkin aplikasi umum akan mengalami masalah performa hanya dengan menjadi terlalu besar. Akar penyebab paling umum dari performa perutean yang lambat biasanya adalah middleware kustom yang bertingkah buruk.

Sampel kode berikut ini menunjukkan teknik dasar untuk mempersempit sumber penundaan:

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Mengatur waktu rute

  • Interleave setiap middleware dengan salinan middleware pengaturan waktu yang ditampilkan dalam kode sebelumnya.
  • Tambahkan pengidentifikasi unik untuk menghubungkan data waktu dengan kode.

Ini adalah cara dasar untuk mempersempit penundaan ketika signifikan, misalnya, lebih dari 10ms. Mengurangkan Time 2 dari Time 1 menunjukkan waktu yang dihabiskan dalam middleware UseRouting.

Kode berikut menggunakan pendekatan yang lebih ringkas untuk kode waktu sebelumnya:

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

Fitur routing yang berpotensi mahal

Daftar berikut memberikan wawasan tentang fitur perutean yang relatif lebih mahal dibandingkan dengan templat rute dasar.

  • Ekspresi reguler: Dimungkinkan untuk menulis ekspresi reguler yang kompleks, atau memiliki waktu berjalan lama dengan sejumlah kecil input.
  • Segmen kompleks ({x}-{y}-{z}):
    • Secara signifikan lebih mahal daripada mengurai segmen jalur URL reguler.
    • Mengakibatkan lebih banyak substring dialokasikan.
  • Akses data sinkron: Aplikasi-aplikasi kompleks memiliki akses database sebagai bagian dari pengarahan mereka. Gunakan titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext, yang asinkron.

Panduan untuk tabel rute besar

Secara default, ASP.NET Core menggunakan algoritma perutean yang menukar penggunaan memori untuk waktu CPU. Ini memiliki efek yang bagus bahwa waktu pencocokan rute hanya tergantung pada panjang jalur yang cocok dan bukan jumlah rute. Namun, pendekatan ini dapat berpotensi bermasalah dalam beberapa kasus, ketika aplikasi memiliki sejumlah besar rute (dalam ribuan) dan ada sejumlah besar awalan variabel dalam rute. Misalnya, jika rute memiliki parameter di segmen awal rute, seperti {parameter}/some/literal.

Aplikasi tidak mungkin mengalami situasi di mana ini adalah masalah kecuali:

  • Ada sejumlah besar rute di aplikasi menggunakan pola ini.
  • Ada sejumlah besar rute di aplikasi.

Cara menentukan apakah aplikasi mengalami masalah tabel rute besar

  • Ada dua gejala yang harus dicari:
    • Aplikasi ini lambat untuk memulai pada permintaan pertama.
      • Perhatikan bahwa ini diperlukan tetapi tidak cukup. Ada banyak masalah non-rute lainnya yang dapat menyebabkan aplikasi mulai lambat. Periksa kondisi di bawah ini untuk secara akurat menentukan aplikasi mengalami situasi ini.
    • Aplikasi ini mengonsumsi banyak memori selama startup dan pembuangan memori menunjukkan sejumlah besar instans Microsoft.AspNetCore.Routing.Matching.DfaNode.

Cara mengatasi masalah ini

Ada beberapa teknik dan pengoptimalan yang dapat diterapkan ke rute yang sebagian besar meningkatkan skenario ini:

  • Terapkan batasan rute ke parameter Anda, misalnya {parameter:int}, , {parameter:guid}, {parameter:regex(\\d+)}dll. jika memungkinkan.
    • Ini memungkinkan algoritma perutean untuk mengoptimalkan struktur yang digunakan untuk pencocokan secara internal dan secara drastis mengurangi memori yang digunakan.
    • Dalam sebagian besar kasus, ini akan cukup untuk kembali ke perilaku yang dapat diterima.
  • Ubah rute untuk memindahkan parameter ke segmen selanjutnya dalam templat.
    • Ini mengurangi jumlah kemungkinan "jalur" agar sesuai dengan titik akhir yang diberikan jalur.
  • Gunakan rute dinamis dan lakukan pemetaan ke pengontrol/halaman secara dinamis.
    • Ini dapat dicapai menggunakan MapDynamicControllerRoute dan MapDynamicPageRoute.

Middleware sirkuit pendek setelah perutean

Saat perutean cocok dengan titik akhir, biasanya memungkinkan sisa alur middleware berjalan sebelum memanggil logika titik akhir. Layanan dapat mengurangi penggunaan sumber daya dengan memfilter permintaan yang diketahui di awal alur. ShortCircuit Gunakan metode ekstensi untuk menyebabkan perutean segera memanggil logika titik akhir lalu akhiri permintaan. Misalnya, rute tertentu mungkin tidak perlu melalui autentikasi atau middleware CORS. Contoh permintaan sirkuit pendek berikut yang cocok dengan /short-circuit rute:

app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();

Metode ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) ini dapat mengambil kode status secara opsional.

Gunakan metode MapShortCircuit untuk menyiapkan pemutusan cepat untuk beberapa rute sekaligus, dengan meneruskannya ke array parameter berupa awalan URL. Misalnya, browser dan bot sering memeriksa server untuk lintasan yang terkenal seperti robots.txt dan favicon.ico. Jika aplikasi tidak memiliki file tersebut, satu baris kode dapat mengonfigurasi kedua rute:

app.MapShortCircuit(404, "robots.txt", "favicon.ico");

MapShortCircuit IEndpointConventionBuilder mengembalikan sehingga batasan rute tambahan seperti pemfilteran host dapat ditambahkan ke dalamnya.

Metode ShortCircuit dan MapShortCircuit tidak memengaruhi middleware yang ditempatkan sebelum UseRouting. Upaya untuk menggunakan metode ini pada titik akhir yang juga memiliki metadata [Authorize] atau [RequireCors], dapat menyebabkan permintaan menjadi gagal dengan InvalidOperationException. Metadata ini diterapkan oleh [Authorize] atau [EnableCors] atribut atau oleh RequireCors atau RequireAuthorization metode.

Untuk melihat efek dari middleware yang menghentikan proses, atur kategori pencatatan log "Microsoft" ke "Informasi" di appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Information",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Jalankan kode berikut:

var app = WebApplication.Create();

app.UseHttpLogging();

app.MapGet("/", () => "No short-circuiting!");
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
app.MapShortCircuit(404, "robots.txt", "favicon.ico");

app.Run();

Contoh berikut berasal dari log konsol yang dihasilkan dengan menjalankan endpoint /. Ini termasuk output dari middleware pengelogan:

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8
      Date: Wed, 03 May 2023 21:05:59 GMT
      Server: Kestrel
      Alt-Svc: h3=":5182"; ma=86400
      Transfer-Encoding: chunked

Contoh berikut adalah dari menjalankan endpoint /short-circuit. Ini tidak memiliki apa pun dari middleware pengelogan karena middleware memiliki sirkuit pendek:

info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[4]
      The endpoint 'HTTP: GET /short-circuit' is being executed without running additional middleware.
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[5]
      The endpoint 'HTTP: GET /short-circuit' has been executed without running additional middleware.

Panduan untuk penulis pustaka

Bagian ini berisi panduan untuk penulis pustaka yang membangun di atas perutean. Detail ini dimaksudkan untuk memastikan bahwa pengembang aplikasi memiliki pengalaman yang baik menggunakan pustaka dan kerangka kerja yang memperluas perutean.

Menentukan titik akhir

Untuk membuat kerangka kerja yang menggunakan perutean untuk pencocokan URL, mulailah dengan menentukan pengalaman pengguna yang dibangun di atas UseEndpoints.

DO dibangun di atas IEndpointRouteBuilder. Ini memungkinkan pengguna untuk menyusun kerangka kerja Anda dengan fitur ASP.NET Core lainnya tanpa kebingungan. Setiap templat ASP.NET Core menyertakan routing. Asumsikan perutean ada dan sudah dikenal oleh pengguna.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

DO mengembalikan jenis beton tertutup dari panggilan ke MapMyFramework(...) yang mengimplementasikan IEndpointConventionBuilder. Sebagian besar metode kerangka kerja Map... mengikuti pola ini. Antarmuka IEndpointConventionBuilder :

  • Memungkinkan metadata disusun.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan tipe Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka Anda sendiri ke builder. Tidak apa-apa untuk membungkus pembangun yang dideklarasikan kerangka kerja dan meneruskan panggilan ke dalamnya.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

PERTIMBANGKAN untuk menulis sendiri EndpointDataSource. EndpointDataSource adalah primitif tingkat rendah untuk mendeklarasikan dan memperbarui kumpulan titik akhir. EndpointDataSource adalah API canggih yang digunakan oleh pengontrol dan Razor Halaman. Untuk informasi selengkapnya, lihat Perutean titik akhir dinamis.

Pengujian perutean memiliki contoh dasar sumber data yang tidak diperbarui.

PERTIMBANGKAN untuk menerapkan GetGroupedEndpoints. Ini memberikan kontrol penuh atas konvensi grup yang sedang diterapkan dan metadata akhir pada endpoint yang dikelompokkan. Misalnya, ini memungkinkan implementasi kustom EndpointDataSource untuk menjalankan filter titik akhir yang diterapkan pada grup.

JANGAN mencoba mendaftarkan EndpointDataSource secara default. Mengharuskan pengguna untuk mendaftarkan kerangka kerja Anda di UseEndpoints. Filosofi perutean adalah bahwa tidak ada yang disertakan secara default, dan UseEndpoints merupakan tempat untuk mendaftarkan endpoint.

Membuat middleware yang terintegrasi dengan perutean

PERTIMBANGKAN menentukan tipe metadata sebagai antarmuka.

DO memungkinkan untuk menggunakan jenis metadata sebagai atribut pada kelas dan metode.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Kerangka kerja seperti pengontrol dan Razor Pages mendukung penerapan atribut metadata ke jenis dan metode. Jika Anda mendeklarasikan jenis metadata:

  • Buat mereka dapat diakses sebagai atribut.
  • Sebagian besar pengguna terbiasa menerapkan atribut.

Mendeklarasikan jenis metadata sebagai antarmuka menambahkan lapisan fleksibilitas lain:

  • Antarmuka dapat dikomposisikan.
  • Pengembang dapat mendeklarasikan jenis mereka sendiri yang menggabungkan beberapa kebijakan.

DO memungkinkan untuk mengambil alih metadata, seperti yang ditunjukkan dalam contoh berikut:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Cara terbaik untuk mengikuti panduan ini adalah dengan menghindari penentuan metadata penanda:

  • Jangan hanya mencari ada tidaknya jenis metadata.
  • Tentukan properti pada metadata dan periksa properti .

Koleksi metadata diurutkan dan mendukung penimpaan berdasarkan prioritas. Dalam kasus pengontrol, metadata pada metode tindakan paling spesifik.

DO menjadikan middleware berguna baik dengan maupun tanpa perutean:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Sebagai contoh pedoman ini, pertimbangkan UseAuthorization middleware. Middleware otorisasi memungkinkan Anda untuk menginputkan sebuah kebijakan fallback. Kebijakan fallback, jika ditentukan, berlaku untuk keduanya:

  • Titik akhir tanpa kebijakan yang ditentukan.
  • Permintaan yang tidak cocok dengan titik akhir.

Ini membuat perangkat lunak perantara untuk otorisasi berguna di luar konteks perutean. Middleware otorisasi dapat digunakan untuk pemrograman middleware tradisional.

Pemecahan Masalah Diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan Development, atur tingkat log di appsettings.Development.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Sumber daya tambahan

Perutean bertanggung jawab untuk mencocokkan permintaan HTTP masuk dan mengirimkan permintaan tersebut ke titik akhir aplikasi yang dapat dieksekusi. Titik akhir adalah unit kode penanganan permintaan yang dapat dieksekusi aplikasi. Titik akhir ditentukan dalam aplikasi dan dikonfigurasi saat aplikasi dimulai. Proses pencocokan titik akhir dapat mengekstrak nilai dari URL permintaan dan menyediakan nilai tersebut untuk pemrosesan permintaan. Dengan memanfaatkan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang menghubungkan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Controllers
  • Razor Halaman
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambdas yang terdaftar dalam perutean.

Artikel ini membahas detail tingkat rendah perutean ASP.NET Core. Untuk informasi tentang mengonfigurasi perutean:

Dasar-dasar perutean

Kode berikut menunjukkan contoh dasar perutean:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Contoh sebelumnya mencakup satu titik akhir menggunakan MapGet metode :

  • Ketika permintaan HTTP GET dikirim ke URL /akar :
    • Delegasi permintaan dijalankan.
    • Halo Dunia! dicatat dalam respons HTTP.
  • Jika metode permintaan tidak GET atau URL akar bukan /, tidak ada rute yang cocok dan HTTP 404 dikembalikan.

Perutean menggunakan sepasang middleware, yang didaftarkan oleh UseRouting dan UseEndpoints:

  • UseRouting menambahkan pencocokan rute ke alur middleware. Middleware ini melihat kumpulan titik akhir yang ditentukan dalam aplikasi, dan memilih kecocokan terbaik berdasarkan permintaan.
  • UseEndpoints menambahkan eksekusi titik akhir ke alur middleware. Ini menjalankan delegasi yang terkait dengan titik akhir yang dipilih.

Aplikasi biasanya tidak perlu memanggil UseRouting atau UseEndpoints. WebApplicationBuilder mengonfigurasi alur middleware yang membungkus middleware yang ditambahkan Program.cs dengan UseRouting dan UseEndpoints. Namun, aplikasi dapat mengubah urutan di mana UseRouting dan UseEndpoints berjalan dengan memanggil metode ini secara eksplisit. Misalnya, kode berikut melakukan panggilan eksplisit ke UseRouting:

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Dalam kode sebelumnya:

  • Panggilan ke app.Use mendaftarkan middleware kustom yang berjalan di awal jalur pemrosesan.
  • Panggilan untuk UseRouting mengonfigurasi middleware pencocokan rute untuk dijalankan setelah middleware kustom.
  • Titik akhir yang terdaftar dengan MapGet berjalan di akhir pipeline.

Jika contoh sebelumnya tidak menyertakan panggilan ke UseRouting, middleware kustom akan berjalan setelah middleware yang melakukan pencocokan rute.

Endpoints

Metode MapGet ini digunakan untuk menentukan titik akhir. Titik akhir adalah sesuatu yang dapat berupa:

  • Dipilih, dengan mencocokkan URL dan metode HTTP.
  • Dijalankan, dengan menjalankan delegasi.

Titik akhir yang dapat dicocokkan dan dijalankan oleh aplikasi dikonfigurasi di UseEndpoints. Misalnya, MapGet, MapPost, dan metode serupa menghubungkan delegasi permintaan dengan sistem routing. Metode tambahan dapat digunakan untuk menghubungkan fitur kerangka kerja ASP.NET Core ke sistem perutean:

Contoh berikut menunjukkan perutean dengan templat rute yang lebih canggih:

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

String /hello/{name:alpha} adalah templat rute. Templat rute digunakan untuk mengonfigurasi bagaimana titik akhir cocok. Dalam hal ini, templat cocok:

  • URL seperti /hello/Docs
  • Jalur URL apa pun yang dimulai dengan /hello/ diikuti dengan urutan karakter alfabet. :alpha menerapkan batasan rute yang hanya cocok dengan karakter alfabet. Batasan rute dijelaskan nanti dalam artikel ini.

Segmen kedua dari jalur URL, {name:alpha}:

Contoh berikut menunjukkan perutean dengan pemeriksaan status kesehatan dan otorisasi:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan penerusan jalur.
  • Titik akhir dapat digunakan untuk mengonfigurasi perilaku otorisasi.

Panggilan MapHealthChecks menambahkan titik akhir pemeriksaan kesehatan. Menautkan RequireAuthorization ke panggilan ini melampirkan kebijakan otorisasi ke titik akhir.

Memanggil UseAuthentication dan UseAuthorization menambahkan middleware autentikasi dan otorisasi. Middleware ini ditempatkan di antara UseRouting dan UseEndpoints sehingga mereka dapat:

  • Lihat titik akhir mana yang dipilih oleh UseRouting.
  • Terapkan kebijakan otorisasi sebelum UseEndpoints dikirim ke titik akhir.

Metadata titik akhir

Dalam contoh sebelumnya, ada dua titik akhir, tetapi hanya titik akhir pemeriksaan kesehatan yang memiliki kebijakan otorisasi yang terlampir. Jika permintaan cocok dengan titik akhir pemeriksaan kesehatan, /healthz, pemeriksaan otorisasi dilakukan. Ini menunjukkan bahwa titik akhir dapat memiliki data tambahan yang melekat padanya. Data tambahan ini disebut metadata titik akhir:

  • Metadata dapat diproses dengan middleware berkesadaran perutean.
  • Metadata dapat dari jenis .NET apa pun.

Konsep perutean

Sistem perutean dibangun di atas alur middleware dengan menambahkan konsep titik akhir yang kuat. Titik akhir mewakili unit fungsionalitas aplikasi yang berbeda satu sama lain dalam hal perutean, otorisasi, dan sejumlah sistem ASP.NET Core.

definisi titik akhir ASP.NET Core

Titik akhir ASP.NET Core adalah:

Kode berikut menunjukkan cara mengambil dan memeriksa titik akhir yang cocok dengan permintaan saat ini:

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

Titik akhir, jika dipilih, dapat diambil dari HttpContext. Sifat-sifatnya dapat diperiksa. Objek titik akhir bersifat tetap dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya diseleksi oleh sistem perutean.

Dalam kode sebelumnya, app.Use mengonfigurasi middleware inline.

Kode berikut menunjukkan bahwa, tergantung di mana app.Use dipanggil dalam alur, mungkin tidak ada titik akhir:

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

Sampel sebelumnya menambahkan Console.WriteLine pernyataan yang menampilkan apakah titik akhir telah dipilih atau tidak. Untuk kejelasan, sampel menetapkan nama tampilan ke endpoint yang diberikan /.

Sampel sebelumnya juga mencakup panggilan ke UseRouting dan UseEndpoints untuk mengontrol dengan tepat kapan middleware ini berjalan dalam alur.

Menjalankan kode ini dengan URL / menampilkan:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

Menjalankan kode ini dengan URL lain akan menampilkan:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Output ini menunjukkan bahwa:

  • Titik akhir selalu null sebelum UseRouting dipanggil.
  • Jika kecocokan ditemukan, titik akhir tidak null antara UseRouting dan UseEndpoints.
  • Middleware UseEndpoints mengakhiri proses ketika kecocokan ditemukan. Terminal middleware didefinisikan nanti dalam artikel ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan metode SetEndpoint untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan tetap mendapatkan manfaat dari penggunaan endpoint. Titik akhir adalah primitif tingkat rendah seperti middleware, dan tidak digabungkan dengan implementasi perutean. Sebagian besar aplikasi tidak perlu mengganti UseRouting dengan logika kustom.

Middleware UseEndpoints dirancang untuk digunakan bersama dengan UseRouting middleware. Logika inti untuk menjalankan titik akhir tidak rumit. Gunakan GetEndpoint untuk mengambil titik akhir, lalu panggil propertinya RequestDelegate .

Kode berikut memperlihatkan bagaimana middleware dapat memengaruhi atau bereaksi terhadap perutean:

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

Contoh sebelumnya menunjukkan dua konsep penting:

  • Middleware dapat berjalan sebelum UseRouting untuk memodifikasi data yang akan dioperasikan oleh perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil rute sebelum titik akhir dijalankan.
    • Middleware yang berjalan antara UseRouting dan UseEndpoints:
      • Biasanya memeriksa metadata untuk memahami titik akhir.
      • Sering membuat keputusan keamanan, seperti yang dilakukan oleh UseAuthorization dan UseCors.
    • Kombinasi middleware dan metadata memungkinkan konfigurasi kebijakan per titik akhir.

Kode sebelumnya menunjukkan contoh middleware kustom yang mendukung kebijakan per titik akhir. Middleware menulis log audit ke konsol mengenai akses ke data sensitif. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan RequiresAuditAttribute metadata. Sampel ini menunjukkan pola opt-in di mana hanya endpoint yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem titik akhir metadata itu fleksibel. Logika ini dapat dirancang dengan cara apa pun yang sesuai dengan kasus penggunaan.

Kode sampel sebelumnya dimaksudkan untuk menunjukkan konsep dasar titik akhir. Sampel tidak ditujukan untuk penggunaan produksi. Versi yang lebih lengkap dari middleware audit log akan:

  • Masuk ke file atau database.
  • Sertakan detail seperti pengguna, alamat IP, nama titik akhir sensitif, dan lainnya.

Metadata kebijakan audit RequiresAuditAttribute didefinisikan sebagai Attribute untuk memudahkan penggunaan dengan kerangka kerja berbasis kelas seperti pengontrol dan SignalR. Saat menggunakan rute ke kode:

  • Metadata dilampirkan dengan API penyusun.
  • Kerangka kerja berbasis kelas mencakup semua atribut pada metode dan kelas yang sesuai saat membuat titik akhir.

Praktik terbaik untuk jenis metadata adalah menentukannya sebagai antarmuka atau atribut. Antarmuka dan atribut memungkinkan penggunaan kembali kode. Sistem metadata fleksibel dan tidak memberlakukan batasan apa pun.

Membandingkan middleware terminal dengan perutean

Contoh berikut menunjukkan middleware terminal dan perutean:

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Gaya middleware yang ditampilkan dengan Approach 1: adalah middleware terminal. Ini disebut middleware terminal karena melakukan operasi yang cocok:

  • Operasi pencocokan dalam sampel sebelumnya menggunakan Path == "/" untuk middleware dan Path == "/Routing" untuk perutean.
  • Ketika kecocokan berhasil, sistem menjalankan beberapa fungsi, kemudian mengembalikan nilai, bukan memanggil next middleware.

Ini disebut middleware terminal karena mengakhiri pencarian, menjalankan beberapa fungsionalitas, lalu mengembalikan.

Daftar berikut membandingkan middleware terminal dengan perutean:

  • Kedua pendekatan memungkinkan penghentian alur pemrosesan:
    • Middleware mengakhiri alur dengan mengembalikan daripada memanggil next.
    • Titik akhir selalu terminal.
  • Middleware terminal memungkinkan penempatan middleware di posisi sembarang dalam pipeline:
  • Middleware terminal memungkinkan kode sebarang untuk menentukan kapan middleware tersebut sesuai:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Routing menyediakan solusi sederhana untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Titik akhir berinteraksi dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan penghubungan manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Petugas untuk memproses permintaan.
  • Kumpulan metadata acak. Metadata digunakan untuk menerapkan aspek lintas berdasarkan kebijakan dan konfigurasi yang diterapkan pada setiap titik akhir.

Middleware terminal dapat menjadi alat yang efektif, tetapi dapat memerlukan:

  • Sejumlah besar pengkodian dan pengujian.
  • Integrasi manual dengan sistem lain untuk mencapai tingkat fleksibilitas yang diinginkan.

Pertimbangkan untuk mengintegrasikan dengan routing sebelum menulis terminal middleware.

Middleware terminal yang ada yang dapat terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir yang sadar akan perutean. MapHealthChecks menunjukkan pola untuk router-ware:

Kode berikut menunjukkan penggunaan MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

Sampel sebelumnya menunjukkan mengapa mengembalikan objek pembangun penting. Mengembalikan objek penyusun memungkinkan pengembang aplikasi untuk mengonfigurasi kebijakan seperti otorisasi untuk titik akhir. Dalam contoh ini, middleware pemeriksaan kesehatan tidak memiliki integrasi langsung dengan sistem otorisasi.

Sistem metadata dibuat sebagai respons terhadap masalah yang dihadapi oleh penulis ekstensibilitas menggunakan middleware terminal. Sangat bermasalah bagi setiap middleware untuk menerapkan integrasinya sendiri dengan sistem otorisasi.

Pencocokan URL

  • Apakah proses perutean cocok dengan permintaan masuk ke titik akhir.
  • Didasarkan pada data di jalur URL dan header.
  • Dapat diperluas untuk mempertimbangkan data apa pun dalam permintaan.

Ketika middleware perutean dieksekusi, ia menetapkan nilai Endpoint dan nilai rute ke fitur permintaan pada HttpContext dari permintaan saat ini.

  • Memanggil HttpContext.GetEndpoint mendapatkan titik akhir.
  • HttpRequest.RouteValues mendapatkan kumpulan nilai rute.

Middleware berjalan setelah middleware routing dapat memeriksa endpoint dan mengambil tindakan. Misalnya, middleware otorisasi dapat memeriksa kumpulan metadata dari titik akhir untuk menemukan kebijakan akses otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi endpoint yang dipilih dipanggil.

Sistem routing dalam endpoint routing bertanggung jawab atas semua keputusan dispatching. Karena middleware menerapkan kebijakan berdasarkan titik akhir yang dipilih, penting bahwa:

  • Keputusan apa pun yang dapat memengaruhi pengiriman atau penerapan kebijakan keamanan dibuat di dalam sistem perutean.

Warning

Untuk kompatibilitas mundur, saat delegasi titik akhir Pengontrol atau Razor Halaman dijalankan, properti RouteContext.RouteData diatur ke nilai yang sesuai berdasarkan pemrosesan permintaan yang dilakukan sejauh ini.

Jenisnya RouteContext akan ditandai usang dalam rilis mendatang:

  • Migrasikan RouteData.Values ke HttpRequest.RouteValues.
  • Migrasikan RouteData.DataTokens untuk mengambil IDataTokensMetadata dari metadata titik akhir.

Pencocokan URL beroperasi dalam serangkaian fase yang dapat dikonfigurasi. Dalam setiap fase, output adalah sekumpulan kecocokan. Kumpulan pertandingan dapat disempitkan lebih lanjut pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk endpoint yang sesuai. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL berdasarkan sekumpulan titik akhir dan pola rutenya, mengumpulkan semua kecocokan.
  2. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dengan batasan rute yang diterapkan.
  3. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dalam rangkaian MatcherPolicy instans.
  4. Gunakan EndpointSelector untuk membuat keputusan akhir dari daftar sebelumnya.

Daftar titik akhir diprioritaskan sesuai dengan:

Semua titik akhir yang cocok diproses di setiap fase hingga tercapai EndpointSelector . EndpointSelector adalah fase akhir. Ini memilih titik akhir prioritas tertinggi dari kecocokan sebagai kecocokan terbaik. Jika ada kecocokan lain dengan prioritas yang sama dengan kecocokan terbaik, pengecualian pertandingan ambigu akan dilemparkan.

Prioritas rute dihitung berdasarkan templat rute yang lebih spesifik yang diberi prioritas yang lebih tinggi. Misalnya, pertimbangkan templat /hello dan /{message}:

  • Keduanya cocok dengan jalur URL/hello.
  • /hello lebih spesifik dan oleh karena itu prioritas yang lebih tinggi.

Secara umum, prioritas rute melakukan pekerjaan yang baik untuk memilih kecocokan terbaik untuk jenis skema URL yang digunakan dalam praktiknya. Gunakan Order hanya jika perlu untuk menghindari ambiguitas.

Karena jenis ekstensibilitas yang disediakan oleh perutean, sistem perutean tidak mungkin menghitung rute ambigu sebelumnya. Pertimbangkan contoh seperti templat rute /{message:alpha} dan /{message:int}:

  • Batasan alpha hanya cocok dengan karakter alfabet.
  • Batasan int hanya cocok dengan angka.
  • Templat ini memiliki prioritas rute yang sama, tetapi tidak ada satu URL pun yang cocok untuk keduanya.
  • Jika sistem routing melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir penggunaan valid ini.

Warning

Urutan pelaksanaan operasi di dalam UseEndpoints tidak memengaruhi perilaku perutean, dengan satu pengecualian. MapControllerRoute dan MapAreaRoute secara otomatis menetapkan nilai pesanan ke titik akhir mereka berdasarkan urutan yang dipanggil. Ini mensimulasikan perilaku jangka panjang pengontrol tanpa sistem perutean yang menyediakan jaminan yang sama seperti implementasi perutean lama.

Perutean titik akhir di ASP.NET Core:

  • Tidak memiliki konsep rute.
  • Tidak memberikan jaminan pemesanan. Semua titik akhir diproses sekaligus.

Urutan pemilihan prioritas templat dan titik akhir rute

Prioritas templat rute adalah sistem yang menetapkan setiap templat rute nilai berdasarkan seberapa spesifiknya. Prioritas templat rute:

  • Menghindari kebutuhan untuk menyesuaikan urutan titik akhir dalam kasus umum.
  • Upaya untuk memenuhi ekspektasi wajar terhadap perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa /Products/List adalah pilihan yang lebih baik daripada /Products/{id} untuk jalur /Products/List URL. Ini berfungsi karena segmen /List harfiah dianggap memiliki prioritas yang lebih baik daripada segmen /{id}parameter .

Detail cara kerja prioritas digabungkan dengan bagaimana templat rute ditentukan:

  • Templat dengan lebih banyak segmen dianggap lebih spesifik.
  • Segmen dengan teks harfiah dianggap lebih spesifik daripada segmen parameter.
  • Segmen parameter dengan batasan dianggap lebih spesifik daripada satu tanpanya.
  • Segmen kompleks dianggap spesifik sebagai segmen parameter dengan batasan.
  • Parameter yang bersifat mencakup semua adalah yang paling tidak spesifik. Lihat catch-all di bagian Templat rute untuk informasi penting tentang rute catch-all.

Konsep pembuatan URL

Pembuatan URL:

  • Adalah proses di mana perutean dapat membuat jalur URL berdasarkan serangkaian nilai rute.
  • Memungkinkan pemisahan logis antara titik akhir dan URL yang mengaksesnya.

Rute titik akhir meliputi penggunaan API LinkGenerator. LinkGenerator adalah layanan singleton yang tersedia dari DI. LinkGenerator API dapat digunakan di luar konteks permintaan yang dijalankan. Mvc.IUrlHelper dan skenario yang mengandalkan IUrlHelper, seperti Tag Helpers, HTML Helpers, dan Tindakan Hasil, menggunakan LinkGenerator API secara internal untuk menyediakan kemampuan untuk membuat tautan.

Generator tautan didukung oleh konsep alamat dan skema alamat. Skema alamat adalah cara menentukan titik akhir yang harus dipertimbangkan untuk pembuatan tautan. Misalnya, skenario nama rute dan nilai rute yang dikenal banyak pengguna dari pengontrol dan Razor Pages diimplementasikan sebagai skema alamat.

Generator tautan dapat ditautkan ke pengontrol dan Razor Halaman melalui metode ekstensi berikut:

Kelebihan beban metode ini menerima argumen yang menyertakan HttpContext. Metode ini secara fungsional setara dengan Url.Action dan Url.Page, tetapi menawarkan fleksibilitas dan opsi tambahan.

Metode GetPath* paling mirip dengan Url.Action dan Url.Page, karena itu menghasilkan URI yang berisi jalur absolut. Metode GetUri* tersebut selalu menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima HttpContext menghasilkan URI dalam konteks permintaan yang sedang dijalankan. Nilai rute lingkungan, jalur dasar URL, skema, dan host dari permintaan yang sedang dijalankan digunakan kecuali ditimpa.

LinkGenerator dipanggil dengan alamat. Menghasilkan URI terjadi dalam dua langkah:

  1. Alamat terikat ke daftar titik akhir yang sesuai dengan alamat tersebut.
  2. Setiap titik RoutePattern akhir dievaluasi hingga pola rute yang cocok dengan nilai yang disediakan ditemukan. Output yang dihasilkan dikombinasikan dengan bagian URI lain yang disediakan ke generator tautan dan dikembalikan.

Metode yang disediakan oleh LinkGenerator mendukung kemampuan pembuatan tautan standar untuk semua jenis alamat. Cara paling nyaman untuk menggunakan generator tautan adalah melalui metode ekstensi yang melakukan operasi untuk jenis alamat tertentu:

Metode Ekstensi Description
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Warning

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan metode ekstensi GetUri* dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi header Host dari permintaan yang masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak dipercaya dapat dikirim kembali ke klien dalam URI pada tampilan atau halaman. Kami merekomendasikan agar semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi header Host terhadap nilai valid yang diketahui.

  • Gunakan LinkGenerator dengan hati-hati dalam middleware dalam kombinasi dengan Map atau MapWhen. Map* mengubah jalur dasar permintaan eksekusi, yang memengaruhi output pembuatan tautan. LinkGenerator Semua API memungkinkan menentukan jalur dasar. Tentukan jalur dasar kosong untuk membatalkan Map* pengaruh pada pembuatan tautan.

Contoh middleware

Dalam contoh berikut, middleware menggunakan LinkGenerator API untuk membuat tautan ke metode tindakan yang mencantumkan produk penyimpanan. Menggunakan generator tautan dengan menyuntikkannya ke kelas dan panggilan GenerateLink tersedia untuk kelas apa pun di aplikasi:

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

Pola Rute

Token dalam {} menentukan parameter rute yang terikat jika rute cocok. Lebih dari satu parameter rute dapat didefinisikan dalam segmen rute, tetapi parameter rute harus dipisahkan oleh nilai harfiah. Contohnya:

{controller=Home}{action=Index}

bukan rute yang valid, karena tidak ada nilai harfiah antara {controller} dan {action}. Parameter rute harus memiliki nama dan mungkin memiliki atribut tambahan yang ditentukan.

Teks literal selain parameter rute (misalnya, {id}) dan pemisah / jalur harus cocok dengan teks di URL. Pencocokan teks tidak membedakan huruf besar atau kecil dan didasarkan pada representasi yang didekodekan dari jalur pada URL. Untuk mencocokkan pemisah parameter rute literal { atau }, hindari pemisah tersebut dengan mengulangi karakter tersebut. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

  • Dapat digunakan sebagai awalan untuk parameter rute untuk mengikat ke sisa URI.
  • Disebut sebagai parameter catch-all. Misalnya, blog/{**slug}:
    • Cocok dengan URI apa pun yang dimulai dengan blog/ dan memiliki nilai apa pun yang mengikutinya.
    • Nilai setelah blog/ ditetapkan ke nilai rute slug.

Warning

Parameter catch-all mungkin salah mencocokkan rute karena bug dalam perutean. Aplikasi yang terpengaruh oleh bug ini memiliki karakteristik berikut:

  • Rute tangkap-semua, misalnya, {**slug}"
  • Rute catch-all gagal mencocokkan permintaan yang seharusnya cocok.
  • Menghapus rute lain membuat rute catch-all mulai berfungsi.

Lihat bug GitHub 18677 dan 16579 misalnya kasus yang mengenai bug ini.

Perbaikan yang perlu diaktifkan untuk bug ini tersedia dalam .NET Core 3.1.301 atau SDK yang lebih baru. Kode program berikut menetapkan sakelar internal yang mengatasi bug ini:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Parameter catch-all juga dapat mencocokkan string kosong.

Parameter catch-all mengubah karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter-jalur pemisah /. Misalnya, rute foo/{*path} dengan nilai rute { path = "my/path" } menghasilkan foo/my%2Fpath. Perhatikan garis miring maju yang lolos. Untuk karakter pemisah jalur pulang-pergi, gunakan awalan ** parameter rute. Rute foo/{**path} dengan { path = "my/path" } menghasilkan foo/my/path.

Pola URL yang mencoba mengambil nama file dengan ekstensi file opsional memiliki pertimbangan tambahan. Misalnya, pertimbangkan templat files/{filename}.{ext?}. Ketika nilai untuk keduanya filename dan ext ada, kedua nilai diisi. Jika hanya nilai untuk filename yang ada di URL, rute cocok karena trailing . bersifat opsional. URL berikut cocok dengan rute ini:

  • /files/myFile.txt
  • /files/myFile

Parameter rute mungkin memiliki nilai default yang ditunjuk dengan menentukan nilai default setelah nama parameter dipisahkan oleh tanda sama dengan (=). Misalnya, {controller=Home} mendefinisikan Home sebagai nilai default untuk controller. Nilai default digunakan jika tidak ada nilai yang ada di URL untuk parameter . Parameter rute dibuat opsional dengan menambahkan tanda tanya (?) ke akhir nama parameter. Contohnya,id?. Perbedaan antara nilai opsional dan parameter rute default adalah:

  • Parameter rute dengan nilai default selalu menghasilkan nilai.
  • Parameter opsional hanya memiliki nilai saat nilai disediakan oleh URL permintaan.

Parameter rute mungkin memiliki batasan yang harus memenuhi nilai rute yang diperoleh dari URL. Menambahkan : dan nama batasan setelah nama parameter rute menentukan batasan sebaris pada parameter rute. Jika batasan memerlukan argumen, batasan tersebut diapit dalam tanda kurung (...) setelah nama batasan. Beberapa batasan sebaris dapat ditentukan dengan menambahkan satu : dan nama batasan lainnya.

Nama batasan dan argumen diteruskan ke layanan IInlineConstraintResolver untuk menciptakan instans IRouteConstraint yang akan digunakan dalam pemrosesan URL. Misalnya, templat blog/{article:minlength(10)} rute menentukan minlength batasan dengan argumen 10. Untuk informasi selengkapnya tentang batasan rute dan daftar batasan yang disediakan oleh kerangka kerja, lihat bagian Batasan rute.

Parameter rute juga dapat memiliki pengubah parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti pembatas, transformator parameter dapat ditambahkan secara sebaris ke parameter rute dengan menambahkan : dan nama transformator setelah nama parameter rute. Misalnya, templat rute blog/{article:slugify} menentukan sebuah transformer slugify. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Transformer parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Pola Rute Contoh Pencocokan URI Permintaan URI...
hello /hello Hanya cocok dengan jalur tunggal /hello.
{Page=Home} / Mencocokkan dan mengatur Page menjadi Home.
{Page=Home} /Contact Mencocokkan dan mengatur Page menjadi Contact.
{controller}/{action}/{id?} /Products/List Memetakan ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Memetakan ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Pemetaan ke controller Home dan metode Index. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Pemetaan ke controller Products dan metode Index. id diabaikan.

Menggunakan template umumnya adalah pendekatan paling sederhana untuk perutean. Batasan dan default juga dapat ditentukan di luar templat rute.

Segmen kompleks

Segmen kompleks diproses dengan mencocokkan pemisah harfiah dari kanan ke kiri dengan cara yang tidak serakah . Misalnya, [Route("/a{b}c{d}")] adalah segmen yang kompleks. Segmen kompleks bekerja dengan cara tertentu yang harus dipahami agar berhasil digunakan. Contoh di bagian ini menunjukkan mengapa segmen kompleks hanya benar-benar berfungsi dengan baik ketika teks pemisah tidak muncul di dalam nilai parameter. Menggunakan regex lalu mengekstrak nilai secara manual diperlukan untuk kasus yang lebih kompleks.

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ini adalah ringkasan langkah-langkah yang dilakukan oleh perutean dengan templat /a{b}c{d} dan jalur /abcd URL. | digunakan untuk membantu memvisualisasikan cara kerja algoritma:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /abcd juga dicari dari kanan dan menemukan /ab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Tidak ada teks yang tersisa dan tidak ada templat rute yang tersisa, jadi ini cocok.

Berikut adalah contoh kasus negatif menggunakan templat /a{b}c{d} yang sama dan jalur URL /aabcd. | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /aabcd juga dicari dari kanan dan menemukan /aab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Pada titik ini ada teks ayang tersisa , tetapi algoritma telah kehabisan templat rute untuk diurai, jadi ini bukan kecocokan.

Karena algoritma yang cocok tidak serakah:

  • Ini cocok dengan jumlah teks sekecil mungkin di setiap langkah.
  • Setiap kasus di mana nilai pemisah muncul di dalam nilai parameter menghasilkan tidak cocok.

Ekspresi reguler memberikan kendali yang jauh lebih besar terhadap perilaku pencocokannya.

Pencocokan rakus, juga dikenal sebagai pencocokan malas, cocok dengan string sebesar mungkin. Tidak serakah cocok dengan string sekecil mungkin.

Pengaturan Rute dengan Karakter Khusus

Pengarutan dengan karakter khusus dapat menyebabkan hasil yang tidak terduga. Misalnya, pertimbangkan pengontrol dengan metode tindakan berikut:

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Ketika string id berisi nilai yang dikodekan berikut, hasil yang tidak terduga mungkin terjadi:

ASCII Encoded
/ %2F
+

Parameter rute tidak selalu didekodekan URL. Masalah ini dapat diatasi di masa mendatang. Untuk informasi selengkapnya, lihat masalah GitHub ini;

Batasan rute

Batasan rute dijalankan ketika kecocokan telah terjadi pada URL masuk dan jalur URL ditokenisasi ke dalam nilai rute. Batasan rute umumnya memeriksa nilai rute yang terkait melalui templat rute dan membuat keputusan yang benar atau salah tentang apakah nilai tersebut dapat diterima. Beberapa batasan rute menggunakan data di luar nilai rute untuk mempertimbangkan apakah permintaan dapat dirutekan. Misalnya, HttpMethodRouteConstraint dapat menerima atau menolak permintaan berdasarkan kata kerja HTTP-nya. Batasan digunakan dalam permintaan perutean dan pembuatan tautan.

Warning

Jangan gunakan batasan untuk validasi input. Jika batasan digunakan untuk validasi input, input yang 404 tidak valid menghasilkan respons Tidak Ditemukan. Input yang tidak valid harus menghasilkan 400 Permintaan Buruk dengan pesan kesalahan yang sesuai. Batasan rute digunakan untuk memisahkan rute serupa, bukan untuk memvalidasi input untuk rute tertentu.

Tabel berikut menunjukkan contoh batasan rute dan perilaku yang diharapkan:

batasan Example Contoh Kecocokan Notes
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Sesuai dengan atau false. Case-insensitive
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Cocok dengan nilai yang valid DateTime dalam budaya invarian. Lihat peringatan sebelumnya.
decimal {price:decimal} 49.99, -1,000.01 Cocok dengan nilai yang valid decimal dalam budaya invarian. Lihat peringatan sebelumnya.
double {weight:double} 1.234, -1,001.01e8 Cocok dengan nilai yang valid double dalam budaya invarian. Lihat peringatan sebelumnya.
float {weight:float} 1.234, -1,001.01e8 Cocok dengan nilai yang valid float dalam budaya invarian. Lihat peringatan sebelumnya.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Mencocokkan dengan nilai Guid yang valid
long {ticks:long} 123456789, -123456789 Mencocokkan dengan nilai long yang valid
minlength(value) {username:minlength(4)} Rick String harus minimal 4 karakter
maxlength(value) {filename:maxlength(8)} MyFile String tidak boleh lebih dari 8 karakter
length(length) {filename:length(12)} somefile.txt Panjang string harus persis 12 karakter
length(min,max) {filename:length(8,16)} somefile.txt Panjang string harus minimal 8 dan tidak lebih dari 16 karakter
min(value) {age:min(18)} 19 Nilai bilangan bulat harus minimal 18
max(value) {age:max(120)} 91 Nilai bilangan bulat tidak boleh lebih dari 120
range(min,max) {age:range(18,120)} 91 Nilai bilangan bulat harus setidaknya 18 tetapi tidak lebih dari 120
alpha {name:alpha} Rick String harus terdiri dari satu atau beberapa karakter alfabet, a-z dan tidak peka huruf besar/kecil.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 String harus cocok dengan ekspresi reguler. Lihat tips tentang menentukan ekspresi reguler.
required {name:required} Rick Memberlakukan bahwa nilai eksplisit (bukan nilai sekitar) ada selama pembuatan URL.

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Beberapa batasan yang dipisahkan dengan titik dua dapat diterapkan ke satu parameter. Misalnya, batasan berikut membatasi parameter ke nilai bilangan bulat 1 atau lebih besar:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke CLR jenis int atau DateTime. Batasan ini mengasumsikan bahwa URL tidak dapat dilokalkan. Batasan rute yang disediakan kerangka kerja tidak mengubah nilai yang disimpan dalam nilai rute. Semua nilai rute yang diurai dari URL disimpan sebagai string. Misalnya, float batasan mencoba mengonversi nilai rute menjadi float, tetapi nilai yang dikonversi hanya digunakan untuk memverifikasi bahwa nilai tersebut dapat dikonversi ke float.

Ekspresi reguler dalam batasan

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ekspresi reguler dapat ditentukan sebagai pembatasan sebaris menggunakan batasan regex(...) route. Metode dalam MapControllerRoute keluarga juga menerima objek harfiah batasan. Jika formulir tersebut digunakan, nilai string ditafsirkan sebagai ekspresi reguler.

Kode berikut menggunakan batasan regex sebaris:

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

Kode berikut menggunakan objek literal untuk menentukan batasan regex:

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

Kerangka kerja ASP.NET Core ditambahkan RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ke konstruktor ekspresi reguler. Lihat RegexOptions untuk deskripsi anggota ini.

Ekspresi reguler menggunakan pemisah dan token yang mirip dengan yang digunakan oleh perutean dan bahasa C#. Token ekspresi reguler harus diloloskan. Untuk menggunakan ekspresi ^\d{3}-\d{2}-\d{4}$ reguler dalam batasan sebaris, gunakan salah satu hal berikut ini:

  • Ganti \ karakter yang disediakan dalam string sebagai \\ karakter dalam file sumber C# untuk menghindari \ karakter escape string.
  • String literal verbatim.

Untuk menghindari karakter pemisah parameter perutean seperti {, }, [, ], gandakan karakter tersebut dalam ekspresi, misalnya, {{, }}, [[, ]]. Tabel berikut ini memperlihatkan ekspresi reguler dan versi escape-nya:

Ekspresi reguler Ekspresi reguler yang di-escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Ekspresi reguler yang digunakan dalam perutean sering dimulai dengan karakter ^ dan cocok dengan posisi awal string. Ekspresi sering diakhiri dengan karakter $ dan mengakhiri string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. Tanpa karakter ^ dan $, ekspresi reguler mencocokkan substrak apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Expression String Match Comment
[a-z]{2} hello Yes Kecocokan substring
[a-z]{2} 123abc456 Yes Kecocokan substring
[a-z]{2} mz Yes Ekspresi yang cocok
[a-z]{2} MZ Yes Tidak peka huruf besar/kecil
^[a-z]{2}$ hello No Lihat ^ dan $ di atas
^[a-z]{2}$ 123abc456 No Lihat ^ dan $ di atas

Untuk informasi selengkapnya tentang sintaks ekspresi reguler, lihat .NET Framework Regular Expressions.

Untuk membatasi parameter ke sekumpulan nilai yang mungkin diketahui, gunakan ekspresi reguler. Misalnya, {action:regex(^(list|get|create)$)} hanya menyamai nilai jalur action dengan list, get, atau create. Jika diteruskan ke kamus batasan, string ^(list|get|create)$ setara. Batasan yang diteruskan dalam kamus batasan yang tidak cocok dengan salah satu batasan yang diketahui juga diperlakukan sebagai ekspresi reguler. Batasan yang diteruskan dalam templat yang tidak cocok dengan salah satu batasan yang diketahui tidak diperlakukan sebagai ekspresi reguler.

Pembatas rute khusus

Batasan rute kustom dapat dibuat dengan mengimplementasikan IRouteConstraint antarmuka. Antarmuka IRouteConstraint berisi Match, yang mengembalikan true jika batasan terpenuhi dan false sebaliknya.

Batasan rute kustom jarang diperlukan. Sebelum menerapkan batasan rute kustom, pertimbangkan alternatif, seperti pengikatan model.

Folder ASP.NET Core Constraints memberikan contoh yang baik untuk membuat batasan. Misalnya, GuidRouteConstraint.

Untuk menggunakan kustom IRouteConstraint, jenis batasan rute harus didaftarkan dengan aplikasi ConstraintMap di kontainer layanan. ConstraintMap adalah kamus yang memetakan kunci batasan rute ke IRouteConstraint implementasi yang memvalidasi batasan tersebut. Aplikasi ConstraintMap dapat diperbarui sebagai bagian dari Program.cs panggilan AddRouting atau dengan mengonfigurasi RouteOptions langsung dengan builder.Services.Configure<RouteOptions>. Contohnya:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Batasan sebelumnya diterapkan dalam kode berikut:

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

Implementasi NoZeroesRouteConstraint mencegah 0 digunakan dalam parameter rute:

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Kode sebelumnya:

  • Mencegah 0 di segmen {id} dari rute.
  • Ditunjukkan untuk memberikan contoh dasar penerapan batasan kustom. Ini tidak boleh digunakan dalam aplikasi produksi.

Pendekatan yang lebih baik untuk mencegah id yang mengandung 0 dari pemrosesan adalah dengan menggunakan kode berikut:

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

Kode sebelumnya memiliki keuntungan berikut daripada NoZeroesRouteConstraint pendekatan:

  • Ini tidak memerlukan batasan kustom.
  • Ini mengembalikan kesalahan yang lebih deskriptif ketika parameter rute menyertakan 0.

Parameter transformator

Pengubah parameter

Misalnya, transformator parameter kustom slugify dalam pola rute blog\{article:slugify} dengan Url.Action(new { article = "MyTestArticle" }) menghasilkan blog\my-test-article.

Pertimbangkan implementasi berikut IOutboundParameterTransformer :

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is null)
        {
            return null;
        }

        return Regex.Replace(
            value.ToString()!,
                "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100))
            .ToLowerInvariant();
    }
}

Untuk menggunakan transformator parameter dalam pola rute, konfigurasikan dengan ConstraintMap pada Program.cs.

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

Kerangka kerja ASP.NET Core menggunakan transformator parameter untuk mengubah URI tempat titik akhir diselesaikan. Misalnya, transformator parameter mengubah nilai rute yang digunakan untuk mencocokkan area, , controlleraction, dan page:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

Dengan templat rute sebelumnya, tindakan SubscriptionManagementController.GetAll dicocokkan dengan URI /subscription-management/get-all. Transformator parameter tidak mengubah nilai rute yang digunakan untuk menghasilkan tautan. Misalnya, Url.Action("GetAll", "SubscriptionManagement") menghasilkan /subscription-management/get-all.

ASP.NET Core menyediakan konvensi API untuk menggunakan transformator parameter dengan rute yang dihasilkan:

Referensi pembuatan URL

Bagian ini berisi referensi untuk algoritma yang diterapkan oleh pembuatan URL. Dalam praktiknya, sebagian besar contoh kompleks pembuatan URL menggunakan pengontrol atau halaman Razor. Lihat perutean di pengontrol untuk keterangan lebih lanjut.

Proses pembuatan URL dimulai dengan panggilan ke LinkGenerator.GetPathByAddress atau metode serupa. Metode ini disediakan dengan alamat, sekumpulan nilai rute, dan informasi opsional tentang permintaan saat ini dari HttpContext.

Langkah pertama adalah menggunakan alamat untuk menyelesaikan sekumpulan titik akhir kandidat menggunakan IEndpointAddressScheme<TAddress> yang cocok dengan jenis alamat.

Setelah kumpulan kandidat ditemukan oleh skema alamat, titik akhir diurutkan dan diproses secara berulang sampai operasi pembuatan URL berhasil. Pembuatan URL tidak memeriksa ambiguitas, hasil pertama yang dikembalikan adalah hasil akhir.

Pemecahan masalah pembuatan URL dengan pengelogan

Langkah pertama dalam pemecahan masalah pembuatan URL adalah mengatur tingkat pengelogan Microsoft.AspNetCore.Routing ke TRACE. LinkGenerator mencatat banyak detail tentang pemrosesannya yang dapat berguna untuk memecahkan masalah.

Lihat Referensi pembuatan URL untuk detail tentang pembuatan URL.

Addresses

Alamat adalah konsep dalam pembuatan URL yang digunakan untuk mengikat panggilan ke generator tautan ke sekumpulan titik akhir kandidat.

Alamat adalah konsep yang dapat diperluas yang dilengkapi dengan dua implementasi secara default:

  • Menggunakan nama titik akhir (string) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan nama rute pada MVC.
    • Menggunakan jenis metadata IEndpointNameMetadata.
    • Menyelesaikan string yang disediakan terhadap metadata semua titik akhir terdaftar.
    • Memberikan pengecualian pada startup jika beberapa titik akhir menggunakan nama yang sama.
    • Direkomendasikan untuk penggunaan tujuan umum di luar pengontrol dan Razor Halaman.
  • Menggunakan nilai rute (RouteValuesAddress) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan pengontrol dan pembuatan URL lama untuk Pages.
    • Sangat kompleks untuk diperpanjang dan di-debug.
    • Menyediakan implementasi yang digunakan oleh IUrlHelper, Pembantu Tag, Pembantu HTML, Hasil Tindakan, dll.

Peran skema alamat adalah membuat hubungan antara alamat dan titik akhir yang cocok dengan kriteria semena-mena:

  • Skema nama titik akhir melakukan pencarian kamus dasar.
  • Skema nilai rute memiliki subset algoritma set terbaik yang kompleks.

Nilai sekitar dan nilai eksplisit

Dari permintaan saat ini, perutean mengakses nilai rute permintaan HttpContext.Request.RouteValuessaat ini . Nilai yang terkait dengan permintaan saat ini disebut sebagai nilai ambient. Untuk tujuan kejelasan, dokumentasi mengacu pada nilai rute yang diteruskan ke metode sebagai nilai eksplisit.

Contoh berikut menunjukkan nilai sekitar dan nilai eksplisit. Ini menyediakan nilai pengaturan lingkungan dari permintaan saat ini dan nilai eksplisit.

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

Kode sebelumnya:

Kode berikut hanya menyediakan nilai eksplisit dan tanpa nilai sekitar:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Metode sebelumnya mengembalikan /Home/Subscribe/17

Kode berikut mengembalikan WidgetController/Widget/Subscribe/17:

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

Kode berikut menyediakan pengontrol dari nilai sekitar dalam permintaan saat ini dan nilai eksplisit:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Dalam kode sebelumnya:

  • /Gadget/Edit/17 dikembalikan.
  • Url mendapatkan IUrlHelper.
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama action dan nilai route yang ditentukan.

Kode berikut menyediakan nilai sekitar dari permintaan saat ini dan nilai eksplisit:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Kode sebelumnya mengatur url ke /Edit/17 ketika Halaman Edit Razor memuat arahan halaman berikut ini:

@page "{id:int}"

Jika halaman Edit tidak berisi "{id:int}" templat rute, url adalah /Edit?id=17.

Perilaku MVC IUrlHelper menambahkan lapisan kompleksitas selain aturan yang dijelaskan di sini:

  • IUrlHelper selalu menyediakan nilai rute dari permintaan saat ini sebagai nilai ambien.
  • IUrlHelper.Action selalu menyalin nilai rute action dan controller saat ini sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali digantikan.
  • IUrlHelper.Page selalu menggantikan nilai rute saat ini handler dengan null sebagai nilai eksplisit, kecuali dikesampingkan.

Pengguna sering terkejut dengan detail perilaku nilai sekitar, karena MVC tampaknya tidak mengikuti aturannya sendiri. Untuk alasan historis dan kompatibilitas, nilai rute tertentu seperti action, , controllerpage, dan handler memiliki perilaku kasus khusus mereka sendiri.

Fungsionalitas yang setara yang disediakan oleh LinkGenerator.GetPathByAction dan LinkGenerator.GetPathByPage menduplikasi anomali IUrlHelper ini untuk kompatibilitas.

Proses pembuatan URL

Setelah kumpulan titik akhir kandidat ditemukan, algoritma pembuatan URL:

  • Memproses titik akhir secara berulang.
  • Mengembalikan hasil pertama yang berhasil.

Langkah pertama dalam proses ini disebut pembatalan nilai rute. Invalidasi nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai lingkungan yang digunakan dan mana yang diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai ambien adalah mereka berusaha mengurangi pengetikan yang perlu dilakukan pengembang aplikasi, dalam beberapa kasus umum. Secara tradisional, skenario di mana nilai sekitar bermanfaat terkait dengan MVC:

  • Saat menautkan ke tindakan lain di pengontrol yang sama, nama pengontrol tidak perlu ditentukan.
  • Saat menautkan ke pengontrol lain di area yang sama, nama area tidak perlu ditentukan.
  • Saat menautkan ke metode tindakan yang sama, nilai rute tidak perlu ditentukan.
  • Saat menautkan ke bagian lain aplikasi, Anda tidak ingin membawa nilai rute yang tidak memiliki arti di bagian aplikasi tersebut.

Panggilan ke LinkGenerator atau IUrlHelper yang mengembalikan null biasanya disebabkan oleh tidak memahami ketidakvalidan nilai rute. Memecahkan masalah pembatalan nilai rute dengan secara eksplisit menentukan lebih banyak nilai rute untuk melihat apakah itu menyelesaikan masalah.

Pembatalan nilai rute berfungsi dengan asumsi bahwa skema URL aplikasi bersifat hierarkis, dengan hierarki yang terbentuk dari kiri-ke-kanan. Pertimbangkan templat {controller}/{action}/{id?} rute pengontrol dasar untuk mendapatkan rasa intuitif tentang cara kerjanya dalam praktik. Perubahan pada nilai membatalkan semua nilai rute yang muncul di sebelah kanan. Ini mencerminkan asumsi tentang hierarki. Jika aplikasi memiliki nilai lingkungan untuk id, dan operasi menentukan nilai yang berbeda untuk controller:

  • id tidak akan digunakan kembali karena {controller} berada di sebelah kiri {id?}.

Beberapa contoh yang menunjukkan prinsip ini:

  • Jika nilai eksplisit berisi nilai untuk id, nilai sekitar untuk id diabaikan. Nilai lingkungan untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai ambien untuk controller dapat digunakan. Jika nilai eksplisit untuk action berbeda dari nilai sekitar untuk action, id nilai tidak akan digunakan. Jika nilai eksplisit untuk action sama dengan nilai sekitar untuk action, id nilai dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk controller, nilai sekitar apa pun untuk controller diabaikan. Jika nilai eksplisit untuk controller berbeda dari nilai lingkungan untuk controller, nilai action dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai ambien untuk controller, action dan id nilai dapat digunakan.

Proses ini semakin rumit dengan adanya rute atribut dan rute konvensional khusus. Rute konvensional pengontrol seperti {controller}/{action}/{id?} menentukan hierarki menggunakan parameter rute. Untuk rute konvensional dedikasi dan rute atribut ke pengontrol dan Razor Halaman:

  • Ada hierarki nilai rute.
  • Mereka tidak muncul di templat.

Untuk kasus ini, pembuatan URL menentukan konsep nilai yang diperlukan. Titik akhir yang dibuat oleh pengontrol dan Razor Halaman memiliki nilai yang telah ditetapkan yang memungkinkan pembatalan nilai rute berfungsi dengan baik.

Algoritma invalidasi nilai rute secara rinci:

  • Nama nilai yang diperlukan dikombinasikan dengan parameter rute, lalu diproses dari kiri ke kanan.
  • Untuk setiap parameter, nilai sekitar dan nilai eksplisit dibandingkan:
    • Jika nilai sekitar dan nilai eksplisit sama, proses berlanjut.
    • Jika nilai sekitar ada dan nilai eksplisit tidak, nilai sekitar digunakan saat membuat URL.
    • Jika nilai sekitar tidak ada dan hanya nilai eksplisit yang ada, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika terdapat nilai ambien dan nilai eksplisit, dan kedua nilai tersebut berbeda, abaikan nilai ambien dan semua nilai ambien berikutnya.

Pada titik ini, operasi pembuatan URL siap untuk mengevaluasi batasan rute. Kumpulan nilai yang diterima dikombinasikan dengan nilai default parameter, yang disediakan untuk batasan. Jika semua batasan lolos, operasi berlanjut.

Selanjutnya, nilai yang diterima dapat digunakan untuk memperluas templat rute. Templat rute diproses:

  • Dari kiri-ke-kanan.
  • Setiap parameter digantikan dengan nilai yang diterima.
  • Dengan kasus khusus berikut:
    • Jika nilai yang diterima kehilangan nilai dan parameter memiliki nilai default, nilai default akan digunakan.
    • Jika nilai yang diterima kehilangan nilai dan parameter bersifat opsional, pemrosesan berlanjut.
    • Jika ada parameter rute di sebelah kanan parameter opsional yang hilang memiliki nilai, operasi gagal.
    • Parameter bernilai default yang bersebelahan dan parameter opsional diciutkan jika memungkinkan.

Nilai yang disediakan secara eksplisit yang tidak cocok dengan segmen rute ditambahkan ke string kueri. Tabel berikut ini memperlihatkan hasilnya saat menggunakan template rute {controller}/{action}/{id?}.

Nilai Lingkungan Nilai Eksplisit Result
pengontrol = "Home" action = "Tentang" /Home/About
pengontrol = "Home" controller = "Pesanan", action = "Tentang" /Order/About
controller = "Home", warna = "Merah" action = "Tentang" /Home/About
pengontrol = "Home" action = "Tentang", color = "Merah" /Home/About?color=Red

Urutan parameter rute opsional

Parameter rute opsional harus datang setelah semua parameter rute yang diperlukan. Dalam kode berikut, parameter id dan name harus datang setelah parameter color:

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
    // GET /api/my/red/2/joe
    // GET /api/my/red/2
    // GET /api/my
    [HttpGet("{color}/{id:int?}/{name?}")]
    public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
    {
        return Ok($"{color} {id} {name ?? ""}");
    }
}

Masalah dengan pembatalan nilai rute

Kode berikut menunjukkan contoh skema generasi URL yang tidak didukung oleh rute:

app.MapControllerRoute(
    "default",
    "{culture}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

Dalam kode sebelumnya, culture parameter rute digunakan untuk pelokalan. Keinginannya adalah agar culture parameter selalu diterima sebagai nilai sekitar. Namun, culture parameter tidak diterima sebagai nilai sekitar karena cara kerja nilai yang diperlukan:

  • "default" Dalam templat rute, culture parameter rute berada di sebelah kiri controller, sehingga perubahan menjadi controller tidak akan membatalkan culture.
  • "blog" Dalam templat rute, culture parameter rute dianggap berada di sebelah kanan controller, yang muncul dalam nilai yang diperlukan.

Mengurai jalur URL dengan LinkParser

Kelas LinkParser menambahkan dukungan untuk mengurai jalur URL ke dalam sekumpulan nilai rute. Metode ini ParsePathByEndpointName mengambil nama titik akhir dan jalur URL, dan mengembalikan sekumpulan nilai rute yang diekstrak dari jalur URL.

Dalam contoh pengontrol berikut, GetProduct aksi menggunakan templat rute api/Products/{id} dan memiliki Name dari GetProduct.

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

Di kelas pengontrol yang sama, AddRelatedProduct tindakan mengharapkan jalur URL, pathToRelatedProduct, yang dapat disediakan sebagai parameter string kueri:

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

Dalam contoh sebelumnya, aksi AddRelatedProduct mengekstrak nilai rute id dari jalur URL. Misalnya, dengan jalur URL/api/Products/1, nilai relatedProductId ditetapkan ke 1. Pendekatan ini memungkinkan klien API untuk menggunakan jalur URL saat merujuk ke sumber daya, tanpa memerlukan pengetahuan tentang bagaimana URL tersebut disusun.

Mengonfigurasi metadata titik akhir

Tautan berikut ini menyediakan informasi tentang cara mengonfigurasi metadata titik akhir:

Pencocokan host dalam rute dengan RequireHost

RequireHost menerapkan batasan ke rute yang memerlukan host yang ditentukan. Parameter RequireHost atau [Host] dapat berupa:

  • Host: www.domain.com, cocok www.domain.com dengan port apa pun.
  • Host dengan wildcard: *.domain.com, cocok dengan www.domain.com, subdomain.domain.com, atau www.subdomain.domain.com pada port apa pun.
  • Port: *:5000, cocok dengan port 5000 dengan host apa pun.
  • Host dan port: www.domain.com:5000 atau *.domain.com:5000, cocok dengan host dan port.

Beberapa parameter dapat ditentukan menggunakan RequireHost atau [Host]. Batasan cocok dengan host yang valid untuk salah satu parameter. Misalnya, [Host("domain.com", "*.domain.com")] cocok dengan , domain.com, dan www.domain.comsubdomain.domain.com.

Kode berikut menggunakan RequireHost untuk memerlukan host yang ditentukan pada rute:

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Kode berikut menggunakan [Host] atribut pada pengontrol untuk memerlukan salah satu host yang ditentukan:

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Ketika atribut [Host] diterapkan ke pengendali dan metode aksi

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Kelompok Rute

Metode MapGroup ekstensi membantu mengatur grup titik akhir dengan awalan umum dan mengurangi kode berulang. Gunakan metode ini untuk menyesuaikan seluruh grup titik akhir dengan satu panggilan ke metode seperti RequireAuthorization dan WithMetadata yang menambahkan metadata titik akhir.

Misalnya, kode berikut membuat dua grup titik akhir serupa:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

Dalam skenario ini, Anda dapat menggunakan alamat relatif untuk Location header dalam hasil 201 Created :

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

Grup titik akhir pertama cocok hanya dengan permintaan yang diawali dengan /public/todos dan dapat diakses tanpa autentikasi apa pun. Grup titik akhir kedua hanya cocok dengan permintaan yang diawali dengan /private/todos dan memerlukan autentikasi.

QueryPrivateTodos adalah fungsi lokal yang memodifikasi parameter dari handler rute, agar dapat mengakses dan menyimpan data todo privat. QueryPrivateTodos berfungsi sebagai pabrik filter titik akhir.

Grup rute juga mendukung grup berlapis dan pola awalan kompleks dengan parameter rute dan batasan. Dalam contoh berikut, handler rute yang dipetakan ke user grup dapat menangkap parameter rute {org} dan {group} yang ditentukan dalam awalan grup luar.

Awalan juga dapat kosong. Pendekatan ini dapat berguna untuk menambahkan metadata titik akhir atau filter ke sekelompok titik akhir tanpa mengubah pola rute.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Menambahkan filter atau metadata ke grup menghasilkan perilaku yang sama seperti menambahkannya satu per satu ke setiap titik akhir (sebelum menambahkan filter atau metadata tambahan yang mungkin ada dalam grup dalam atau titik akhir tertentu).

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

Dalam contoh sebelumnya, penyaring luar mencatat permintaan masuk sebelum penyaring dalam, meskipun penyaring luar ditambahkan kedua. Karena filter diterapkan ke grup yang berbeda, urutan penambahan relatif satu sama lain tidak menjadi masalah. Urutan di mana filter ditambahkan penting saat diterapkan ke grup yang sama atau titik akhir tertentu.

Permintaan untuk /outer/inner/ mencatat data berikut:

/outer group filter
/inner group filter
MapGet filter

Panduan performa untuk perutean

Ketika aplikasi mengalami masalah kinerja, perutean sering dicurigai sebagai penyebab masalah. Alasan perutean dicurigai adalah bahwa kerangka kerja seperti pengontrol dan Razor Pages melaporkan jumlah waktu yang dihabiskan di dalam kerangka kerja dalam pesan pengelogan mereka. Ketika ada perbedaan signifikan antara waktu yang dilaporkan oleh pengontrol dan total waktu permintaan:

  • Pengembang menghilangkan kode aplikasi mereka sebagai sumber masalah.
  • Hal yang umum untuk mengasumsikan perutean menjadi penyebabnya.

Routing diukur kinerjanya menggunakan ribuan titik akhir. Tidak mungkin aplikasi umum akan mengalami masalah performa hanya dengan menjadi terlalu besar. Akar penyebab paling umum dari performa perutean yang lambat biasanya adalah middleware kustom yang bertingkah buruk.

Sampel kode berikut ini menunjukkan teknik dasar untuk mempersempit sumber penundaan:

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Mengatur waktu rute

  • Interleave setiap middleware dengan salinan middleware pengaturan waktu yang ditampilkan dalam kode sebelumnya.
  • Tambahkan pengidentifikasi unik untuk menghubungkan data waktu dengan kode.

Ini adalah cara dasar untuk mempersempit penundaan ketika signifikan, misalnya, lebih dari 10ms. Mengurangkan Time 2 dari Time 1 menunjukkan waktu yang dihabiskan dalam middleware UseRouting.

Kode berikut menggunakan pendekatan yang lebih ringkas untuk kode waktu sebelumnya:

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

Fitur routing yang berpotensi mahal

Daftar berikut memberikan wawasan tentang fitur perutean yang relatif lebih mahal dibandingkan dengan templat rute dasar.

  • Ekspresi reguler: Dimungkinkan untuk menulis ekspresi reguler yang kompleks, atau memiliki waktu berjalan lama dengan sejumlah kecil input.
  • Segmen kompleks ({x}-{y}-{z}):
    • Secara signifikan lebih mahal daripada mengurai segmen jalur URL reguler.
    • Mengakibatkan lebih banyak substring dialokasikan.
  • Akses data sinkron: Aplikasi-aplikasi kompleks memiliki akses database sebagai bagian dari pengarahan mereka. Gunakan titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext, yang asinkron.

Panduan untuk tabel rute besar

Secara default, ASP.NET Core menggunakan algoritma perutean yang menukar penggunaan memori untuk waktu CPU. Ini memiliki efek yang bagus bahwa waktu pencocokan rute hanya tergantung pada panjang jalur yang cocok dan bukan jumlah rute. Namun, pendekatan ini dapat berpotensi bermasalah dalam beberapa kasus, ketika aplikasi memiliki sejumlah besar rute (dalam ribuan) dan ada sejumlah besar awalan variabel dalam rute. Misalnya, jika rute memiliki parameter di segmen awal rute, seperti {parameter}/some/literal.

Aplikasi tidak mungkin mengalami situasi di mana ini adalah masalah kecuali:

  • Ada sejumlah besar rute di aplikasi menggunakan pola ini.
  • Ada sejumlah besar rute di aplikasi.

Cara menentukan apakah aplikasi mengalami masalah tabel rute besar

  • Ada dua gejala yang harus dicari:
    • Aplikasi ini lambat untuk memulai pada permintaan pertama.
      • Perhatikan bahwa ini diperlukan tetapi tidak cukup. Ada banyak masalah non-rute lainnya yang dapat menyebabkan aplikasi mulai lambat. Periksa kondisi di bawah ini untuk secara akurat menentukan aplikasi mengalami situasi ini.
    • Aplikasi ini mengonsumsi banyak memori selama startup dan pembuangan memori menunjukkan sejumlah besar instans Microsoft.AspNetCore.Routing.Matching.DfaNode.

Cara mengatasi masalah ini

Ada beberapa teknik dan pengoptimalan dapat diterapkan ke rute yang sebagian besar akan meningkatkan skenario ini:

  • Terapkan batasan rute ke parameter Anda, misalnya {parameter:int}, , {parameter:guid}, {parameter:regex(\\d+)}dll. jika memungkinkan.
    • Ini memungkinkan algoritma perutean untuk mengoptimalkan struktur yang digunakan untuk pencocokan secara internal dan secara drastis mengurangi memori yang digunakan.
    • Dalam sebagian besar kasus, ini akan cukup untuk kembali ke perilaku yang dapat diterima.
  • Ubah rute untuk memindahkan parameter ke segmen selanjutnya dalam templat.
    • Ini mengurangi jumlah kemungkinan "jalur" agar sesuai dengan titik akhir yang diberikan jalur.
  • Gunakan rute dinamis dan lakukan pemetaan ke pengontrol/halaman secara dinamis.
    • Ini dapat dicapai menggunakan MapDynamicControllerRoute dan MapDynamicPageRoute.

Panduan untuk penulis pustaka

Bagian ini berisi panduan untuk penulis pustaka yang membangun di atas perutean. Detail ini dimaksudkan untuk memastikan bahwa pengembang aplikasi memiliki pengalaman yang baik menggunakan pustaka dan kerangka kerja yang memperluas perutean.

Menentukan titik akhir

Untuk membuat kerangka kerja yang menggunakan perutean untuk pencocokan URL, mulailah dengan menentukan pengalaman pengguna yang dibangun di atas UseEndpoints.

DO dibangun di atas IEndpointRouteBuilder. Ini memungkinkan pengguna untuk menyusun kerangka kerja Anda dengan fitur ASP.NET Core lainnya tanpa kebingungan. Setiap templat ASP.NET Core menyertakan routing. Asumsikan perutean ada dan sudah dikenal oleh pengguna.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

DO mengembalikan jenis beton tertutup dari panggilan ke MapMyFramework(...) yang mengimplementasikan IEndpointConventionBuilder. Sebagian besar metode kerangka kerja Map... mengikuti pola ini. Antarmuka IEndpointConventionBuilder :

  • Memungkinkan metadata disusun.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan tipe Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka Anda sendiri ke builder. Tidak apa-apa untuk membungkus pembangun yang dideklarasikan kerangka kerja dan meneruskan panggilan ke dalamnya.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

PERTIMBANGKAN untuk menulis sendiri EndpointDataSource. EndpointDataSource adalah primitif tingkat rendah untuk mendeklarasikan dan memperbarui kumpulan titik akhir. EndpointDataSource adalah API canggih yang digunakan oleh pengontrol dan Razor Halaman.

Pengujian perutean memiliki contoh dasar sumber data yang tidak diperbarui.

PERTIMBANGKAN untuk menerapkan GetGroupedEndpoints. Ini memberikan kontrol penuh atas konvensi grup yang sedang diterapkan dan metadata akhir pada endpoint yang dikelompokkan. Misalnya, ini memungkinkan implementasi kustom EndpointDataSource untuk menjalankan filter titik akhir yang diterapkan pada grup.

JANGAN mencoba mendaftarkan EndpointDataSource secara default. Mengharuskan pengguna untuk mendaftarkan kerangka kerja Anda di UseEndpoints. Filosofi perutean adalah bahwa tidak ada yang disertakan secara default, dan UseEndpoints merupakan tempat untuk mendaftarkan endpoint.

Membuat middleware yang terintegrasi dengan perutean

PERTIMBANGKAN menentukan tipe metadata sebagai antarmuka.

DO memungkinkan untuk menggunakan jenis metadata sebagai atribut pada kelas dan metode.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Kerangka kerja seperti pengontrol dan Razor Pages mendukung penerapan atribut metadata ke jenis dan metode. Jika Anda mendeklarasikan jenis metadata:

  • Buat mereka dapat diakses sebagai atribut.
  • Sebagian besar pengguna terbiasa menerapkan atribut.

Mendeklarasikan jenis metadata sebagai antarmuka menambahkan lapisan fleksibilitas lain:

  • Antarmuka dapat dikomposisikan.
  • Pengembang dapat mendeklarasikan jenis mereka sendiri yang menggabungkan beberapa kebijakan.

DO memungkinkan untuk mengambil alih metadata, seperti yang ditunjukkan dalam contoh berikut:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Cara terbaik untuk mengikuti panduan ini adalah dengan menghindari penentuan metadata penanda:

  • Jangan hanya mencari ada tidaknya jenis metadata.
  • Tentukan properti pada metadata dan periksa properti .

Koleksi metadata diurutkan dan mendukung penimpaan berdasarkan prioritas. Dalam kasus pengontrol, metadata pada metode tindakan paling spesifik.

DO menjadikan middleware berguna baik dengan maupun tanpa perutean:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Sebagai contoh pedoman ini, pertimbangkan UseAuthorization middleware. Middleware otorisasi memungkinkan Anda untuk menginputkan sebuah kebijakan fallback. Kebijakan fallback, jika ditentukan, berlaku untuk keduanya:

  • Titik akhir tanpa kebijakan yang ditentukan.
  • Permintaan yang tidak cocok dengan titik akhir.

Ini membuat perangkat lunak perantara untuk otorisasi berguna di luar konteks perutean. Middleware otorisasi dapat digunakan untuk pemrograman middleware tradisional.

Pemecahan Masalah Diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan Development, atur tingkat log di appsettings.Development.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Sumber daya tambahan

Perutean bertanggung jawab untuk mencocokkan permintaan HTTP masuk dan mengirimkan permintaan tersebut ke titik akhir aplikasi yang dapat dieksekusi. Titik akhir adalah unit kode penanganan permintaan yang dapat dieksekusi aplikasi. Titik akhir ditentukan dalam aplikasi dan dikonfigurasi saat aplikasi dimulai. Proses pencocokan titik akhir dapat mengekstrak nilai dari URL permintaan dan menyediakan nilai tersebut untuk pemrosesan permintaan. Dengan memanfaatkan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang menghubungkan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Controllers
  • Razor Halaman
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambdas yang terdaftar dalam perutean.

Artikel ini membahas detail tingkat rendah perutean ASP.NET Core. Untuk informasi tentang mengonfigurasi perutean:

Dasar-dasar perutean

Kode berikut menunjukkan contoh dasar perutean:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Contoh sebelumnya mencakup satu titik akhir menggunakan MapGet metode :

  • Ketika permintaan HTTP GET dikirim ke URL /akar :
    • Delegasi permintaan dijalankan.
    • Halo Dunia! dicatat dalam respons HTTP.
  • Jika metode permintaan tidak GET atau URL akar bukan /, tidak ada rute yang cocok dan HTTP 404 dikembalikan.

Perutean menggunakan sepasang middleware, yang didaftarkan oleh UseRouting dan UseEndpoints:

  • UseRouting menambahkan pencocokan rute ke alur middleware. Middleware ini melihat kumpulan titik akhir yang ditentukan dalam aplikasi, dan memilih kecocokan terbaik berdasarkan permintaan.
  • UseEndpoints menambahkan eksekusi titik akhir ke alur middleware. Ini menjalankan delegasi yang terkait dengan titik akhir yang dipilih.

Aplikasi biasanya tidak perlu memanggil UseRouting atau UseEndpoints. WebApplicationBuilder mengonfigurasi alur middleware yang membungkus middleware yang ditambahkan Program.cs dengan UseRouting dan UseEndpoints. Namun, aplikasi dapat mengubah urutan di mana UseRouting dan UseEndpoints berjalan dengan memanggil metode ini secara eksplisit. Misalnya, kode berikut melakukan panggilan eksplisit ke UseRouting:

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Dalam kode sebelumnya:

  • Panggilan ke app.Use mendaftarkan middleware kustom yang berjalan di awal jalur pemrosesan.
  • Panggilan untuk UseRouting mengonfigurasi middleware pencocokan rute untuk dijalankan setelah middleware kustom.
  • Titik akhir yang terdaftar dengan MapGet berjalan di akhir pipeline.

Jika contoh sebelumnya tidak menyertakan panggilan ke UseRouting, middleware kustom akan berjalan setelah middleware yang melakukan pencocokan rute.

Endpoints

Metode MapGet ini digunakan untuk menentukan titik akhir. Titik akhir adalah sesuatu yang dapat berupa:

  • Dipilih, dengan mencocokkan URL dan metode HTTP.
  • Dijalankan, dengan menjalankan delegasi.

Titik akhir yang dapat dicocokkan dan dijalankan oleh aplikasi dikonfigurasi di UseEndpoints. Misalnya, MapGet, MapPost, dan metode serupa menghubungkan delegasi permintaan dengan sistem routing. Metode tambahan dapat digunakan untuk menghubungkan fitur kerangka kerja ASP.NET Core ke sistem perutean:

Contoh berikut menunjukkan perutean dengan templat rute yang lebih canggih:

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

String /hello/{name:alpha} adalah templat rute. Templat rute digunakan untuk mengonfigurasi bagaimana titik akhir cocok. Dalam hal ini, templat cocok:

  • URL seperti /hello/Docs
  • Jalur URL apa pun yang dimulai dengan /hello/ diikuti dengan urutan karakter alfabet. :alpha menerapkan batasan rute yang hanya cocok dengan karakter alfabet. Batasan rute dijelaskan nanti dalam artikel ini.

Segmen kedua dari jalur URL, {name:alpha}:

Contoh berikut menunjukkan perutean dengan pemeriksaan status kesehatan dan otorisasi:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan penerusan jalur.
  • Titik akhir dapat digunakan untuk mengonfigurasi perilaku otorisasi.

Panggilan MapHealthChecks menambahkan titik akhir pemeriksaan kesehatan. Menautkan RequireAuthorization ke panggilan ini melampirkan kebijakan otorisasi ke titik akhir.

Memanggil UseAuthentication dan UseAuthorization menambahkan middleware autentikasi dan otorisasi. Middleware ini ditempatkan di antara UseRouting dan UseEndpoints sehingga mereka dapat:

  • Lihat titik akhir mana yang dipilih oleh UseRouting.
  • Terapkan kebijakan otorisasi sebelum UseEndpoints dikirim ke titik akhir.

Metadata titik akhir

Dalam contoh sebelumnya, ada dua titik akhir, tetapi hanya titik akhir pemeriksaan kesehatan yang memiliki kebijakan otorisasi yang terlampir. Jika permintaan cocok dengan titik akhir pemeriksaan kesehatan, /healthz, pemeriksaan otorisasi dilakukan. Ini menunjukkan bahwa titik akhir dapat memiliki data tambahan yang melekat padanya. Data tambahan ini disebut metadata titik akhir:

  • Metadata dapat diproses dengan middleware berkesadaran perutean.
  • Metadata dapat dari jenis .NET apa pun.

Konsep perutean

Sistem perutean dibangun di atas alur middleware dengan menambahkan konsep titik akhir yang kuat. Titik akhir mewakili unit fungsionalitas aplikasi yang berbeda satu sama lain dalam hal perutean, otorisasi, dan sejumlah sistem ASP.NET Core.

definisi titik akhir ASP.NET Core

Titik akhir ASP.NET Core adalah:

Kode berikut menunjukkan cara mengambil dan memeriksa titik akhir yang cocok dengan permintaan saat ini:

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

Titik akhir, jika dipilih, dapat diambil dari HttpContext. Sifat-sifatnya dapat diperiksa. Objek titik akhir bersifat tetap dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya diseleksi oleh sistem perutean.

Dalam kode sebelumnya, app.Use mengonfigurasi middleware inline.

Kode berikut menunjukkan bahwa, tergantung di mana app.Use dipanggil dalam alur, mungkin tidak ada titik akhir:

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

Sampel sebelumnya menambahkan Console.WriteLine pernyataan yang menampilkan apakah titik akhir telah dipilih atau tidak. Untuk kejelasan, sampel menetapkan nama tampilan ke endpoint yang diberikan /.

Sampel sebelumnya juga mencakup panggilan ke UseRouting dan UseEndpoints untuk mengontrol dengan tepat kapan middleware ini berjalan dalam alur.

Menjalankan kode ini dengan URL / menampilkan:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

Menjalankan kode ini dengan URL lain akan menampilkan:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Output ini menunjukkan bahwa:

  • Titik akhir selalu null sebelum UseRouting dipanggil.
  • Jika kecocokan ditemukan, titik akhir tidak null antara UseRouting dan UseEndpoints.
  • Middleware UseEndpoints mengakhiri proses ketika kecocokan ditemukan. Terminal middleware didefinisikan nanti dalam artikel ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan metode SetEndpoint untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan tetap mendapatkan manfaat dari penggunaan endpoint. Titik akhir adalah primitif tingkat rendah seperti middleware, dan tidak digabungkan dengan implementasi perutean. Sebagian besar aplikasi tidak perlu mengganti UseRouting dengan logika kustom.

Middleware UseEndpoints dirancang untuk digunakan bersama dengan UseRouting middleware. Logika inti untuk menjalankan titik akhir tidak rumit. Gunakan GetEndpoint untuk mengambil titik akhir, lalu panggil propertinya RequestDelegate .

Kode berikut memperlihatkan bagaimana middleware dapat memengaruhi atau bereaksi terhadap perutean:

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

Contoh sebelumnya menunjukkan dua konsep penting:

  • Middleware dapat berjalan sebelum UseRouting untuk memodifikasi data yang akan dioperasikan oleh perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil rute sebelum titik akhir dijalankan.
    • Middleware yang berjalan antara UseRouting dan UseEndpoints:
      • Biasanya memeriksa metadata untuk memahami titik akhir.
      • Sering membuat keputusan keamanan, seperti yang dilakukan oleh UseAuthorization dan UseCors.
    • Kombinasi middleware dan metadata memungkinkan konfigurasi kebijakan per titik akhir.

Kode sebelumnya menunjukkan contoh middleware kustom yang mendukung kebijakan per titik akhir. Middleware menulis log audit ke konsol mengenai akses ke data sensitif. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan RequiresAuditAttribute metadata. Sampel ini menunjukkan pola opt-in di mana hanya endpoint yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem titik akhir metadata itu fleksibel. Logika ini dapat dirancang dengan cara apa pun yang sesuai dengan kasus penggunaan.

Kode sampel sebelumnya dimaksudkan untuk menunjukkan konsep dasar titik akhir. Sampel tidak ditujukan untuk penggunaan produksi. Versi yang lebih lengkap dari middleware audit log akan:

  • Masuk ke file atau database.
  • Sertakan detail seperti pengguna, alamat IP, nama titik akhir sensitif, dan lainnya.

Metadata kebijakan audit RequiresAuditAttribute didefinisikan sebagai Attribute untuk memudahkan penggunaan dengan kerangka kerja berbasis kelas seperti pengontrol dan SignalR. Saat menggunakan rute ke kode:

  • Metadata dilampirkan dengan API penyusun.
  • Kerangka kerja berbasis kelas mencakup semua atribut pada metode dan kelas yang sesuai saat membuat titik akhir.

Praktik terbaik untuk jenis metadata adalah menentukannya sebagai antarmuka atau atribut. Antarmuka dan atribut memungkinkan penggunaan kembali kode. Sistem metadata fleksibel dan tidak memberlakukan batasan apa pun.

Membandingkan middleware terminal dengan perutean

Contoh berikut menunjukkan middleware terminal dan perutean:

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Gaya middleware yang ditampilkan dengan Approach 1: adalah middleware terminal. Ini disebut middleware terminal karena melakukan operasi yang cocok:

  • Operasi pencocokan dalam sampel sebelumnya menggunakan Path == "/" untuk middleware dan Path == "/Routing" untuk perutean.
  • Ketika kecocokan berhasil, sistem menjalankan beberapa fungsi, kemudian mengembalikan nilai, bukan memanggil next middleware.

Ini disebut middleware terminal karena mengakhiri pencarian, menjalankan beberapa fungsionalitas, lalu mengembalikan.

Daftar berikut membandingkan middleware terminal dengan perutean:

  • Kedua pendekatan memungkinkan penghentian alur pemrosesan:
    • Middleware mengakhiri alur dengan mengembalikan daripada memanggil next.
    • Titik akhir selalu terminal.
  • Middleware terminal memungkinkan penempatan middleware di posisi sembarang dalam pipeline:
  • Middleware terminal memungkinkan kode sebarang untuk menentukan kapan middleware tersebut sesuai:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Routing menyediakan solusi sederhana untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Titik akhir berinteraksi dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan penghubungan manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Petugas untuk memproses permintaan.
  • Kumpulan metadata acak. Metadata digunakan untuk menerapkan aspek lintas berdasarkan kebijakan dan konfigurasi yang diterapkan pada setiap titik akhir.

Middleware terminal dapat menjadi alat yang efektif, tetapi dapat memerlukan:

  • Sejumlah besar pengkodian dan pengujian.
  • Integrasi manual dengan sistem lain untuk mencapai tingkat fleksibilitas yang diinginkan.

Pertimbangkan untuk mengintegrasikan dengan routing sebelum menulis terminal middleware.

Middleware terminal yang ada yang dapat terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir yang sadar akan perutean. MapHealthChecks menunjukkan pola untuk router-ware:

Kode berikut menunjukkan penggunaan MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

Sampel sebelumnya menunjukkan mengapa mengembalikan objek pembangun penting. Mengembalikan objek penyusun memungkinkan pengembang aplikasi untuk mengonfigurasi kebijakan seperti otorisasi untuk titik akhir. Dalam contoh ini, middleware pemeriksaan kesehatan tidak memiliki integrasi langsung dengan sistem otorisasi.

Sistem metadata dibuat sebagai respons terhadap masalah yang dihadapi oleh penulis ekstensibilitas menggunakan middleware terminal. Sangat bermasalah bagi setiap middleware untuk menerapkan integrasinya sendiri dengan sistem otorisasi.

Pencocokan URL

  • Apakah proses perutean cocok dengan permintaan masuk ke titik akhir.
  • Didasarkan pada data di jalur URL dan header.
  • Dapat diperluas untuk mempertimbangkan data apa pun dalam permintaan.

Ketika middleware perutean dieksekusi, ia menetapkan nilai Endpoint dan nilai rute ke fitur permintaan pada HttpContext dari permintaan saat ini.

  • Memanggil HttpContext.GetEndpoint mendapatkan titik akhir.
  • HttpRequest.RouteValues mendapatkan kumpulan nilai rute.

Middleware berjalan setelah middleware routing dapat memeriksa endpoint dan mengambil tindakan. Misalnya, middleware otorisasi dapat memeriksa kumpulan metadata dari titik akhir untuk menemukan kebijakan akses otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi endpoint yang dipilih dipanggil.

Sistem routing dalam endpoint routing bertanggung jawab atas semua keputusan dispatching. Karena middleware menerapkan kebijakan berdasarkan titik akhir yang dipilih, penting bahwa:

  • Keputusan apa pun yang dapat memengaruhi pengiriman atau penerapan kebijakan keamanan dibuat di dalam sistem perutean.

Warning

Untuk kompatibilitas mundur, saat delegasi titik akhir Pengontrol atau Razor Halaman dijalankan, properti RouteContext.RouteData diatur ke nilai yang sesuai berdasarkan pemrosesan permintaan yang dilakukan sejauh ini.

Jenisnya RouteContext akan ditandai usang dalam rilis mendatang:

  • Migrasikan RouteData.Values ke HttpRequest.RouteValues.
  • Migrasikan RouteData.DataTokens untuk mengambil IDataTokensMetadata dari metadata titik akhir.

Pencocokan URL beroperasi dalam serangkaian fase yang dapat dikonfigurasi. Dalam setiap fase, output adalah sekumpulan kecocokan. Kumpulan pertandingan dapat disempitkan lebih lanjut pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk endpoint yang sesuai. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL berdasarkan sekumpulan titik akhir dan pola rutenya, mengumpulkan semua kecocokan.
  2. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dengan batasan rute yang diterapkan.
  3. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dalam rangkaian MatcherPolicy instans.
  4. Gunakan EndpointSelector untuk membuat keputusan akhir dari daftar sebelumnya.

Daftar titik akhir diprioritaskan sesuai dengan:

Semua titik akhir yang cocok diproses di setiap fase hingga tercapai EndpointSelector . EndpointSelector adalah fase akhir. Ini memilih titik akhir prioritas tertinggi dari kecocokan sebagai kecocokan terbaik. Jika ada kecocokan lain dengan prioritas yang sama dengan kecocokan terbaik, pengecualian pertandingan ambigu akan dilemparkan.

Prioritas rute dihitung berdasarkan templat rute yang lebih spesifik yang diberi prioritas yang lebih tinggi. Misalnya, pertimbangkan templat /hello dan /{message}:

  • Keduanya cocok dengan jalur URL/hello.
  • /hello lebih spesifik dan oleh karena itu prioritas yang lebih tinggi.

Secara umum, prioritas rute melakukan pekerjaan yang baik untuk memilih kecocokan terbaik untuk jenis skema URL yang digunakan dalam praktiknya. Gunakan Order hanya jika perlu untuk menghindari ambiguitas.

Karena jenis ekstensibilitas yang disediakan oleh perutean, sistem perutean tidak mungkin menghitung rute ambigu sebelumnya. Pertimbangkan contoh seperti templat rute /{message:alpha} dan /{message:int}:

  • Batasan alpha hanya cocok dengan karakter alfabet.
  • Batasan int hanya cocok dengan angka.
  • Templat ini memiliki prioritas rute yang sama, tetapi tidak ada satu URL pun yang cocok untuk keduanya.
  • Jika sistem routing melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir penggunaan valid ini.

Warning

Urutan pelaksanaan operasi di dalam UseEndpoints tidak memengaruhi perilaku perutean, dengan satu pengecualian. MapControllerRoute dan MapAreaRoute secara otomatis menetapkan nilai pesanan ke titik akhir mereka berdasarkan urutan yang dipanggil. Ini mensimulasikan perilaku jangka panjang pengontrol tanpa sistem perutean yang menyediakan jaminan yang sama seperti implementasi perutean lama.

Perutean titik akhir di ASP.NET Core:

  • Tidak memiliki konsep rute.
  • Tidak memberikan jaminan pemesanan. Semua titik akhir diproses sekaligus.

Urutan pemilihan prioritas templat dan titik akhir rute

Prioritas templat rute adalah sistem yang menetapkan setiap templat rute nilai berdasarkan seberapa spesifiknya. Prioritas templat rute:

  • Menghindari kebutuhan untuk menyesuaikan urutan titik akhir dalam kasus umum.
  • Upaya untuk memenuhi ekspektasi wajar terhadap perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa /Products/List adalah pilihan yang lebih baik daripada /Products/{id} untuk jalur /Products/List URL. Ini berfungsi karena segmen /List harfiah dianggap memiliki prioritas yang lebih baik daripada segmen /{id}parameter .

Detail cara kerja prioritas digabungkan dengan bagaimana templat rute ditentukan:

  • Templat dengan lebih banyak segmen dianggap lebih spesifik.
  • Segmen dengan teks harfiah dianggap lebih spesifik daripada segmen parameter.
  • Segmen parameter dengan batasan dianggap lebih spesifik daripada satu tanpanya.
  • Segmen kompleks dianggap spesifik sebagai segmen parameter dengan batasan.
  • Parameter yang bersifat mencakup semua adalah yang paling tidak spesifik. Lihat catch-all di bagian Templat rute untuk informasi penting tentang rute catch-all.

Konsep pembuatan URL

Pembuatan URL:

  • Adalah proses di mana perutean dapat membuat jalur URL berdasarkan serangkaian nilai rute.
  • Memungkinkan pemisahan logis antara titik akhir dan URL yang mengaksesnya.

Rute titik akhir meliputi penggunaan API LinkGenerator. LinkGenerator adalah layanan singleton yang tersedia dari DI. LinkGenerator API dapat digunakan di luar konteks permintaan yang dijalankan. Mvc.IUrlHelper dan skenario yang mengandalkan IUrlHelper, seperti Tag Helpers, HTML Helpers, dan Tindakan Hasil, menggunakan LinkGenerator API secara internal untuk menyediakan kemampuan untuk membuat tautan.

Generator tautan didukung oleh konsep alamat dan skema alamat. Skema alamat adalah cara menentukan titik akhir yang harus dipertimbangkan untuk pembuatan tautan. Misalnya, skenario nama rute dan nilai rute yang dikenal banyak pengguna dari pengontrol dan Razor Pages diimplementasikan sebagai skema alamat.

Generator tautan dapat ditautkan ke pengontrol dan Razor Halaman melalui metode ekstensi berikut:

Kelebihan beban metode ini menerima argumen yang menyertakan HttpContext. Metode ini secara fungsional setara dengan Url.Action dan Url.Page, tetapi menawarkan fleksibilitas dan opsi tambahan.

Metode GetPath* paling mirip dengan Url.Action dan Url.Page, karena itu menghasilkan URI yang berisi jalur absolut. Metode GetUri* tersebut selalu menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima HttpContext menghasilkan URI dalam konteks permintaan yang sedang dijalankan. Nilai rute lingkungan, jalur dasar URL, skema, dan host dari permintaan yang sedang dijalankan digunakan kecuali ditimpa.

LinkGenerator dipanggil dengan alamat. Menghasilkan URI terjadi dalam dua langkah:

  1. Alamat terikat ke daftar titik akhir yang sesuai dengan alamat tersebut.
  2. Setiap titik RoutePattern akhir dievaluasi hingga pola rute yang cocok dengan nilai yang disediakan ditemukan. Output yang dihasilkan dikombinasikan dengan bagian URI lain yang disediakan ke generator tautan dan dikembalikan.

Metode yang disediakan oleh LinkGenerator mendukung kemampuan pembuatan tautan standar untuk semua jenis alamat. Cara paling nyaman untuk menggunakan generator tautan adalah melalui metode ekstensi yang melakukan operasi untuk jenis alamat tertentu:

Metode Ekstensi Description
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Warning

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan metode ekstensi GetUri* dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi header Host dari permintaan yang masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak dipercaya dapat dikirim kembali ke klien dalam URI pada tampilan atau halaman. Kami merekomendasikan agar semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi header Host terhadap nilai valid yang diketahui.

  • Gunakan LinkGenerator dengan hati-hati dalam middleware dalam kombinasi dengan Map atau MapWhen. Map* mengubah jalur dasar permintaan eksekusi, yang memengaruhi output pembuatan tautan. LinkGenerator Semua API memungkinkan menentukan jalur dasar. Tentukan jalur dasar kosong untuk membatalkan Map* pengaruh pada pembuatan tautan.

Contoh middleware

Dalam contoh berikut, middleware menggunakan LinkGenerator API untuk membuat tautan ke metode tindakan yang mencantumkan produk penyimpanan. Menggunakan generator tautan dengan menyuntikkannya ke kelas dan panggilan GenerateLink tersedia untuk kelas apa pun di aplikasi:

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

Pola Rute

Token dalam {} menentukan parameter rute yang terikat jika rute cocok. Lebih dari satu parameter rute dapat didefinisikan dalam segmen rute, tetapi parameter rute harus dipisahkan oleh nilai harfiah. Contohnya:

{controller=Home}{action=Index}

bukan rute yang valid, karena tidak ada nilai harfiah antara {controller} dan {action}. Parameter rute harus memiliki nama dan mungkin memiliki atribut tambahan yang ditentukan.

Teks literal selain parameter rute (misalnya, {id}) dan pemisah / jalur harus cocok dengan teks di URL. Pencocokan teks tidak membedakan huruf besar atau kecil dan didasarkan pada representasi yang didekodekan dari jalur pada URL. Untuk mencocokkan pemisah parameter rute literal { atau }, hindari pemisah tersebut dengan mengulangi karakter tersebut. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

  • Dapat digunakan sebagai awalan untuk parameter rute untuk mengikat ke sisa URI.
  • Disebut sebagai parameter catch-all. Misalnya, blog/{**slug}:
    • Cocok dengan URI apa pun yang dimulai dengan blog/ dan memiliki nilai apa pun yang mengikutinya.
    • Nilai setelah blog/ ditetapkan ke nilai rute slug.

Warning

Parameter catch-all mungkin salah mencocokkan rute karena bug dalam perutean. Aplikasi yang terpengaruh oleh bug ini memiliki karakteristik berikut:

  • Rute tangkap-semua, misalnya, {**slug}"
  • Rute catch-all gagal mencocokkan permintaan yang seharusnya cocok.
  • Menghapus rute lain membuat rute catch-all mulai berfungsi.

Lihat bug GitHub 18677 dan 16579 misalnya kasus yang mengenai bug ini.

Perbaikan yang perlu diaktifkan untuk bug ini tersedia dalam .NET Core 3.1.301 atau SDK yang lebih baru. Kode program berikut menetapkan sakelar internal yang mengatasi bug ini:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Parameter catch-all juga dapat mencocokkan string kosong.

Parameter catch-all mengubah karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter-jalur pemisah /. Misalnya, rute foo/{*path} dengan nilai rute { path = "my/path" } menghasilkan foo/my%2Fpath. Perhatikan garis miring maju yang lolos. Untuk karakter pemisah jalur pulang-pergi, gunakan awalan ** parameter rute. Rute foo/{**path} dengan { path = "my/path" } menghasilkan foo/my/path.

Pola URL yang mencoba mengambil nama file dengan ekstensi file opsional memiliki pertimbangan tambahan. Misalnya, pertimbangkan templat files/{filename}.{ext?}. Ketika nilai untuk keduanya filename dan ext ada, kedua nilai diisi. Jika hanya nilai untuk filename yang ada di URL, rute cocok karena trailing . bersifat opsional. URL berikut cocok dengan rute ini:

  • /files/myFile.txt
  • /files/myFile

Parameter rute mungkin memiliki nilai default yang ditunjuk dengan menentukan nilai default setelah nama parameter dipisahkan oleh tanda sama dengan (=). Misalnya, {controller=Home} mendefinisikan Home sebagai nilai default untuk controller. Nilai default digunakan jika tidak ada nilai yang ada di URL untuk parameter . Parameter rute dibuat opsional dengan menambahkan tanda tanya (?) ke akhir nama parameter. Contohnya,id?. Perbedaan antara nilai opsional dan parameter rute default adalah:

  • Parameter rute dengan nilai default selalu menghasilkan nilai.
  • Parameter opsional hanya memiliki nilai saat nilai disediakan oleh URL permintaan.

Parameter rute mungkin memiliki batasan yang harus memenuhi nilai rute yang diperoleh dari URL. Menambahkan : dan nama batasan setelah nama parameter rute menentukan batasan sebaris pada parameter rute. Jika batasan memerlukan argumen, batasan tersebut diapit dalam tanda kurung (...) setelah nama batasan. Beberapa batasan sebaris dapat ditentukan dengan menambahkan satu : dan nama batasan lainnya.

Nama batasan dan argumen diteruskan ke layanan IInlineConstraintResolver untuk menciptakan instans IRouteConstraint yang akan digunakan dalam pemrosesan URL. Misalnya, templat blog/{article:minlength(10)} rute menentukan minlength batasan dengan argumen 10. Untuk informasi selengkapnya tentang batasan rute dan daftar batasan yang disediakan oleh kerangka kerja, lihat bagian Batasan rute.

Parameter rute juga dapat memiliki pengubah parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti pembatas, transformator parameter dapat ditambahkan secara sebaris ke parameter rute dengan menambahkan : dan nama transformator setelah nama parameter rute. Misalnya, templat rute blog/{article:slugify} menentukan sebuah transformer slugify. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Transformer parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Pola Rute Contoh Pencocokan URI Permintaan URI...
hello /hello Hanya cocok dengan jalur tunggal /hello.
{Page=Home} / Mencocokkan dan mengatur Page menjadi Home.
{Page=Home} /Contact Mencocokkan dan mengatur Page menjadi Contact.
{controller}/{action}/{id?} /Products/List Memetakan ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Memetakan ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Pemetaan ke controller Home dan metode Index. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Pemetaan ke controller Products dan metode Index. id diabaikan.

Menggunakan template umumnya adalah pendekatan paling sederhana untuk perutean. Batasan dan default juga dapat ditentukan di luar templat rute.

Segmen kompleks

Segmen kompleks diproses dengan mencocokkan pemisah harfiah dari kanan ke kiri dengan cara yang tidak serakah . Misalnya, [Route("/a{b}c{d}")] adalah segmen yang kompleks. Segmen kompleks bekerja dengan cara tertentu yang harus dipahami agar berhasil digunakan. Contoh di bagian ini menunjukkan mengapa segmen kompleks hanya benar-benar berfungsi dengan baik ketika teks pemisah tidak muncul di dalam nilai parameter. Menggunakan regex lalu mengekstrak nilai secara manual diperlukan untuk kasus yang lebih kompleks.

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ini adalah ringkasan langkah-langkah yang dilakukan oleh perutean dengan templat /a{b}c{d} dan jalur /abcd URL. | digunakan untuk membantu memvisualisasikan cara kerja algoritma:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /abcd juga dicari dari kanan dan menemukan /ab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Tidak ada teks yang tersisa dan tidak ada templat rute yang tersisa, jadi ini cocok.

Berikut adalah contoh kasus negatif menggunakan templat /a{b}c{d} yang sama dan jalur URL /aabcd. | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /aabcd juga dicari dari kanan dan menemukan /aab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Pada titik ini ada teks ayang tersisa , tetapi algoritma telah kehabisan templat rute untuk diurai, jadi ini bukan kecocokan.

Karena algoritma yang cocok tidak serakah:

  • Ini cocok dengan jumlah teks sekecil mungkin di setiap langkah.
  • Setiap kasus di mana nilai pemisah muncul di dalam nilai parameter menghasilkan tidak cocok.

Ekspresi reguler memberikan kendali yang jauh lebih besar terhadap perilaku pencocokannya.

Pencocokan rakus, juga dikenal sebagai pencocokan malas, cocok dengan string sebesar mungkin. Tidak serakah cocok dengan string sekecil mungkin.

Pengaturan Rute dengan Karakter Khusus

Pengarutan dengan karakter khusus dapat menyebabkan hasil yang tidak terduga. Misalnya, pertimbangkan pengontrol dengan metode tindakan berikut:

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Ketika string id berisi nilai yang dikodekan berikut, hasil yang tidak terduga mungkin terjadi:

ASCII Encoded
/ %2F
+

Parameter rute tidak selalu didekodekan URL. Masalah ini dapat diatasi di masa mendatang. Untuk informasi selengkapnya, lihat masalah GitHub ini;

Batasan rute

Batasan rute dijalankan ketika kecocokan telah terjadi pada URL masuk dan jalur URL ditokenisasi ke dalam nilai rute. Batasan rute umumnya memeriksa nilai rute yang terkait melalui templat rute dan membuat keputusan yang benar atau salah tentang apakah nilai tersebut dapat diterima. Beberapa batasan rute menggunakan data di luar nilai rute untuk mempertimbangkan apakah permintaan dapat dirutekan. Misalnya, HttpMethodRouteConstraint dapat menerima atau menolak permintaan berdasarkan kata kerja HTTP-nya. Batasan digunakan dalam permintaan perutean dan pembuatan tautan.

Warning

Jangan gunakan batasan untuk validasi input. Jika batasan digunakan untuk validasi input, input yang 404 tidak valid menghasilkan respons Tidak Ditemukan. Input yang tidak valid harus menghasilkan 400 Permintaan Buruk dengan pesan kesalahan yang sesuai. Batasan rute digunakan untuk memisahkan rute serupa, bukan untuk memvalidasi input untuk rute tertentu.

Tabel berikut menunjukkan contoh batasan rute dan perilaku yang diharapkan:

batasan Example Contoh Kecocokan Notes
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Sesuai dengan atau false. Case-insensitive
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Cocok dengan nilai yang valid DateTime dalam budaya invarian. Lihat peringatan sebelumnya.
decimal {price:decimal} 49.99, -1,000.01 Cocok dengan nilai yang valid decimal dalam budaya invarian. Lihat peringatan sebelumnya.
double {weight:double} 1.234, -1,001.01e8 Cocok dengan nilai yang valid double dalam budaya invarian. Lihat peringatan sebelumnya.
float {weight:float} 1.234, -1,001.01e8 Cocok dengan nilai yang valid float dalam budaya invarian. Lihat peringatan sebelumnya.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Mencocokkan dengan nilai Guid yang valid
long {ticks:long} 123456789, -123456789 Mencocokkan dengan nilai long yang valid
minlength(value) {username:minlength(4)} Rick String harus minimal 4 karakter
maxlength(value) {filename:maxlength(8)} MyFile String tidak boleh lebih dari 8 karakter
length(length) {filename:length(12)} somefile.txt Panjang string harus persis 12 karakter
length(min,max) {filename:length(8,16)} somefile.txt Panjang string harus minimal 8 dan tidak lebih dari 16 karakter
min(value) {age:min(18)} 19 Nilai bilangan bulat harus minimal 18
max(value) {age:max(120)} 91 Nilai bilangan bulat tidak boleh lebih dari 120
range(min,max) {age:range(18,120)} 91 Nilai bilangan bulat harus setidaknya 18 tetapi tidak lebih dari 120
alpha {name:alpha} Rick String harus terdiri dari satu atau beberapa karakter alfabet, a-z dan tidak peka huruf besar/kecil.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 String harus cocok dengan ekspresi reguler. Lihat tips tentang menentukan ekspresi reguler.
required {name:required} Rick Digunakan untuk memberlakukan bahwa nilai non-parameter ada selama pembuatan URL

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Beberapa batasan yang dipisahkan dengan titik dua dapat diterapkan ke satu parameter. Misalnya, batasan berikut membatasi parameter ke nilai bilangan bulat 1 atau lebih besar:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke CLR jenis int atau DateTime. Batasan ini mengasumsikan bahwa URL tidak dapat dilokalkan. Batasan rute yang disediakan kerangka kerja tidak mengubah nilai yang disimpan dalam nilai rute. Semua nilai rute yang diurai dari URL disimpan sebagai string. Misalnya, float batasan mencoba mengonversi nilai rute menjadi float, tetapi nilai yang dikonversi hanya digunakan untuk memverifikasi bahwa nilai tersebut dapat dikonversi ke float.

Ekspresi reguler dalam batasan

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ekspresi reguler dapat ditentukan sebagai pembatasan sebaris menggunakan batasan regex(...) route. Metode dalam MapControllerRoute keluarga juga menerima objek harfiah batasan. Jika formulir tersebut digunakan, nilai string ditafsirkan sebagai ekspresi reguler.

Kode berikut menggunakan batasan regex sebaris:

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

Kode berikut menggunakan objek literal untuk menentukan batasan regex:

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

Kerangka kerja ASP.NET Core ditambahkan RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ke konstruktor ekspresi reguler. Lihat RegexOptions untuk deskripsi anggota ini.

Ekspresi reguler menggunakan pemisah dan token yang mirip dengan yang digunakan oleh perutean dan bahasa C#. Token ekspresi reguler harus diloloskan. Untuk menggunakan ekspresi ^\d{3}-\d{2}-\d{4}$ reguler dalam batasan sebaris, gunakan salah satu hal berikut ini:

  • Ganti \ karakter yang disediakan dalam string sebagai \\ karakter dalam file sumber C# untuk menghindari \ karakter escape string.
  • String literal verbatim.

Untuk menghindari karakter pemisah parameter perutean seperti {, }, [, ], gandakan karakter tersebut dalam ekspresi, misalnya, {{, }}, [[, ]]. Tabel berikut ini memperlihatkan ekspresi reguler dan versi escape-nya:

Ekspresi reguler Ekspresi reguler yang di-escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Ekspresi reguler yang digunakan dalam perutean sering dimulai dengan karakter ^ dan cocok dengan posisi awal string. Ekspresi sering diakhiri dengan karakter $ dan mengakhiri string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. Tanpa karakter ^ dan $, ekspresi reguler mencocokkan substrak apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Expression String Match Comment
[a-z]{2} hello Yes Kecocokan substring
[a-z]{2} 123abc456 Yes Kecocokan substring
[a-z]{2} mz Yes Ekspresi yang cocok
[a-z]{2} MZ Yes Tidak peka huruf besar/kecil
^[a-z]{2}$ hello No Lihat ^ dan $ di atas
^[a-z]{2}$ 123abc456 No Lihat ^ dan $ di atas

Untuk informasi selengkapnya tentang sintaks ekspresi reguler, lihat .NET Framework Regular Expressions.

Untuk membatasi parameter ke sekumpulan nilai yang mungkin diketahui, gunakan ekspresi reguler. Misalnya, {action:regex(^(list|get|create)$)} hanya menyamai nilai jalur action dengan list, get, atau create. Jika diteruskan ke kamus batasan, string ^(list|get|create)$ setara. Batasan yang diteruskan dalam kamus batasan yang tidak cocok dengan salah satu batasan yang diketahui juga diperlakukan sebagai ekspresi reguler. Batasan yang diteruskan dalam templat yang tidak cocok dengan salah satu batasan yang diketahui tidak diperlakukan sebagai ekspresi reguler.

Pembatas rute khusus

Batasan rute kustom dapat dibuat dengan mengimplementasikan IRouteConstraint antarmuka. Antarmuka IRouteConstraint berisi Match, yang mengembalikan true jika batasan terpenuhi dan false sebaliknya.

Batasan rute kustom jarang diperlukan. Sebelum menerapkan batasan rute kustom, pertimbangkan alternatif, seperti pengikatan model.

Folder ASP.NET Core Constraints memberikan contoh yang baik untuk membuat batasan. Misalnya, GuidRouteConstraint.

Untuk menggunakan kustom IRouteConstraint, jenis batasan rute harus didaftarkan dengan aplikasi ConstraintMap di kontainer layanan. ConstraintMap adalah kamus yang memetakan kunci batasan rute ke IRouteConstraint implementasi yang memvalidasi batasan tersebut. Aplikasi ConstraintMap dapat diperbarui sebagai bagian dari Program.cs panggilan AddRouting atau dengan mengonfigurasi RouteOptions langsung dengan builder.Services.Configure<RouteOptions>. Contohnya:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Batasan sebelumnya diterapkan dalam kode berikut:

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

Implementasi NoZeroesRouteConstraint mencegah 0 digunakan dalam parameter rute:

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Kode sebelumnya:

  • Mencegah 0 di segmen {id} dari rute.
  • Ditunjukkan untuk memberikan contoh dasar penerapan batasan kustom. Ini tidak boleh digunakan dalam aplikasi produksi.

Pendekatan yang lebih baik untuk mencegah id yang mengandung 0 dari pemrosesan adalah dengan menggunakan kode berikut:

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

Kode sebelumnya memiliki keuntungan berikut daripada NoZeroesRouteConstraint pendekatan:

  • Ini tidak memerlukan batasan kustom.
  • Ini mengembalikan kesalahan yang lebih deskriptif ketika parameter rute menyertakan 0.

Parameter transformator

Pengubah parameter

Misalnya, transformator parameter kustom slugify dalam pola rute blog\{article:slugify} dengan Url.Action(new { article = "MyTestArticle" }) menghasilkan blog\my-test-article.

Pertimbangkan implementasi berikut IOutboundParameterTransformer :

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is null)
        {
            return null;
        }

        return Regex.Replace(
            value.ToString()!,
                "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100))
            .ToLowerInvariant();
    }
}

Untuk menggunakan transformator parameter dalam pola rute, konfigurasikan dengan ConstraintMap pada Program.cs.

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

Kerangka kerja ASP.NET Core menggunakan transformator parameter untuk mengubah URI tempat titik akhir diselesaikan. Misalnya, transformator parameter mengubah nilai rute yang digunakan untuk mencocokkan area, , controlleraction, dan page:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

Dengan templat rute sebelumnya, tindakan SubscriptionManagementController.GetAll dicocokkan dengan URI /subscription-management/get-all. Transformator parameter tidak mengubah nilai rute yang digunakan untuk menghasilkan tautan. Misalnya, Url.Action("GetAll", "SubscriptionManagement") menghasilkan /subscription-management/get-all.

ASP.NET Core menyediakan konvensi API untuk menggunakan transformator parameter dengan rute yang dihasilkan:

Referensi pembuatan URL

Bagian ini berisi referensi untuk algoritma yang diterapkan oleh pembuatan URL. Dalam praktiknya, sebagian besar contoh kompleks pembuatan URL menggunakan pengontrol atau halaman Razor. Lihat perutean di pengontrol untuk keterangan lebih lanjut.

Proses pembuatan URL dimulai dengan panggilan ke LinkGenerator.GetPathByAddress atau metode serupa. Metode ini disediakan dengan alamat, sekumpulan nilai rute, dan informasi opsional tentang permintaan saat ini dari HttpContext.

Langkah pertama adalah menggunakan alamat untuk menyelesaikan sekumpulan titik akhir kandidat menggunakan IEndpointAddressScheme<TAddress> yang cocok dengan jenis alamat.

Setelah kumpulan kandidat ditemukan oleh skema alamat, titik akhir diurutkan dan diproses secara berulang sampai operasi pembuatan URL berhasil. Pembuatan URL tidak memeriksa ambiguitas, hasil pertama yang dikembalikan adalah hasil akhir.

Pemecahan masalah pembuatan URL dengan pengelogan

Langkah pertama dalam pemecahan masalah pembuatan URL adalah mengatur tingkat pengelogan Microsoft.AspNetCore.Routing ke TRACE. LinkGenerator mencatat banyak detail tentang pemrosesannya yang dapat berguna untuk memecahkan masalah.

Lihat Referensi pembuatan URL untuk detail tentang pembuatan URL.

Addresses

Alamat adalah konsep dalam pembuatan URL yang digunakan untuk mengikat panggilan ke generator tautan ke sekumpulan titik akhir kandidat.

Alamat adalah konsep yang dapat diperluas yang dilengkapi dengan dua implementasi secara default:

  • Menggunakan nama titik akhir (string) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan nama rute pada MVC.
    • Menggunakan jenis metadata IEndpointNameMetadata.
    • Menyelesaikan string yang disediakan terhadap metadata semua titik akhir terdaftar.
    • Memberikan pengecualian pada startup jika beberapa titik akhir menggunakan nama yang sama.
    • Direkomendasikan untuk penggunaan tujuan umum di luar pengontrol dan Razor Halaman.
  • Menggunakan nilai rute (RouteValuesAddress) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan pengontrol dan pembuatan URL lama untuk Pages.
    • Sangat kompleks untuk diperpanjang dan di-debug.
    • Menyediakan implementasi yang digunakan oleh IUrlHelper, Pembantu Tag, Pembantu HTML, Hasil Tindakan, dll.

Peran skema alamat adalah membuat hubungan antara alamat dan titik akhir yang cocok dengan kriteria semena-mena:

  • Skema nama titik akhir melakukan pencarian kamus dasar.
  • Skema nilai rute memiliki subset algoritma set terbaik yang kompleks.

Nilai sekitar dan nilai eksplisit

Dari permintaan saat ini, perutean mengakses nilai rute permintaan HttpContext.Request.RouteValuessaat ini . Nilai yang terkait dengan permintaan saat ini disebut sebagai nilai ambient. Untuk tujuan kejelasan, dokumentasi mengacu pada nilai rute yang diteruskan ke metode sebagai nilai eksplisit.

Contoh berikut menunjukkan nilai sekitar dan nilai eksplisit. Ini menyediakan nilai pengaturan lingkungan dari permintaan saat ini dan nilai eksplisit.

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

Kode sebelumnya:

Kode berikut hanya menyediakan nilai eksplisit dan tanpa nilai sekitar:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Metode sebelumnya mengembalikan /Home/Subscribe/17

Kode berikut mengembalikan WidgetController/Widget/Subscribe/17:

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

Kode berikut menyediakan pengontrol dari nilai sekitar dalam permintaan saat ini dan nilai eksplisit:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Dalam kode sebelumnya:

  • /Gadget/Edit/17 dikembalikan.
  • Url mendapatkan IUrlHelper.
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama action dan nilai route yang ditentukan.

Kode berikut menyediakan nilai sekitar dari permintaan saat ini dan nilai eksplisit:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Kode sebelumnya mengatur url ke /Edit/17 ketika Halaman Edit Razor memuat arahan halaman berikut ini:

@page "{id:int}"

Jika halaman Edit tidak berisi "{id:int}" templat rute, url adalah /Edit?id=17.

Perilaku MVC IUrlHelper menambahkan lapisan kompleksitas selain aturan yang dijelaskan di sini:

  • IUrlHelper selalu menyediakan nilai rute dari permintaan saat ini sebagai nilai ambien.
  • IUrlHelper.Action selalu menyalin nilai rute action dan controller saat ini sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali digantikan.
  • IUrlHelper.Page selalu menggantikan nilai rute saat ini handler dengan null sebagai nilai eksplisit, kecuali dikesampingkan.

Pengguna sering terkejut dengan detail perilaku nilai sekitar, karena MVC tampaknya tidak mengikuti aturannya sendiri. Untuk alasan historis dan kompatibilitas, nilai rute tertentu seperti action, , controllerpage, dan handler memiliki perilaku kasus khusus mereka sendiri.

Fungsionalitas yang setara yang disediakan oleh LinkGenerator.GetPathByAction dan LinkGenerator.GetPathByPage menduplikasi anomali IUrlHelper ini untuk kompatibilitas.

Proses pembuatan URL

Setelah kumpulan titik akhir kandidat ditemukan, algoritma pembuatan URL:

  • Memproses titik akhir secara berulang.
  • Mengembalikan hasil pertama yang berhasil.

Langkah pertama dalam proses ini disebut pembatalan nilai rute. Invalidasi nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai lingkungan yang digunakan dan mana yang diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai ambien adalah mereka berusaha mengurangi pengetikan yang perlu dilakukan pengembang aplikasi, dalam beberapa kasus umum. Secara tradisional, skenario di mana nilai sekitar bermanfaat terkait dengan MVC:

  • Saat menautkan ke tindakan lain di pengontrol yang sama, nama pengontrol tidak perlu ditentukan.
  • Saat menautkan ke pengontrol lain di area yang sama, nama area tidak perlu ditentukan.
  • Saat menautkan ke metode tindakan yang sama, nilai rute tidak perlu ditentukan.
  • Saat menautkan ke bagian lain aplikasi, Anda tidak ingin membawa nilai rute yang tidak memiliki arti di bagian aplikasi tersebut.

Panggilan ke LinkGenerator atau IUrlHelper yang mengembalikan null biasanya disebabkan oleh tidak memahami ketidakvalidan nilai rute. Memecahkan masalah pembatalan nilai rute dengan secara eksplisit menentukan lebih banyak nilai rute untuk melihat apakah itu menyelesaikan masalah.

Pembatalan nilai rute berfungsi dengan asumsi bahwa skema URL aplikasi bersifat hierarkis, dengan hierarki yang terbentuk dari kiri-ke-kanan. Pertimbangkan templat {controller}/{action}/{id?} rute pengontrol dasar untuk mendapatkan rasa intuitif tentang cara kerjanya dalam praktik. Perubahan pada nilai membatalkan semua nilai rute yang muncul di sebelah kanan. Ini mencerminkan asumsi tentang hierarki. Jika aplikasi memiliki nilai lingkungan untuk id, dan operasi menentukan nilai yang berbeda untuk controller:

  • id tidak akan digunakan kembali karena {controller} berada di sebelah kiri {id?}.

Beberapa contoh yang menunjukkan prinsip ini:

  • Jika nilai eksplisit berisi nilai untuk id, nilai sekitar untuk id diabaikan. Nilai lingkungan untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai ambien untuk controller dapat digunakan. Jika nilai eksplisit untuk action berbeda dari nilai sekitar untuk action, id nilai tidak akan digunakan. Jika nilai eksplisit untuk action sama dengan nilai sekitar untuk action, id nilai dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk controller, nilai sekitar apa pun untuk controller diabaikan. Jika nilai eksplisit untuk controller berbeda dari nilai lingkungan untuk controller, nilai action dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai ambien untuk controller, action dan id nilai dapat digunakan.

Proses ini semakin rumit dengan adanya rute atribut dan rute konvensional khusus. Rute konvensional pengontrol seperti {controller}/{action}/{id?} menentukan hierarki menggunakan parameter rute. Untuk rute konvensional dedikasi dan rute atribut ke pengontrol dan Razor Halaman:

  • Ada hierarki nilai rute.
  • Mereka tidak muncul di templat.

Untuk kasus ini, pembuatan URL menentukan konsep nilai yang diperlukan. Titik akhir yang dibuat oleh pengontrol dan Razor Halaman memiliki nilai yang telah ditetapkan yang memungkinkan pembatalan nilai rute berfungsi dengan baik.

Algoritma invalidasi nilai rute secara rinci:

  • Nama nilai yang diperlukan dikombinasikan dengan parameter rute, lalu diproses dari kiri ke kanan.
  • Untuk setiap parameter, nilai sekitar dan nilai eksplisit dibandingkan:
    • Jika nilai sekitar dan nilai eksplisit sama, proses berlanjut.
    • Jika nilai sekitar ada dan nilai eksplisit tidak, nilai sekitar digunakan saat membuat URL.
    • Jika nilai sekitar tidak ada dan hanya nilai eksplisit yang ada, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika terdapat nilai ambien dan nilai eksplisit, dan kedua nilai tersebut berbeda, abaikan nilai ambien dan semua nilai ambien berikutnya.

Pada titik ini, operasi pembuatan URL siap untuk mengevaluasi batasan rute. Kumpulan nilai yang diterima dikombinasikan dengan nilai default parameter, yang disediakan untuk batasan. Jika semua batasan lolos, operasi berlanjut.

Selanjutnya, nilai yang diterima dapat digunakan untuk memperluas templat rute. Templat rute diproses:

  • Dari kiri-ke-kanan.
  • Setiap parameter digantikan dengan nilai yang diterima.
  • Dengan kasus khusus berikut:
    • Jika nilai yang diterima kehilangan nilai dan parameter memiliki nilai default, nilai default akan digunakan.
    • Jika nilai yang diterima kehilangan nilai dan parameter bersifat opsional, pemrosesan berlanjut.
    • Jika ada parameter rute di sebelah kanan parameter opsional yang hilang memiliki nilai, operasi gagal.
    • Parameter bernilai default yang bersebelahan dan parameter opsional diciutkan jika memungkinkan.

Nilai yang disediakan secara eksplisit yang tidak cocok dengan segmen rute ditambahkan ke string kueri. Tabel berikut ini memperlihatkan hasilnya saat menggunakan template rute {controller}/{action}/{id?}.

Nilai Lingkungan Nilai Eksplisit Result
pengontrol = "Home" action = "Tentang" /Home/About
pengontrol = "Home" controller = "Pesanan", action = "Tentang" /Order/About
controller = "Home", warna = "Merah" action = "Tentang" /Home/About
pengontrol = "Home" action = "Tentang", color = "Merah" /Home/About?color=Red

Masalah dengan pembatalan nilai rute

Kode berikut menunjukkan contoh skema generasi URL yang tidak didukung oleh rute:

app.MapControllerRoute(
    "default",
    "{culture}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

Dalam kode sebelumnya, culture parameter rute digunakan untuk pelokalan. Keinginannya adalah agar culture parameter selalu diterima sebagai nilai sekitar. Namun, culture parameter tidak diterima sebagai nilai sekitar karena cara kerja nilai yang diperlukan:

  • "default" Dalam templat rute, culture parameter rute berada di sebelah kiri controller, sehingga perubahan menjadi controller tidak akan membatalkan culture.
  • "blog" Dalam templat rute, culture parameter rute dianggap berada di sebelah kanan controller, yang muncul dalam nilai yang diperlukan.

Mengurai jalur URL dengan LinkParser

Kelas LinkParser menambahkan dukungan untuk mengurai jalur URL ke dalam sekumpulan nilai rute. Metode ini ParsePathByEndpointName mengambil nama titik akhir dan jalur URL, dan mengembalikan sekumpulan nilai rute yang diekstrak dari jalur URL.

Dalam contoh pengontrol berikut, GetProduct aksi menggunakan templat rute api/Products/{id} dan memiliki Name dari GetProduct.

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

Di kelas pengontrol yang sama, AddRelatedProduct tindakan mengharapkan jalur URL, pathToRelatedProduct, yang dapat disediakan sebagai parameter string kueri:

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

Dalam contoh sebelumnya, aksi AddRelatedProduct mengekstrak nilai rute id dari jalur URL. Misalnya, dengan jalur URL/api/Products/1, nilai relatedProductId ditetapkan ke 1. Pendekatan ini memungkinkan klien API untuk menggunakan jalur URL saat merujuk ke sumber daya, tanpa memerlukan pengetahuan tentang bagaimana URL tersebut disusun.

Mengonfigurasi metadata titik akhir

Tautan berikut ini menyediakan informasi tentang cara mengonfigurasi metadata titik akhir:

Pencocokan host dalam rute dengan RequireHost

RequireHost menerapkan batasan ke rute yang memerlukan host yang ditentukan. Parameter RequireHost atau [Host] dapat berupa:

  • Host: www.domain.com, cocok www.domain.com dengan port apa pun.
  • Host dengan wildcard: *.domain.com, cocok dengan www.domain.com, subdomain.domain.com, atau www.subdomain.domain.com pada port apa pun.
  • Port: *:5000, cocok dengan port 5000 dengan host apa pun.
  • Host dan port: www.domain.com:5000 atau *.domain.com:5000, cocok dengan host dan port.

Beberapa parameter dapat ditentukan menggunakan RequireHost atau [Host]. Batasan cocok dengan host yang valid untuk salah satu parameter. Misalnya, [Host("domain.com", "*.domain.com")] cocok dengan , domain.com, dan www.domain.comsubdomain.domain.com.

Kode berikut menggunakan RequireHost untuk memerlukan host yang ditentukan pada rute:

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Kode berikut menggunakan [Host] atribut pada pengontrol untuk memerlukan salah satu host yang ditentukan:

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Ketika atribut [Host] diterapkan ke pengendali dan metode aksi

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Panduan performa untuk perutean

Ketika aplikasi mengalami masalah kinerja, perutean sering dicurigai sebagai penyebab masalah. Alasan perutean dicurigai adalah bahwa kerangka kerja seperti pengontrol dan Razor Pages melaporkan jumlah waktu yang dihabiskan di dalam kerangka kerja dalam pesan pengelogan mereka. Ketika ada perbedaan signifikan antara waktu yang dilaporkan oleh pengontrol dan total waktu permintaan:

  • Pengembang menghilangkan kode aplikasi mereka sebagai sumber masalah.
  • Hal yang umum untuk mengasumsikan perutean menjadi penyebabnya.

Routing diukur kinerjanya menggunakan ribuan titik akhir. Tidak mungkin aplikasi umum akan mengalami masalah performa hanya dengan menjadi terlalu besar. Akar penyebab paling umum dari performa perutean yang lambat biasanya adalah middleware kustom yang bertingkah buruk.

Sampel kode berikut ini menunjukkan teknik dasar untuk mempersempit sumber penundaan:

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Mengatur waktu rute

  • Interleave setiap middleware dengan salinan middleware pengaturan waktu yang ditampilkan dalam kode sebelumnya.
  • Tambahkan pengidentifikasi unik untuk menghubungkan data waktu dengan kode.

Ini adalah cara dasar untuk mempersempit penundaan ketika signifikan, misalnya, lebih dari 10ms. Mengurangkan Time 2 dari Time 1 menunjukkan waktu yang dihabiskan dalam middleware UseRouting.

Kode berikut menggunakan pendekatan yang lebih ringkas untuk kode waktu sebelumnya:

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

Fitur routing yang berpotensi mahal

Daftar berikut memberikan wawasan tentang fitur perutean yang relatif lebih mahal dibandingkan dengan templat rute dasar.

  • Ekspresi reguler: Dimungkinkan untuk menulis ekspresi reguler yang kompleks, atau memiliki waktu berjalan lama dengan sejumlah kecil input.
  • Segmen kompleks ({x}-{y}-{z}):
    • Secara signifikan lebih mahal daripada mengurai segmen jalur URL reguler.
    • Mengakibatkan lebih banyak substring dialokasikan.
  • Akses data sinkron: Aplikasi-aplikasi kompleks memiliki akses database sebagai bagian dari pengarahan mereka. Gunakan titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext, yang asinkron.

Panduan untuk tabel rute besar

Secara default, ASP.NET Core menggunakan algoritma perutean yang menukar penggunaan memori untuk waktu CPU. Ini memiliki efek yang bagus bahwa waktu pencocokan rute hanya tergantung pada panjang jalur yang cocok dan bukan jumlah rute. Namun, pendekatan ini dapat berpotensi bermasalah dalam beberapa kasus, ketika aplikasi memiliki sejumlah besar rute (dalam ribuan) dan ada sejumlah besar awalan variabel dalam rute. Misalnya, jika rute memiliki parameter di segmen awal rute, seperti {parameter}/some/literal.

Aplikasi tidak mungkin mengalami situasi di mana ini adalah masalah kecuali:

  • Ada sejumlah besar rute di aplikasi menggunakan pola ini.
  • Ada sejumlah besar rute di aplikasi.

Cara menentukan apakah aplikasi mengalami masalah tabel rute besar

  • Ada dua gejala yang harus dicari:
    • Aplikasi ini lambat untuk memulai pada permintaan pertama.
      • Perhatikan bahwa ini diperlukan tetapi tidak cukup. Ada banyak masalah non-rute lainnya yang dapat menyebabkan aplikasi mulai lambat. Periksa kondisi di bawah ini untuk secara akurat menentukan aplikasi mengalami situasi ini.
    • Aplikasi ini mengonsumsi banyak memori selama startup dan pembuangan memori menunjukkan sejumlah besar instans Microsoft.AspNetCore.Routing.Matching.DfaNode.

Cara mengatasi masalah ini

Ada beberapa teknik dan pengoptimalan dapat diterapkan ke rute yang sebagian besar akan meningkatkan skenario ini:

  • Terapkan batasan rute ke parameter Anda, misalnya {parameter:int}, , {parameter:guid}, {parameter:regex(\\d+)}dll. jika memungkinkan.
    • Ini memungkinkan algoritma perutean untuk mengoptimalkan struktur yang digunakan untuk pencocokan secara internal dan secara drastis mengurangi memori yang digunakan.
    • Dalam sebagian besar kasus, ini akan cukup untuk kembali ke perilaku yang dapat diterima.
  • Ubah rute untuk memindahkan parameter ke segmen selanjutnya dalam templat.
    • Ini mengurangi jumlah kemungkinan "jalur" agar sesuai dengan titik akhir yang diberikan jalur.
  • Gunakan rute dinamis dan lakukan pemetaan ke pengontrol/halaman secara dinamis.
    • Ini dapat dicapai menggunakan MapDynamicControllerRoute dan MapDynamicPageRoute.

Panduan untuk penulis pustaka

Bagian ini berisi panduan untuk penulis pustaka yang membangun di atas perutean. Detail ini dimaksudkan untuk memastikan bahwa pengembang aplikasi memiliki pengalaman yang baik menggunakan pustaka dan kerangka kerja yang memperluas perutean.

Menentukan titik akhir

Untuk membuat kerangka kerja yang menggunakan perutean untuk pencocokan URL, mulailah dengan menentukan pengalaman pengguna yang dibangun di atas UseEndpoints.

DO dibangun di atas IEndpointRouteBuilder. Ini memungkinkan pengguna untuk menyusun kerangka kerja Anda dengan fitur ASP.NET Core lainnya tanpa kebingungan. Setiap templat ASP.NET Core menyertakan routing. Asumsikan perutean ada dan sudah dikenal oleh pengguna.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

DO mengembalikan jenis beton tertutup dari panggilan ke MapMyFramework(...) yang mengimplementasikan IEndpointConventionBuilder. Sebagian besar metode kerangka kerja Map... mengikuti pola ini. Antarmuka IEndpointConventionBuilder :

  • Memungkinkan metadata disusun.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan tipe Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka Anda sendiri ke builder. Tidak apa-apa untuk membungkus pembangun yang dideklarasikan kerangka kerja dan meneruskan panggilan ke dalamnya.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

PERTIMBANGKAN untuk menulis sendiri EndpointDataSource. EndpointDataSource adalah primitif tingkat rendah untuk mendeklarasikan dan memperbarui kumpulan titik akhir. EndpointDataSource adalah API canggih yang digunakan oleh pengontrol dan Razor Halaman.

Pengujian perutean memiliki contoh dasar sumber data yang tidak diperbarui.

JANGAN mencoba mendaftarkan EndpointDataSource secara default. Mengharuskan pengguna untuk mendaftarkan kerangka kerja Anda di UseEndpoints. Filosofi perutean adalah bahwa tidak ada yang disertakan secara default, dan UseEndpoints merupakan tempat untuk mendaftarkan endpoint.

Membuat middleware yang terintegrasi dengan perutean

PERTIMBANGKAN menentukan tipe metadata sebagai antarmuka.

DO memungkinkan untuk menggunakan jenis metadata sebagai atribut pada kelas dan metode.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Kerangka kerja seperti pengontrol dan Razor Pages mendukung penerapan atribut metadata ke jenis dan metode. Jika Anda mendeklarasikan jenis metadata:

  • Buat mereka dapat diakses sebagai atribut.
  • Sebagian besar pengguna terbiasa menerapkan atribut.

Mendeklarasikan jenis metadata sebagai antarmuka menambahkan lapisan fleksibilitas lain:

  • Antarmuka dapat dikomposisikan.
  • Pengembang dapat mendeklarasikan jenis mereka sendiri yang menggabungkan beberapa kebijakan.

DO memungkinkan untuk mengambil alih metadata, seperti yang ditunjukkan dalam contoh berikut:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Cara terbaik untuk mengikuti panduan ini adalah dengan menghindari penentuan metadata penanda:

  • Jangan hanya mencari ada tidaknya jenis metadata.
  • Tentukan properti pada metadata dan periksa properti .

Koleksi metadata diurutkan dan mendukung penimpaan berdasarkan prioritas. Dalam kasus pengontrol, metadata pada metode tindakan paling spesifik.

DO menjadikan middleware berguna baik dengan maupun tanpa perutean:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Sebagai contoh pedoman ini, pertimbangkan UseAuthorization middleware. Middleware otorisasi memungkinkan Anda untuk menginputkan sebuah kebijakan fallback. Kebijakan fallback, jika ditentukan, berlaku untuk keduanya:

  • Titik akhir tanpa kebijakan yang ditentukan.
  • Permintaan yang tidak cocok dengan titik akhir.

Ini membuat perangkat lunak perantara untuk otorisasi berguna di luar konteks perutean. Middleware otorisasi dapat digunakan untuk pemrograman middleware tradisional.

Pemecahan Masalah Diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan Development, atur tingkat log di appsettings.Development.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Sumber daya tambahan

Perutean bertanggung jawab untuk mencocokkan permintaan HTTP masuk dan mengirimkan permintaan tersebut ke titik akhir aplikasi yang dapat dieksekusi. Titik akhir adalah unit kode penanganan permintaan yang dapat dieksekusi aplikasi. Titik akhir ditentukan dalam aplikasi dan dikonfigurasi saat aplikasi dimulai. Proses pencocokan titik akhir dapat mengekstrak nilai dari URL permintaan dan menyediakan nilai tersebut untuk pemrosesan permintaan. Dengan memanfaatkan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang menghubungkan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Controllers
  • Razor Halaman
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambdas yang terdaftar dalam perutean.

Dokumen ini mencakup detail tingkat rendah perutean ASP.NET Core. Untuk informasi tentang mengonfigurasi perutean:

Sistem perutean titik akhir yang dijelaskan dalam dokumen ini berlaku untuk ASP.NET Core 3.0 atau yang lebih baru. Untuk mendapatkan informasi mengenai sistem perutean yang sebelumnya berbasis IRouter, pilih versi ASP.NET Core 2.1 dengan salah satu pendekatan berikut:

  • Pemilih versi untuk versi sebelumnya.
  • Pilih perutean ASP.NET Core 2.1.

Melihat atau mengunduh kode sampel (cara mengunduh)

Sampel unduhan untuk dokumen ini diaktifkan oleh kelas tertentu Startup . Untuk menjalankan sampel tertentu, ubah Program.cs untuk memanggil kelas yang diinginkan Startup .

Dasar-dasar perutean

Semua templat ASP.NET Core termasuk perutean dalam kode yang dihasilkan. Perutean terdaftar di alur middleware di Startup.Configure.

Kode berikut menunjukkan contoh dasar perutean:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Perutean menggunakan sepasang middleware, yang didaftarkan oleh UseRouting dan UseEndpoints:

  • UseRouting menambahkan pencocokan rute ke alur middleware. Middleware ini melihat kumpulan titik akhir yang ditentukan dalam aplikasi, dan memilih kecocokan terbaik berdasarkan permintaan.
  • UseEndpoints menambahkan eksekusi titik akhir ke alur middleware. Ini menjalankan delegasi yang terkait dengan titik akhir yang dipilih.

Contoh sebelumnya mencakup satu rute ke titik akhir kode menggunakan metode MapGet :

  • Ketika permintaan HTTP GET dikirim ke URL /akar :
    • Delegasi permintaan yang ditampilkan dijalankan.
    • Halo Dunia! dicatat dalam respons HTTP. Secara default, URL / akarnya adalah https://localhost:5001/.
  • Jika metode permintaan tidak GET atau URL akar bukan /, tidak ada rute yang cocok dan HTTP 404 dikembalikan.

Endpoint

Metode MapGet ini digunakan untuk menentukan titik akhir. Titik akhir adalah sesuatu yang dapat berupa:

  • Dipilih, dengan mencocokkan URL dan metode HTTP.
  • Dijalankan, dengan menjalankan delegasi.

Titik akhir yang dapat dicocokkan dan dijalankan oleh aplikasi dikonfigurasi di UseEndpoints. Misalnya, MapGet, MapPost, dan metode serupa menghubungkan delegasi permintaan dengan sistem routing. Metode tambahan dapat digunakan untuk menghubungkan fitur kerangka kerja ASP.NET Core ke sistem perutean:

Contoh berikut menunjukkan perutean dengan templat rute yang lebih canggih:

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/hello/{name:alpha}", async context =>
    {
        var name = context.Request.RouteValues["name"];
        await context.Response.WriteAsync($"Hello {name}!");
    });
});

String /hello/{name:alpha} adalah templat rute. Ini digunakan untuk mengonfigurasi bagaimana titik akhir cocok. Dalam hal ini, templat cocok:

  • URL seperti /hello/Ryan
  • Jalur URL apa pun yang dimulai dengan /hello/ diikuti dengan urutan karakter alfabet. :alpha menerapkan batasan rute yang hanya cocok dengan karakter alfabet. Batasan rute dijelaskan nanti dalam dokumen ini.

Segmen kedua dari jalur URL, {name:alpha}:

Sistem perutean titik akhir yang dijelaskan dalam dokumen ini baru sejak ASP.NET Core 3.0. Namun, semua versi ASP.NET Core mendukung serangkaian fitur templat rute dan batasan rute yang sama.

Contoh berikut menunjukkan perutean dengan pemeriksaan status kesehatan dan otorisasi:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan penerusan jalur.
  • Titik akhir dapat digunakan untuk mengonfigurasi perilaku otorisasi.

Panggilan MapHealthChecks menambahkan titik akhir pemeriksaan kesehatan. Menautkan RequireAuthorization ke panggilan ini melampirkan kebijakan otorisasi ke titik akhir.

Memanggil UseAuthentication dan UseAuthorization menambahkan middleware autentikasi dan otorisasi. Middleware ini ditempatkan di antara UseRouting dan UseEndpoints sehingga mereka dapat:

  • Lihat titik akhir mana yang dipilih oleh UseRouting.
  • Terapkan kebijakan otorisasi sebelum UseEndpoints dikirim ke titik akhir.

Metadata titik akhir

Dalam contoh sebelumnya, ada dua titik akhir, tetapi hanya titik akhir pemeriksaan kesehatan yang memiliki kebijakan otorisasi yang terlampir. Jika permintaan cocok dengan titik akhir pemeriksaan kesehatan, /healthz, pemeriksaan otorisasi dilakukan. Ini menunjukkan bahwa titik akhir dapat memiliki data tambahan yang melekat padanya. Data tambahan ini disebut metadata titik akhir:

  • Metadata dapat diproses dengan middleware berkesadaran perutean.
  • Metadata dapat dari jenis .NET apa pun.

Konsep perutean

Sistem perutean dibangun di atas alur middleware dengan menambahkan konsep titik akhir yang kuat. Titik akhir mewakili unit fungsionalitas aplikasi yang berbeda satu sama lain dalam hal perutean, otorisasi, dan sejumlah sistem ASP.NET Core.

definisi titik akhir ASP.NET Core

Titik akhir ASP.NET Core adalah:

Kode berikut menunjukkan cara mengambil dan memeriksa titik akhir yang cocok dengan permintaan saat ini:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.Use(next => context =>
    {
        var endpoint = context.GetEndpoint();
        if (endpoint is null)
        {
            return Task.CompletedTask;
        }
        
        Console.WriteLine($"Endpoint: {endpoint.DisplayName}");

        if (endpoint is RouteEndpoint routeEndpoint)
        {
            Console.WriteLine("Endpoint has route pattern: " +
                routeEndpoint.RoutePattern.RawText);
        }

        foreach (var metadata in endpoint.Metadata)
        {
            Console.WriteLine($"Endpoint has metadata: {metadata}");
        }

        return Task.CompletedTask;
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Titik akhir, jika dipilih, dapat diambil dari HttpContext. Sifat-sifatnya dapat diperiksa. Objek titik akhir bersifat tetap dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya diseleksi oleh sistem perutean.

Dalam kode sebelumnya, app.Use mengonfigurasi middleware in-line.

Kode berikut menunjukkan bahwa, tergantung di mana app.Use dipanggil dalam alur, mungkin tidak ada titik akhir:

// Location 1: before routing runs, endpoint is always null here
app.Use(next => context =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match
app.Use(next => context =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseEndpoints(endpoints =>
{
    // Location 3: runs when this endpoint matches
    endpoints.MapGet("/", context =>
    {
        Console.WriteLine(
            $"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
        return Task.CompletedTask;
    }).WithDisplayName("Hello");
});

// Location 4: runs after UseEndpoints - will only run if there was no match
app.Use(next => context =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

Sampel sebelumnya ini menambahkan Console.WriteLine pernyataan yang menampilkan apakah titik akhir telah dipilih atau tidak. Untuk kejelasan, sampel menetapkan nama tampilan ke endpoint yang diberikan /.

Menjalankan kode ini dengan URL / menampilkan:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

Menjalankan kode ini dengan URL lain akan menampilkan:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Output ini menunjukkan bahwa:

  • Titik akhir selalu null sebelum UseRouting dipanggil.
  • Jika kecocokan ditemukan, titik akhir tidak null antara UseRouting dan UseEndpoints.
  • Middleware UseEndpoints mengakhiri proses ketika kecocokan ditemukan. Middleware terminal didefinisikan nanti dalam dokumen ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan metode SetEndpoint untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan tetap mendapatkan manfaat dari penggunaan endpoint. Titik akhir adalah primitif tingkat rendah seperti middleware, dan tidak digabungkan dengan implementasi perutean. Sebagian besar aplikasi tidak perlu mengganti UseRouting dengan logika kustom.

Middleware UseEndpoints dirancang untuk digunakan bersama dengan UseRouting middleware. Logika inti untuk menjalankan titik akhir tidak rumit. Gunakan GetEndpoint untuk mengambil titik akhir, lalu panggil propertinya RequestDelegate .

Kode berikut memperlihatkan bagaimana middleware dapat memengaruhi atau bereaksi terhadap perutean:

public class IntegratedMiddlewareStartup
{ 
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // Location 1: Before routing runs. Can influence request before routing runs.
        app.UseHttpMethodOverride();

        app.UseRouting();

        // Location 2: After routing runs. Middleware can match based on metadata.
        app.Use(next => context =>
        {
            var endpoint = context.GetEndpoint();
            if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit
                                                                            == true)
            {
                Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
            }

            return next(context);
        });

        app.UseEndpoints(endpoints =>
        {         
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Hello world!");
            });

            // Using metadata to configure the audit policy.
            endpoints.MapGet("/sensitive", async context =>
            {
                await context.Response.WriteAsync("sensitive data");
            })
            .WithMetadata(new AuditPolicyAttribute(needsAudit: true));
        });

    } 
}

public class AuditPolicyAttribute : Attribute
{
    public AuditPolicyAttribute(bool needsAudit)
    {
        NeedsAudit = needsAudit;
    }

    public bool NeedsAudit { get; }
}

Contoh sebelumnya menunjukkan dua konsep penting:

  • Middleware dapat berjalan sebelum UseRouting untuk memodifikasi data yang akan dioperasikan oleh perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil rute sebelum titik akhir dijalankan.
    • Middleware yang berjalan antara UseRouting dan UseEndpoints:
      • Biasanya memeriksa metadata untuk memahami titik akhir.
      • Sering membuat keputusan keamanan, seperti yang dilakukan oleh UseAuthorization dan UseCors.
    • Kombinasi middleware dan metadata memungkinkan konfigurasi kebijakan per titik akhir.

Kode sebelumnya menunjukkan contoh middleware kustom yang mendukung kebijakan per titik akhir. Middleware menulis log audit ke konsol mengenai akses ke data sensitif. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan AuditPolicyAttribute metadata. Sampel ini menunjukkan pola opt-in di mana hanya endpoint yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem titik akhir metadata itu fleksibel. Logika ini dapat dirancang dengan cara apa pun yang sesuai dengan kasus penggunaan.

Kode sampel sebelumnya dimaksudkan untuk menunjukkan konsep dasar titik akhir. Sampel tidak ditujukan untuk penggunaan produksi. Versi yang lebih lengkap dari middleware audit log akan:

  • Masuk ke file atau database.
  • Sertakan detail seperti pengguna, alamat IP, nama titik akhir sensitif, dan lainnya.

Metadata kebijakan audit AuditPolicyAttribute didefinisikan sebagai Attribute untuk memudahkan penggunaan dengan kerangka kerja berbasis kelas seperti pengontrol dan SignalR. Saat menggunakan rute ke kode:

  • Metadata dilampirkan dengan API penyusun.
  • Kerangka kerja berbasis kelas mencakup semua atribut pada metode dan kelas yang sesuai saat membuat titik akhir.

Praktik terbaik untuk jenis metadata adalah menentukannya sebagai antarmuka atau atribut. Antarmuka dan atribut memungkinkan penggunaan kembali kode. Sistem metadata fleksibel dan tidak memberlakukan batasan apa pun.

Membandingkan middleware dan perutean terminal

Contoh kode berikut kontras menggunakan middleware dengan menggunakan perutean:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // Approach 1: Writing a terminal middleware.
    app.Use(next => async context =>
    {
        if (context.Request.Path == "/")
        {
            await context.Response.WriteAsync("Hello terminal middleware!");
            return;
        }

        await next(context);
    });

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // Approach 2: Using routing.
        endpoints.MapGet("/Movie", async context =>
        {
            await context.Response.WriteAsync("Hello routing!");
        });
    });
}

Gaya middleware yang ditampilkan dengan Approach 1: adalah middleware terminal. Ini disebut middleware terminal karena melakukan operasi yang cocok:

  • Operasi pencocokan dalam sampel sebelumnya menggunakan Path == "/" untuk middleware dan Path == "/Movie" untuk perutean.
  • Ketika kecocokan berhasil, sistem menjalankan beberapa fungsi, kemudian mengembalikan nilai, bukan memanggil next middleware.

Ini disebut middleware terminal karena mengakhiri pencarian, menjalankan beberapa fungsionalitas, lalu mengembalikan.

Membandingkan middleware terminal dan perutean:

  • Kedua pendekatan memungkinkan penghentian alur pemrosesan:
    • Middleware mengakhiri alur dengan mengembalikan daripada memanggil next.
    • Titik akhir selalu terminal.
  • Middleware terminal memungkinkan penempatan middleware di posisi sembarang dalam pipeline:
  • Middleware terminal memungkinkan kode sebarang untuk menentukan kapan middleware tersebut sesuai:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Routing menyediakan solusi sederhana untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Titik akhir berinteraksi dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan penghubungan manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Petugas untuk memproses permintaan.
  • Kumpulan metadata acak. Metadata digunakan untuk menerapkan aspek lintas berdasarkan kebijakan dan konfigurasi yang diterapkan pada setiap titik akhir.

Middleware terminal dapat menjadi alat yang efektif, tetapi dapat memerlukan:

  • Sejumlah besar pengkodian dan pengujian.
  • Integrasi manual dengan sistem lain untuk mencapai tingkat fleksibilitas yang diinginkan.

Pertimbangkan untuk mengintegrasikan dengan routing sebelum menulis terminal middleware.

Middleware terminal yang ada yang dapat terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir yang sadar akan perutean. MapHealthChecks menunjukkan pola untuk router-ware:

Kode berikut menunjukkan penggunaan MapHealthChecks:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Sampel sebelumnya menunjukkan mengapa mengembalikan objek pembangun penting. Mengembalikan objek penyusun memungkinkan pengembang aplikasi untuk mengonfigurasi kebijakan seperti otorisasi untuk titik akhir. Dalam contoh ini, middleware pemeriksaan kesehatan tidak memiliki integrasi langsung dengan sistem otorisasi.

Sistem metadata dibuat sebagai respons terhadap masalah yang dihadapi oleh penulis ekstensibilitas menggunakan middleware terminal. Sangat bermasalah bagi setiap middleware untuk menerapkan integrasinya sendiri dengan sistem otorisasi.

Pencocokan URL

  • Apakah proses perutean cocok dengan permintaan masuk ke titik akhir.
  • Didasarkan pada data di jalur URL dan header.
  • Dapat diperluas untuk mempertimbangkan data apa pun dalam permintaan.

Ketika middleware perutean dieksekusi, ia menetapkan nilai Endpoint dan nilai rute ke fitur permintaan pada HttpContext dari permintaan saat ini.

  • Memanggil HttpContext.GetEndpoint mendapatkan titik akhir.
  • HttpRequest.RouteValues mendapatkan kumpulan nilai rute.

Middleware berjalan setelah middleware routing dapat memeriksa endpoint dan mengambil tindakan. Misalnya, middleware otorisasi dapat memeriksa kumpulan metadata dari titik akhir untuk menemukan kebijakan akses otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi endpoint yang dipilih dipanggil.

Sistem routing dalam endpoint routing bertanggung jawab atas semua keputusan dispatching. Karena middleware menerapkan kebijakan berdasarkan titik akhir yang dipilih, penting bahwa:

  • Keputusan apa pun yang dapat memengaruhi pengiriman atau penerapan kebijakan keamanan dibuat di dalam sistem perutean.

Warning

Untuk kompatibilitas ke belakang, saat delegasi titik akhir Pengontrol atau Razor Halaman dieksekusi, properti RouteContext.RouteData akan ditetapkan dengan nilai yang sesuai berdasarkan pemrosesan permintaan yang dilakukan sejauh ini.

Jenisnya RouteContext akan ditandai usang dalam rilis mendatang:

  • Migrasikan RouteData.Values ke HttpRequest.RouteValues.
  • Migrasikan RouteData.DataTokens untuk mengambil IDataTokensMetadata dari metadata titik akhir.

Pencocokan URL beroperasi dalam serangkaian fase yang dapat dikonfigurasi. Dalam setiap fase, output adalah sekumpulan kecocokan. Kumpulan pertandingan dapat disempitkan lebih lanjut pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk endpoint yang sesuai. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL berdasarkan sekumpulan titik akhir dan pola rutenya, mengumpulkan semua kecocokan.
  2. Mengambil daftar sebelumnya dan menghapus kecocokan yang gagal dengan batasan rute yang diterapkan.
  3. Mengambil daftar sebelumnya dan menghapus kecocokan yang tidak memenuhi kumpulan instans MatcherPolicy.
  4. Menggunakan EndpointSelector untuk membuat keputusan akhir dari daftar sebelumnya.

Daftar titik akhir diprioritaskan sesuai dengan:

Semua titik akhir yang cocok diproses di setiap fase hingga tercapai EndpointSelector . EndpointSelector adalah fase akhir. Ini memilih titik akhir prioritas tertinggi dari kecocokan sebagai kecocokan terbaik. Jika ada kecocokan lain dengan prioritas yang sama dengan kecocokan terbaik, pengecualian pertandingan ambigu akan dilemparkan.

Prioritas rute dihitung berdasarkan templat rute yang lebih spesifik yang diberi prioritas yang lebih tinggi. Misalnya, pertimbangkan templat /hello dan /{message}:

  • Keduanya cocok dengan jalur URL/hello.
  • /hello lebih spesifik dan oleh karena itu prioritas yang lebih tinggi.

Secara umum, prioritas rute melakukan pekerjaan yang baik untuk memilih kecocokan terbaik untuk jenis skema URL yang digunakan dalam praktiknya. Gunakan Order hanya jika perlu untuk menghindari ambiguitas.

Karena jenis ekstensibilitas yang disediakan oleh perutean, sistem perutean tidak mungkin menghitung rute ambigu sebelumnya. Pertimbangkan contoh seperti templat rute /{message:alpha} dan /{message:int}:

  • Batasan alpha hanya cocok dengan karakter alfabet.
  • Batasan int hanya cocok dengan angka.
  • Templat ini memiliki prioritas rute yang sama, tetapi tidak ada satu URL pun yang cocok untuk keduanya.
  • Jika sistem routing melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir penggunaan valid ini.

Warning

Urutan pelaksanaan operasi di dalam UseEndpoints tidak memengaruhi perilaku perutean, dengan satu pengecualian. MapControllerRoute dan MapAreaRoute secara otomatis menetapkan nilai pesanan ke titik akhir mereka berdasarkan urutan yang dipanggil. Ini mensimulasikan perilaku jangka panjang pengontrol tanpa sistem perutean yang menyediakan jaminan yang sama seperti implementasi perutean lama.

Dalam implementasi perutean warisan, dimungkinkan untuk menerapkan ekstensibilitas perutean yang memiliki dependensi pada urutan di mana rute diproses. Perutean titik akhir di ASP.NET Core 3.0 atau yang lebih baru:

  • Tidak memiliki konsep routing.
  • Tidak memberikan jaminan pemesanan. Semua titik akhir diproses sekaligus.

Urutan pemilihan prioritas templat dan titik akhir rute

Prioritas templat rute adalah sistem yang menetapkan setiap templat rute nilai berdasarkan seberapa spesifiknya. Prioritas templat rute:

  • Menghindari kebutuhan untuk menyesuaikan urutan titik akhir dalam kasus umum.
  • Upaya untuk memenuhi ekspektasi wajar terhadap perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa /Products/List adalah pilihan yang lebih baik daripada /Products/{id} untuk jalur /Products/List URL. Ini berfungsi karena segmen /List harfiah dianggap memiliki prioritas yang lebih baik daripada segmen /{id}parameter .

Detail cara kerja prioritas digabungkan dengan bagaimana templat rute ditentukan:

  • Templat dengan lebih banyak segmen dianggap lebih spesifik.
  • Segmen dengan teks harfiah dianggap lebih spesifik daripada segmen parameter.
  • Segmen parameter dengan batasan dianggap lebih spesifik daripada satu tanpanya.
  • Segmen kompleks dianggap spesifik sebagai segmen parameter dengan batasan.
  • Parameter yang bersifat mencakup semua adalah yang paling tidak spesifik. Lihat catch-all di Referensi Templat Rute untuk informasi penting tentang rute catch-all.

Lihat kode sumber di GitHub untuk referensi nilai yang tepat.

Konsep pembuatan URL

Pembuatan URL:

  • Adalah proses di mana perutean dapat membuat jalur URL berdasarkan serangkaian nilai rute.
  • Memungkinkan pemisahan logis antara titik akhir dan URL yang mengaksesnya.

Rute titik akhir meliputi penggunaan API LinkGenerator. LinkGenerator adalah layanan singleton yang tersedia dari DI. LinkGenerator API dapat digunakan di luar konteks permintaan yang dijalankan. Mvc.IUrlHelper dan skenario yang mengandalkan IUrlHelper, seperti Tag Helpers, HTML Helpers, dan Tindakan Hasil, menggunakan LinkGenerator API secara internal untuk menyediakan kemampuan untuk membuat tautan.

Generator tautan didukung oleh konsep alamat dan skema alamat. Skema alamat adalah cara menentukan titik akhir yang harus dipertimbangkan untuk pembuatan tautan. Misalnya, skenario nama rute dan nilai rute yang dikenal banyak pengguna dari pengontrol dan Razor Pages diimplementasikan sebagai skema alamat.

Generator tautan dapat ditautkan ke pengontrol dan Razor Halaman melalui metode ekstensi berikut:

Kelebihan beban metode ini menerima argumen yang menyertakan HttpContext. Metode ini secara fungsional setara dengan Url.Action dan Url.Page, tetapi menawarkan fleksibilitas dan opsi tambahan.

Metode GetPath* paling mirip dengan Url.Action dan Url.Page, karena itu menghasilkan URI yang berisi jalur absolut. Metode GetUri* tersebut selalu menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima HttpContext menghasilkan URI dalam konteks permintaan yang sedang dijalankan. Nilai rute lingkungan, jalur dasar URL, skema, dan host dari permintaan yang sedang dijalankan digunakan kecuali ditimpa.

LinkGenerator dipanggil dengan alamat. Menghasilkan URI terjadi dalam dua langkah:

  1. Alamat terikat ke daftar titik akhir yang sesuai dengan alamat tersebut.
  2. Setiap titik RoutePattern akhir dievaluasi hingga pola rute yang cocok dengan nilai yang disediakan ditemukan. Output yang dihasilkan dikombinasikan dengan bagian URI lain yang disediakan ke generator tautan dan dikembalikan.

Metode yang disediakan oleh LinkGenerator mendukung kemampuan pembuatan tautan standar untuk semua jenis alamat. Cara paling nyaman untuk menggunakan generator tautan adalah melalui metode ekstensi yang melakukan operasi untuk jenis alamat tertentu:

Metode Ekstensi Description
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Warning

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan metode ekstensi GetUri* dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi header Host dari permintaan yang masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak dipercaya dapat dikirim kembali ke klien dalam URI pada tampilan atau halaman. Kami merekomendasikan agar semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi header Host terhadap nilai valid yang diketahui.

  • Gunakan LinkGenerator dengan hati-hati dalam middleware dalam kombinasi dengan Map atau MapWhen. Map* mengubah jalur dasar permintaan eksekusi, yang memengaruhi output pembuatan tautan. LinkGenerator Semua API memungkinkan menentukan jalur dasar. Tentukan jalur dasar kosong untuk membatalkan Map* pengaruh pada pembuatan tautan.

Contoh middleware

Dalam contoh berikut, middleware menggunakan LinkGenerator API untuk membuat tautan ke metode tindakan yang mencantumkan produk penyimpanan. Menggunakan generator tautan dengan menyuntikkannya ke kelas dan panggilan GenerateLink tersedia untuk kelas apa pun di aplikasi:

public class ProductsLinkMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        var url = _linkGenerator.GetPathByAction("ListProducts", "Store");

        httpContext.Response.ContentType = "text/plain";

        await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
    }
}

Referensi templat rute

Token dalam {} menentukan parameter rute yang terikat jika rute cocok. Lebih dari satu parameter rute dapat didefinisikan dalam segmen rute, tetapi parameter rute harus dipisahkan oleh nilai harfiah. Misalnya, {controller=Home}{action=Index} bukan rute yang valid, karena tidak ada nilai harfiah antara {controller} dan {action}. Parameter rute harus memiliki nama dan mungkin memiliki atribut tambahan yang ditentukan.

Teks literal selain parameter rute (misalnya, {id}) dan pemisah / jalur harus cocok dengan teks di URL. Pencocokan teks tidak membedakan huruf besar atau kecil dan didasarkan pada representasi yang didekodekan dari jalur pada URL. Untuk mencocokkan pemisah parameter rute literal { atau }, hindari pemisah tersebut dengan mengulangi karakter tersebut. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

  • Dapat digunakan sebagai awalan untuk parameter rute untuk mengikat ke sisa URI.
  • Disebut sebagai parameter catch-all. Misalnya, blog/{**slug}:
    • Cocok dengan URI apa pun yang dimulai dengan /blog dan memiliki nilai apa pun yang mengikutinya.
    • Nilai setelah /blog ditetapkan ke nilai rute slug.

Warning

Parameter catch-all mungkin salah mencocokkan rute karena bug dalam perutean. Aplikasi yang terpengaruh oleh bug ini memiliki karakteristik berikut:

  • Rute tangkap-semua, misalnya, {**slug}"
  • Rute catch-all gagal mencocokkan permintaan yang seharusnya cocok.
  • Menghapus rute lain membuat rute catch-all mulai berfungsi.

Lihat bug GitHub 18677 dan 16579 misalnya kasus yang mengenai bug ini.

Perbaikan yang perlu diaktifkan untuk bug ini tersedia dalam .NET Core 3.1.301 atau SDK yang lebih baru. Kode program berikut menetapkan sakelar internal yang mengatasi bug ini:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Parameter catch-all juga dapat mencocokkan string kosong.

Parameter catch-all mengubah karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter-jalur pemisah /. Misalnya, rute foo/{*path} dengan nilai rute { path = "my/path" } menghasilkan foo/my%2Fpath. Perhatikan garis miring maju yang lolos. Untuk karakter pemisah jalur pulang-pergi, gunakan awalan ** parameter rute. Rute foo/{**path} dengan { path = "my/path" } menghasilkan foo/my/path.

Pola URL yang mencoba mengambil nama file dengan ekstensi file opsional memiliki pertimbangan tambahan. Misalnya, pertimbangkan templat files/{filename}.{ext?}. Ketika nilai untuk keduanya filename dan ext ada, kedua nilai diisi. Jika hanya nilai untuk filename yang ada di URL, rute cocok karena trailing . bersifat opsional. URL berikut cocok dengan rute ini:

  • /files/myFile.txt
  • /files/myFile

Parameter rute mungkin memiliki nilai default yang ditunjuk dengan menentukan nilai default setelah nama parameter dipisahkan oleh tanda sama dengan (=). Misalnya, {controller=Home} mendefinisikan Home sebagai nilai default untuk controller. Nilai default digunakan jika tidak ada nilai yang ada di URL untuk parameter . Parameter rute dibuat opsional dengan menambahkan tanda tanya (?) ke akhir nama parameter. Contohnya,id?. Perbedaan antara nilai opsional dan parameter rute default adalah:

  • Parameter rute dengan nilai default selalu menghasilkan nilai.
  • Parameter opsional hanya memiliki nilai saat nilai disediakan oleh URL permintaan.

Parameter rute mungkin memiliki batasan yang harus memenuhi nilai rute yang diperoleh dari URL. Menambahkan : dan nama batasan setelah nama parameter rute menentukan batasan sebaris pada parameter rute. Jika batasan memerlukan argumen, batasan tersebut diapit dalam tanda kurung (...) setelah nama batasan. Beberapa batasan sebaris dapat ditentukan dengan menambahkan satu : dan nama batasan lainnya.

Nama batasan dan argumen diteruskan ke layanan IInlineConstraintResolver untuk menciptakan instans IRouteConstraint yang akan digunakan dalam pemrosesan URL. Misalnya, templat blog/{article:minlength(10)} rute menentukan minlength batasan dengan argumen 10. Untuk informasi selengkapnya tentang batasan rute dan daftar batasan yang disediakan oleh kerangka kerja, lihat bagian Referensi batasan rute.

Parameter rute juga dapat memiliki pengubah parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti pembatas, transformator parameter dapat ditambahkan secara sebaris ke parameter rute dengan menambahkan : dan nama transformator setelah nama parameter rute. Misalnya, templat rute blog/{article:slugify} menentukan sebuah transformer slugify. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Referensi transformator parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Pola Rute Contoh Pencocokan URI Permintaan URI...
hello /hello Hanya cocok dengan jalur tunggal /hello.
{Page=Home} / Mencocokkan dan mengatur Page menjadi Home.
{Page=Home} /Contact Mencocokkan dan mengatur Page menjadi Contact.
{controller}/{action}/{id?} /Products/List Memetakan ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Memetakan ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Pemetaan ke controller Home dan metode Index. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Pemetaan ke controller Products dan metode Index. id diabaikan.

Menggunakan template umumnya adalah pendekatan paling sederhana untuk perutean. Batasan dan default juga dapat ditentukan di luar templat rute.

Segmen kompleks

Segmen kompleks diproses dengan mencocokkan pemisah harfiah dari kanan ke kiri dengan cara yang tidak serakah . Misalnya, [Route("/a{b}c{d}")] adalah segmen yang kompleks. Segmen kompleks bekerja dengan cara tertentu yang harus dipahami agar berhasil digunakan. Contoh di bagian ini menunjukkan mengapa segmen kompleks hanya benar-benar berfungsi dengan baik ketika teks pemisah tidak muncul di dalam nilai parameter. Menggunakan regex lalu mengekstrak nilai secara manual diperlukan untuk kasus yang lebih kompleks.

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ini adalah ringkasan langkah-langkah yang dilakukan oleh perutean dengan templat /a{b}c{d} dan jalur /abcd URL. | digunakan untuk membantu memvisualisasikan cara kerja algoritma:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /abcd juga dicari dari kanan dan menemukan /ab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Tidak ada teks yang tersisa dan tidak ada templat rute yang tersisa, jadi ini cocok.

Berikut adalah contoh kasus negatif menggunakan templat /a{b}c{d} yang sama dan jalur URL /aabcd. | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, dimulai dari kanan ke kiri, adalah c. Demikian /aabcd juga dicari dari kanan dan menemukan /aab|c|d.
  • Semuanya di sebelah kanan (d) sekarang cocok dengan parameter rute {d}.
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai dari tempat kita berhenti, lalu a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang disesuaikan dengan parameter rute {b}.
  • Pada titik ini ada teks ayang tersisa , tetapi algoritma telah kehabisan templat rute untuk diurai, jadi ini bukan kecocokan.

Karena algoritma yang cocok tidak serakah:

  • Ini cocok dengan jumlah teks sekecil mungkin di setiap langkah.
  • Setiap kasus di mana nilai pemisah muncul di dalam nilai parameter menghasilkan tidak cocok.

Ekspresi reguler memberikan kendali yang jauh lebih besar terhadap perilaku pencocokannya.

Pencocokan rakus, juga dikenal sebagai pencocokan malas, cocok dengan string sebesar mungkin. Tidak serakah cocok dengan string sekecil mungkin.

Referensi batasan rute

Batasan rute dijalankan ketika kecocokan telah terjadi pada URL masuk dan jalur URL ditokenisasi ke dalam nilai rute. Batasan rute umumnya memeriksa nilai rute yang terkait melalui templat rute dan membuat keputusan yang benar atau salah tentang apakah nilai tersebut dapat diterima. Beberapa batasan rute menggunakan data di luar nilai rute untuk mempertimbangkan apakah permintaan dapat dirutekan. Misalnya, HttpMethodRouteConstraint dapat menerima atau menolak permintaan berdasarkan kata kerja HTTP-nya. Batasan digunakan dalam permintaan perutean dan pembuatan tautan.

Warning

Jangan gunakan batasan untuk validasi input. Jika batasan digunakan untuk validasi input, input yang 404 tidak valid menghasilkan respons Tidak Ditemukan. Input yang tidak valid harus menghasilkan 400 Permintaan Buruk dengan pesan kesalahan yang sesuai. Batasan rute digunakan untuk memisahkan rute serupa, bukan untuk memvalidasi input untuk rute tertentu.

Tabel berikut menunjukkan contoh batasan rute dan perilaku yang diharapkan:

batasan Example Contoh Kecocokan Notes
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Sesuai dengan atau false. Case-insensitive
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Cocok dengan nilai yang valid DateTime dalam budaya invarian. Lihat peringatan sebelumnya.
decimal {price:decimal} 49.99, -1,000.01 Cocok dengan nilai yang valid decimal dalam budaya invarian. Lihat peringatan sebelumnya.
double {weight:double} 1.234, -1,001.01e8 Cocok dengan nilai yang valid double dalam budaya invarian. Lihat peringatan sebelumnya.
float {weight:float} 1.234, -1,001.01e8 Cocok dengan nilai yang valid float dalam budaya invarian. Lihat peringatan sebelumnya.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Mencocokkan dengan nilai Guid yang valid
long {ticks:long} 123456789, -123456789 Mencocokkan dengan nilai long yang valid
minlength(value) {username:minlength(4)} Rick String harus minimal 4 karakter
maxlength(value) {filename:maxlength(8)} MyFile String tidak boleh lebih dari 8 karakter
length(length) {filename:length(12)} somefile.txt Panjang string harus persis 12 karakter
length(min,max) {filename:length(8,16)} somefile.txt Panjang string harus minimal 8 dan tidak lebih dari 16 karakter
min(value) {age:min(18)} 19 Nilai bilangan bulat harus minimal 18
max(value) {age:max(120)} 91 Nilai bilangan bulat tidak boleh lebih dari 120
range(min,max) {age:range(18,120)} 91 Nilai bilangan bulat harus setidaknya 18 tetapi tidak lebih dari 120
alpha {name:alpha} Rick String harus terdiri dari satu atau beberapa karakter alfabet, a-z dan tidak peka huruf besar/kecil.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 String harus cocok dengan ekspresi reguler. Lihat tips tentang menentukan ekspresi reguler.
required {name:required} Rick Digunakan untuk memberlakukan bahwa nilai non-parameter ada selama pembuatan URL

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Beberapa batasan yang dipisahkan dengan titik dua dapat diterapkan ke satu parameter. Misalnya, batasan berikut membatasi parameter ke nilai bilangan bulat 1 atau lebih besar:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke CLR jenis int atau DateTime. Batasan ini mengasumsikan bahwa URL tidak dapat dilokalkan. Batasan rute yang disediakan kerangka kerja tidak mengubah nilai yang disimpan dalam nilai rute. Semua nilai rute yang diurai dari URL disimpan sebagai string. Misalnya, float batasan mencoba mengonversi nilai rute menjadi float, tetapi nilai yang dikonversi hanya digunakan untuk memverifikasi bahwa nilai tersebut dapat dikonversi ke float.

Ekspresi reguler dalam batasan

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Ekspresi reguler dapat ditentukan sebagai pembatasan sebaris menggunakan batasan regex(...) route. Metode dalam MapControllerRoute keluarga juga menerima objek harfiah batasan. Jika formulir tersebut digunakan, nilai string ditafsirkan sebagai ekspresi reguler.

Kode berikut menggunakan batasan regex sebaris:

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
        context => 
        {
            return context.Response.WriteAsync("inline-constraint match");
        });
 });

Kode berikut menggunakan objek literal untuk menentukan batasan regex:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "people",
        pattern: "People/{ssn}",
        constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
        defaults: new { controller = "People", action = "List", });
});

Kerangka kerja ASP.NET Core ditambahkan RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ke konstruktor ekspresi reguler. Lihat RegexOptions untuk deskripsi anggota ini.

Ekspresi reguler menggunakan pemisah dan token yang mirip dengan yang digunakan oleh perutean dan bahasa C#. Token ekspresi reguler harus diloloskan. Untuk menggunakan ekspresi ^\d{3}-\d{2}-\d{4}$ reguler dalam batasan sebaris, gunakan salah satu hal berikut ini:

  • Ganti \ karakter yang disediakan dalam string sebagai \\ karakter dalam file sumber C# untuk menghindari \ karakter escape string.
  • String literal verbatim.

Untuk menghindari karakter pemisah parameter perutean seperti {, }, [, ], gandakan karakter tersebut dalam ekspresi, misalnya, {{, }}, [[, ]]. Tabel berikut ini memperlihatkan ekspresi reguler dan versi escape-nya:

Ekspresi reguler Ekspresi reguler yang di-escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Ekspresi reguler yang digunakan dalam perutean sering dimulai dengan karakter ^ dan cocok dengan posisi awal string. Ekspresi sering diakhiri dengan karakter $ dan mengakhiri string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. Tanpa karakter ^ dan $, ekspresi reguler mencocokkan substrak apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Expression String Match Comment
[a-z]{2} hello Yes Kecocokan substring
[a-z]{2} 123abc456 Yes Kecocokan substring
[a-z]{2} mz Yes Ekspresi yang cocok
[a-z]{2} MZ Yes Tidak peka huruf besar/kecil
^[a-z]{2}$ hello No Lihat ^ dan $ di atas
^[a-z]{2}$ 123abc456 No Lihat ^ dan $ di atas

Untuk informasi selengkapnya tentang sintaks ekspresi reguler, lihat .NET Framework Regular Expressions.

Untuk membatasi parameter ke sekumpulan nilai yang mungkin diketahui, gunakan ekspresi reguler. Misalnya, {action:regex(^(list|get|create)$)} hanya menyamai nilai jalur action dengan list, get, atau create. Jika diteruskan ke kamus batasan, string ^(list|get|create)$ setara. Batasan yang diteruskan dalam kamus batasan yang tidak cocok dengan salah satu batasan yang diketahui juga diperlakukan sebagai ekspresi reguler. Batasan yang diteruskan dalam templat yang tidak cocok dengan salah satu batasan yang diketahui tidak diperlakukan sebagai ekspresi reguler.

Pembatas rute khusus

Batasan rute kustom dapat dibuat dengan mengimplementasikan IRouteConstraint antarmuka. Antarmuka IRouteConstraint berisi Match, yang mengembalikan true jika batasan terpenuhi dan false sebaliknya.

Batasan rute kustom jarang diperlukan. Sebelum menerapkan batasan rute kustom, pertimbangkan alternatif, seperti pengikatan model.

Folder ASP.NET Core Constraints memberikan contoh yang baik untuk membuat batasan. Misalnya, GuidRouteConstraint.

Untuk menggunakan kustom IRouteConstraint, jenis batasan rute harus didaftarkan dengan aplikasi ConstraintMap di kontainer layanan. ConstraintMap adalah kamus yang memetakan kunci batasan rute ke IRouteConstraint implementasi yang memvalidasi batasan tersebut. Aplikasi ConstraintMap dapat diperbarui di Startup.ConfigureServices baik sebagai bagian dari services.AddRouting atau dengan mengonfigurasi RouteOptions secara langsung dengan services.Configure<RouteOptions>. Contohnya:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddRouting(options =>
    {
        options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
    });
}

Batasan sebelumnya diterapkan dalam kode berikut:

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    // GET /api/test/3
    [HttpGet("{id:customName}")]
    public IActionResult Get(string id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // GET /api/test/my/3
    [HttpGet("my/{id:customName}")]
    public IActionResult Get(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

MyDisplayRouteInfo disediakan oleh paket Rick.Docs.Samples.RouteInfo NuGet dan menampilkan informasi rute.

Implementasi MyCustomConstraint mencegah 0 diterapkan ke parameter rute:

class MyCustomConstraint : IRouteConstraint
{
    private Regex _regex;

    public MyCustomConstraint()
    {
        _regex = new Regex(@"^[1-9]*$",
                            RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
                            TimeSpan.FromMilliseconds(100));
    }
    public bool Match(HttpContext httpContext, IRouter route, string routeKey,
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out object value))
        {
            var parameterValueString = Convert.ToString(value,
                                                        CultureInfo.InvariantCulture);
            if (parameterValueString == null)
            {
                return false;
            }

            return _regex.IsMatch(parameterValueString);
        }

        return false;
    }
}

Warning

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya mampu memberikan input ke RegularExpressions yang menyebabkan Serangan Penolakan Layanan. Kerangka kerja API Core ASP.NET yang menggunakan RegularExpressions batas waktu operasi.

Kode sebelumnya:

  • Mencegah 0 di segmen {id} dari rute.
  • Ditunjukkan untuk memberikan contoh dasar penerapan batasan kustom. Ini tidak boleh digunakan dalam aplikasi produksi.

Pendekatan yang lebih baik untuk mencegah id yang mengandung 0 dari pemrosesan adalah dengan menggunakan kode berikut:

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return ControllerContext.MyDisplayRouteInfo(id);
}

Kode sebelumnya memiliki keuntungan berikut daripada MyCustomConstraint pendekatan:

  • Ini tidak memerlukan batasan kustom.
  • Ini mengembalikan kesalahan yang lebih deskriptif ketika parameter rute menyertakan 0.

Referensi pengubah parameter

Pengubah parameter

Misalnya, transformator parameter kustom slugify dalam pola rute blog\{article:slugify} dengan Url.Action(new { article = "MyTestArticle" }) menghasilkan blog\my-test-article.

Pertimbangkan implementasi berikut IOutboundParameterTransformer :

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString(), 
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

Untuk menggunakan transformator parameter dalam pola rute, konfigurasikan dengan ConstraintMap pada Startup.ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddRouting(options =>
    {
        options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
    });
}

Kerangka kerja ASP.NET Core menggunakan transformator parameter untuk mengubah URI tempat titik akhir diselesaikan. Misalnya, transformator parameter mengubah nilai rute yang digunakan untuk mencocokkan area, , controlleraction, dan page.

routes.MapControllerRoute(
    name: "default",
    template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

Dengan templat rute sebelumnya, tindakan SubscriptionManagementController.GetAll dicocokkan dengan URI /subscription-management/get-all. Transformator parameter tidak mengubah nilai rute yang digunakan untuk menghasilkan tautan. Misalnya, Url.Action("GetAll", "SubscriptionManagement") menghasilkan /subscription-management/get-all.

ASP.NET Core menyediakan konvensi API untuk menggunakan transformator parameter dengan rute yang dihasilkan:

Referensi pembuatan URL

Bagian ini berisi referensi untuk algoritma yang diterapkan oleh pembuatan URL. Dalam praktiknya, sebagian besar contoh kompleks pembuatan URL menggunakan pengontrol atau halaman Razor. Lihat perutean di pengontrol untuk keterangan lebih lanjut.

Proses pembuatan URL dimulai dengan panggilan ke LinkGenerator.GetPathByAddress atau metode serupa. Metode ini disediakan dengan alamat, sekumpulan nilai rute, dan informasi opsional tentang permintaan saat ini dari HttpContext.

Langkah pertama adalah menggunakan alamat untuk menyelesaikan sekumpulan titik akhir kandidat menggunakan IEndpointAddressScheme<TAddress> yang cocok dengan jenis alamat.

Setelah kumpulan kandidat ditemukan oleh skema alamat, titik akhir diurutkan dan diproses secara berulang sampai operasi pembuatan URL berhasil. Pembuatan URL tidak memeriksa ambiguitas, hasil pertama yang dikembalikan adalah hasil akhir.

Pemecahan masalah pembuatan URL dengan pengelogan

Langkah pertama dalam pemecahan masalah pembuatan URL adalah mengatur tingkat pengelogan Microsoft.AspNetCore.Routing ke TRACE. LinkGenerator mencatat banyak detail tentang pemrosesannya yang dapat berguna untuk memecahkan masalah.

Lihat Referensi pembuatan URL untuk detail tentang pembuatan URL.

Addresses

Alamat adalah konsep dalam pembuatan URL yang digunakan untuk mengikat panggilan ke generator tautan ke sekumpulan titik akhir kandidat.

Alamat adalah konsep yang dapat diperluas yang dilengkapi dengan dua implementasi secara default:

  • Menggunakan nama titik akhir (string) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan nama rute pada MVC.
    • Menggunakan jenis metadata IEndpointNameMetadata.
    • Menyelesaikan string yang disediakan terhadap metadata semua titik akhir terdaftar.
    • Memberikan pengecualian pada startup jika beberapa titik akhir menggunakan nama yang sama.
    • Direkomendasikan untuk penggunaan tujuan umum di luar pengontrol dan Razor Halaman.
  • Menggunakan nilai rute (RouteValuesAddress) sebagai alamat:
    • Menyediakan fungsionalitas serupa dengan pengontrol dan pembuatan URL lama untuk Pages.
    • Sangat kompleks untuk diperpanjang dan di-debug.
    • Menyediakan implementasi yang digunakan oleh IUrlHelper, Pembantu Tag, Pembantu HTML, Hasil Tindakan, dll.

Peran skema alamat adalah membuat hubungan antara alamat dan titik akhir yang cocok dengan kriteria semena-mena:

  • Skema nama titik akhir melakukan pencarian kamus dasar.
  • Skema nilai rute memiliki subset algoritma set terbaik yang kompleks.

Nilai sekitar dan nilai eksplisit

Dari permintaan saat ini, perutean mengakses nilai rute permintaan HttpContext.Request.RouteValuessaat ini . Nilai yang terkait dengan permintaan saat ini disebut sebagai nilai ambient. Untuk tujuan kejelasan, dokumentasi mengacu pada nilai rute yang diteruskan ke metode sebagai nilai eksplisit.

Contoh berikut menunjukkan nilai sekitar dan nilai eksplisit. Ini menyediakan nilai ambient dari permintaan saat ini dan nilai eksplisit: { id = 17, }

public class WidgetController : Controller
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public IActionResult Index()
    {
        var url = _linkGenerator.GetPathByAction(HttpContext,
                                                 null, null,
                                                 new { id = 17, });
        return Content(url);
    }

Kode sebelumnya:

Kode berikut tidak menyediakan nilai sekitar dan nilai eksplisit: : { controller = "Home", action = "Subscribe", id = 17, }

public IActionResult Index2()
{
    var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
                                             new { id = 17, });
    return Content(url);
}

Metode sebelumnya mengembalikan /Home/Subscribe/17

Kode berikut mengembalikan WidgetController/Widget/Subscribe/17:

var url = _linkGenerator.GetPathByAction("Subscribe", null,
                                         new { id = 17, });

Kode berikut menyediakan pengontrol dari nilai sekitar dalam permintaan saat ini dan nilai eksplisit: { action = "Edit", id = 17, }:

public class GadgetController : Controller
{
    public IActionResult Index()
    {
        var url = Url.Action("Edit", new { id = 17, });
        return Content(url);
    }

Dalam kode sebelumnya:

  • /Gadget/Edit/17 dikembalikan.
  • Url mendapatkan IUrlHelper.
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama action dan nilai route yang ditentukan.

Kode berikut menyediakan nilai sekitar dari permintaan saat ini dan nilai eksplisit: : { page = "./Edit, id = 17, }

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var url = Url.Page("./Edit", new { id = 17, });
        ViewData["URL"] = url;
    }
}

Kode sebelumnya mengatur url ke /Edit/17 ketika Halaman Edit Razor memuat arahan halaman berikut ini:

@page "{id:int}"

Jika halaman Edit tidak berisi "{id:int}" templat rute, url adalah /Edit?id=17.

Perilaku MVC IUrlHelper menambahkan lapisan kompleksitas selain aturan yang dijelaskan di sini:

  • IUrlHelper selalu menyediakan nilai rute dari permintaan saat ini sebagai nilai ambien.
  • IUrlHelper.Action selalu menyalin nilai rute action dan controller saat ini sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali digantikan.
  • IUrlHelper.Page selalu menggantikan nilai rute saat ini handler dengan null sebagai nilai eksplisit, kecuali dikesampingkan.

Pengguna sering terkejut dengan detail perilaku nilai sekitar, karena MVC tampaknya tidak mengikuti aturannya sendiri. Untuk alasan historis dan kompatibilitas, nilai rute tertentu seperti action, , controllerpage, dan handler memiliki perilaku kasus khusus mereka sendiri.

Fungsionalitas yang setara yang disediakan oleh LinkGenerator.GetPathByAction dan LinkGenerator.GetPathByPage menduplikasi anomali IUrlHelper ini untuk kompatibilitas.

Proses pembuatan URL

Setelah kumpulan titik akhir kandidat ditemukan, algoritma pembuatan URL:

  • Memproses titik akhir secara berulang.
  • Mengembalikan hasil pertama yang berhasil.

Langkah pertama dalam proses ini disebut pembatalan nilai rute. Invalidasi nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai lingkungan yang digunakan dan mana yang diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai ambien adalah mereka berusaha mengurangi pengetikan yang perlu dilakukan pengembang aplikasi, dalam beberapa kasus umum. Secara tradisional, skenario di mana nilai sekitar bermanfaat terkait dengan MVC:

  • Saat menautkan ke tindakan lain di pengontrol yang sama, nama pengontrol tidak perlu ditentukan.
  • Saat menautkan ke pengontrol lain di area yang sama, nama area tidak perlu ditentukan.
  • Saat menautkan ke metode tindakan yang sama, nilai rute tidak perlu ditentukan.
  • Saat menautkan ke bagian lain aplikasi, Anda tidak ingin membawa nilai rute yang tidak memiliki arti di bagian aplikasi tersebut.

Panggilan ke LinkGenerator atau IUrlHelper yang mengembalikan null biasanya disebabkan oleh tidak memahami ketidakvalidan nilai rute. Memecahkan masalah pembatalan nilai rute dengan secara eksplisit menentukan lebih banyak nilai rute untuk melihat apakah itu menyelesaikan masalah.

Pembatalan nilai rute berfungsi dengan asumsi bahwa skema URL aplikasi bersifat hierarkis, dengan hierarki yang terbentuk dari kiri-ke-kanan. Pertimbangkan templat {controller}/{action}/{id?} rute pengontrol dasar untuk mendapatkan rasa intuitif tentang cara kerjanya dalam praktik. Perubahan pada nilai membatalkan semua nilai rute yang muncul di sebelah kanan. Ini mencerminkan asumsi tentang hierarki. Jika aplikasi memiliki nilai lingkungan untuk id, dan operasi menentukan nilai yang berbeda untuk controller:

  • id tidak akan digunakan kembali karena {controller} berada di sebelah kiri {id?}.

Beberapa contoh yang menunjukkan prinsip ini:

  • Jika nilai eksplisit berisi nilai untuk id, nilai sekitar untuk id diabaikan. Nilai lingkungan untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai ambien untuk controller dapat digunakan. Jika nilai eksplisit untuk action berbeda dari nilai sekitar untuk action, id nilai tidak akan digunakan. Jika nilai eksplisit untuk action sama dengan nilai sekitar untuk action, id nilai dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk controller, nilai sekitar apa pun untuk controller diabaikan. Jika nilai eksplisit untuk controller berbeda dari nilai lingkungan untuk controller, nilai action dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai ambien untuk controller, action dan id nilai dapat digunakan.

Proses ini semakin rumit dengan adanya rute atribut dan rute konvensional khusus. Rute konvensional pengontrol seperti {controller}/{action}/{id?} menentukan hierarki menggunakan parameter rute. Untuk rute konvensional dedikasi dan rute atribut ke pengontrol dan Razor Halaman:

  • Ada hierarki nilai rute.
  • Mereka tidak muncul di templat.

Untuk kasus ini, pembuatan URL menentukan konsep nilai yang diperlukan. Titik akhir yang dibuat oleh pengontrol dan Razor Halaman memiliki nilai yang telah ditetapkan yang memungkinkan pembatalan nilai rute berfungsi dengan baik.

Algoritma invalidasi nilai rute secara rinci:

  • Nama nilai yang diperlukan dikombinasikan dengan parameter rute, lalu diproses dari kiri ke kanan.
  • Untuk setiap parameter, nilai sekitar dan nilai eksplisit dibandingkan:
    • Jika nilai sekitar dan nilai eksplisit sama, proses berlanjut.
    • Jika nilai sekitar ada dan nilai eksplisit tidak, nilai sekitar digunakan saat membuat URL.
    • Jika nilai sekitar tidak ada dan hanya nilai eksplisit yang ada, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika terdapat nilai ambien dan nilai eksplisit, dan kedua nilai tersebut berbeda, abaikan nilai ambien dan semua nilai ambien berikutnya.

Pada titik ini, operasi pembuatan URL siap untuk mengevaluasi batasan rute. Kumpulan nilai yang diterima dikombinasikan dengan nilai default parameter, yang disediakan untuk batasan. Jika semua batasan lolos, operasi berlanjut.

Selanjutnya, nilai yang diterima dapat digunakan untuk memperluas templat rute. Templat rute diproses:

  • Dari kiri-ke-kanan.
  • Setiap parameter digantikan dengan nilai yang diterima.
  • Dengan kasus khusus berikut:
    • Jika nilai yang diterima kehilangan nilai dan parameter memiliki nilai default, nilai default akan digunakan.
    • Jika nilai yang diterima kehilangan nilai dan parameter bersifat opsional, pemrosesan berlanjut.
    • Jika ada parameter rute di sebelah kanan parameter opsional yang hilang memiliki nilai, operasi gagal.
    • Parameter bernilai default yang bersebelahan dan parameter opsional diciutkan jika memungkinkan.

Nilai yang disediakan secara eksplisit yang tidak cocok dengan segmen rute ditambahkan ke string kueri. Tabel berikut ini memperlihatkan hasilnya saat menggunakan template rute {controller}/{action}/{id?}.

Nilai Lingkungan Nilai Eksplisit Result
pengontrol = "Home" action = "Tentang" /Home/About
pengontrol = "Home" controller = "Pesanan", action = "Tentang" /Order/About
controller = "Home", warna = "Merah" action = "Tentang" /Home/About
pengontrol = "Home" action = "Tentang", color = "Merah" /Home/About?color=Red

Masalah dengan pembatalan nilai rute

Pada ASP.NET Core 3.0, beberapa skema pembuatan URL yang digunakan dalam versi ASP.NET Core sebelumnya tidak berfungsi dengan baik dengan pembuatan URL. Tim ASP.NET Core berencana untuk menambahkan fitur untuk mengatasi kebutuhan ini dalam rilis mendatang. Untuk saat ini solusi terbaik adalah menggunakan perutean lama.

Kode berikut menunjukkan contoh skema pembuatan URL yang tidak didukung oleh perutean.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("default", 
                                     "{culture}/{controller=Home}/{action=Index}/{id?}");
    endpoints.MapControllerRoute("blog", "{culture}/{**slug}", 
                                      new { controller = "Blog", action = "ReadPost", });
});

Dalam kode sebelumnya, culture parameter rute digunakan untuk pelokalan. Keinginannya adalah agar culture parameter selalu diterima sebagai nilai sekitar. Namun, culture parameter tidak diterima sebagai nilai sekitar karena cara kerja nilai yang diperlukan:

  • "default" Dalam templat rute, culture parameter rute berada di sebelah kiri controller, sehingga perubahan menjadi controller tidak akan membatalkan culture.
  • "blog" Dalam templat rute, culture parameter rute dianggap berada di sebelah kanan controller, yang muncul dalam nilai yang diperlukan.

Mengonfigurasi metadata titik akhir

Tautan berikut ini menyediakan informasi tentang mengonfigurasi metadata titik akhir:

Pencocokan host dalam rute dengan RequireHost

RequireHost menerapkan batasan ke rute yang memerlukan host yang ditentukan. Parameter RequireHost atau [Host] dapat berupa:

  • Host: www.domain.com, cocok www.domain.com dengan port apa pun.
  • Host dengan wildcard: *.domain.com, cocok dengan www.domain.com, subdomain.domain.com, atau www.subdomain.domain.com pada port apa pun.
  • Port: *:5000, cocok dengan port 5000 dengan host apa pun.
  • Host dan port: www.domain.com:5000 atau *.domain.com:5000, cocok dengan host dan port.

Beberapa parameter dapat ditentukan menggunakan RequireHost atau [Host]. Batasan cocok dengan host yang valid untuk salah satu parameter. Misalnya, [Host("domain.com", "*.domain.com")] cocok dengan , domain.com, dan www.domain.comsubdomain.domain.com.

Kode berikut menggunakan RequireHost untuk memerlukan host yang ditentukan pada rute:

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!"))
            .RequireHost("contoso.com");
        endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!"))
            .RequireHost("adventure-works.com");
        endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");
    });
}

Kode berikut menggunakan [Host] atribut pada pengontrol untuk memerlukan salah satu host yang ditentukan:

[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Host("example.com:8080")]
    public IActionResult Privacy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Ketika atribut [Host] diterapkan ke pengendali dan metode aksi

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Panduan performa untuk perutean

Sebagian besar pengaturan rute diperbarui di ASP.NET Core 3.0 guna meningkatkan kinerja.

Ketika aplikasi mengalami masalah kinerja, perutean sering dicurigai sebagai penyebab masalah. Alasan perutean dicurigai adalah bahwa kerangka kerja seperti pengontrol dan Razor Pages melaporkan jumlah waktu yang dihabiskan di dalam kerangka kerja dalam pesan pengelogan mereka. Ketika ada perbedaan signifikan antara waktu yang dilaporkan oleh pengontrol dan total waktu permintaan:

  • Pengembang menghilangkan kode aplikasi mereka sebagai sumber masalah.
  • Hal yang umum untuk mengasumsikan perutean menjadi penyebabnya.

Routing diukur kinerjanya menggunakan ribuan titik akhir. Tidak mungkin aplikasi umum akan mengalami masalah performa hanya dengan menjadi terlalu besar. Akar penyebab paling umum dari performa perutean yang lambat biasanya adalah middleware kustom yang bertingkah buruk.

Sampel kode berikut ini menunjukkan teknik dasar untuk mempersempit sumber penundaan:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

Mengatur waktu rute

  • Interleave setiap middleware dengan salinan middleware pengaturan waktu yang ditampilkan dalam kode sebelumnya.
  • Tambahkan pengidentifikasi unik untuk menghubungkan data waktu dengan kode.

Ini adalah cara dasar untuk mempersempit penundaan ketika signifikan, misalnya, lebih dari 10ms. Mengurangkan Time 2 dari Time 1 menunjukkan waktu yang dihabiskan dalam middleware UseRouting.

Kode berikut menggunakan pendekatan yang lebih ringkas untuk kode waktu sebelumnya:

public sealed class MyStopwatch : IDisposable
{
    ILogger<Startup> _logger;
    string _message;
    Stopwatch _sw;

    public MyStopwatch(ILogger<Startup> logger, string message)
    {
        _logger = logger;
        _message = message;
        _sw = Stopwatch.StartNew();
    }

    private bool disposed = false;


    public void Dispose()
    {
        if (!disposed)
        {
            _logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",
                                    _message, _sw.ElapsedMilliseconds);

            disposed = true;
        }
    }
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    int count = 0;
    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }

    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

Fitur routing yang berpotensi mahal

Daftar berikut memberikan wawasan tentang fitur perutean yang relatif lebih mahal dibandingkan dengan templat rute dasar.

  • Ekspresi reguler: Dimungkinkan untuk menulis ekspresi reguler yang kompleks, atau memiliki waktu berjalan lama dengan sejumlah kecil input.
  • Segmen kompleks ({x}-{y}-{z}):
    • Secara signifikan lebih mahal daripada mengurai segmen jalur URL reguler.
    • Mengakibatkan lebih banyak substring dialokasikan.
    • Logika segmen kompleks tidak diperbarui dalam pembaruan performa perutean ASP.NET Core 3.0.
  • Akses data sinkron: Aplikasi-aplikasi kompleks memiliki akses database sebagai bagian dari pengarahan mereka. Perutean ASP.NET Core 2.2 atau versi lebih lama mungkin tidak menyediakan titik ekstensibilitas yang tepat untuk mendukung perutean akses database. Misalnya, IRouteConstraint, dan IActionConstraint sinkron. Titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext asinkron.

Panduan untuk penulis pustaka

Bagian ini berisi panduan untuk penulis pustaka yang membangun di atas perutean. Detail ini dimaksudkan untuk memastikan bahwa pengembang aplikasi memiliki pengalaman yang baik menggunakan pustaka dan kerangka kerja yang memperluas perutean.

Menentukan titik akhir

Untuk membuat kerangka kerja yang menggunakan perutean untuk pencocokan URL, mulailah dengan menentukan pengalaman pengguna yang dibangun di atas UseEndpoints.

DO dibangun di atas IEndpointRouteBuilder. Ini memungkinkan pengguna untuk menyusun kerangka kerja Anda dengan fitur ASP.NET Core lainnya tanpa kebingungan. Setiap templat ASP.NET Core menyertakan routing. Asumsikan perutean ada dan sudah dikenal oleh pengguna.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...);

    endpoints.MapHealthChecks("/healthz");
});

DO mengembalikan jenis beton tertutup dari panggilan ke MapMyFramework(...) yang mengimplementasikan IEndpointConventionBuilder. Sebagian besar metode kerangka kerja Map... mengikuti pola ini. Antarmuka IEndpointConventionBuilder :

  • Memungkinkan penggabungan metadata.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan tipe Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka Anda sendiri ke builder. Tidak apa-apa untuk membungkus pembangun yang dideklarasikan kerangka kerja dan meneruskan panggilan ke dalamnya.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization()
                                 .WithMyFrameworkFeature(awesome: true);

    endpoints.MapHealthChecks("/healthz");
});

PERTIMBANGKAN untuk menulis sendiri EndpointDataSource. EndpointDataSource adalah primitif tingkat rendah untuk mendeklarasikan dan memperbarui kumpulan titik akhir. EndpointDataSource adalah API canggih yang digunakan oleh pengontrol dan Razor Halaman.

Pengujian perutean memiliki contoh dasar sumber data yang tidak diperbarui.

JANGAN mencoba mendaftarkan EndpointDataSource secara default. Mengharuskan pengguna untuk mendaftarkan kerangka kerja Anda di UseEndpoints. Filosofi perutean adalah bahwa tidak ada yang disertakan secara default, dan UseEndpoints merupakan tempat untuk mendaftarkan endpoint.

Membuat middleware yang terintegrasi dengan perutean

PERTIMBANGKAN menentukan tipe metadata sebagai antarmuka.

DO memungkinkan untuk menggunakan jenis metadata sebagai atribut pada kelas dan metode.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Kerangka kerja seperti pengontrol dan Razor Pages mendukung penerapan atribut metadata ke jenis dan metode. Jika Anda mendeklarasikan jenis metadata:

  • Buat mereka dapat diakses sebagai atribut.
  • Sebagian besar pengguna terbiasa menerapkan atribut.

Mendeklarasikan jenis metadata sebagai antarmuka menambahkan lapisan fleksibilitas lain:

  • Antarmuka dapat dikomposisikan.
  • Pengembang dapat mendeklarasikan jenis mereka sendiri yang menggabungkan beberapa kebijakan.

DO memungkinkan untuk mengambil alih metadata, seperti yang ditunjukkan dalam contoh berikut:

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Cara terbaik untuk mengikuti panduan ini adalah dengan menghindari penentuan metadata penanda:

  • Jangan hanya mencari ada tidaknya jenis metadata.
  • Tentukan properti pada metadata dan periksa properti .

Koleksi metadata diurutkan dan mendukung penimpaan berdasarkan prioritas. Dalam kasus pengontrol, metadata pada metode tindakan paling spesifik.

DO membuat middleware berguna dengan atau tanpa perutean.

app.UseRouting();

app.UseAuthorization(new AuthorizationPolicy() { ... });

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization();
});

Sebagai contoh pedoman ini, pertimbangkan UseAuthorization middleware. Middleware otorisasi memungkinkan Anda untuk menginputkan sebuah kebijakan fallback. Kebijakan fallback, jika ditentukan, berlaku untuk keduanya:

  • Titik akhir tanpa kebijakan yang ditentukan.
  • Permintaan yang tidak cocok dengan titik akhir.

Ini membuat perangkat lunak perantara untuk otorisasi berguna di luar konteks perutean. Middleware otorisasi dapat digunakan untuk pemrograman middleware tradisional.

Pemecahan Masalah Diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan Development, atur tingkat log di appsettings.Development.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}