Perutean di ASP.NET Core 3.1

Oleh Ryan Nowak, Kirk Larkin, dan Rick Anderson

Catatan

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

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 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. Menggunakan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang memetakan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Pengontrol
  • Razor Pages
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambda yang terdaftar dengan 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.
    • Hello World! ditulis ke 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 dan UseRoutingUseEndpoints menjalankan 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 untuk app.Use mendaftarkan middleware kustom yang berjalan di awal alur.
  • Panggilan untuk UseRouting mengonfigurasi middleware pencocokan rute untuk dijalankan setelah middleware kustom.
  • Titik akhir yang terdaftar dengan MapGet eksekusi di akhir alur.

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

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

Titik akhir

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, , MapPostdan metode serupa menghubungkan delegasi permintaan ke sistem perutean. 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 kesehatan dan otorisasi:

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

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

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan perutean.
  • 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 sadar 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. Propertinya dapat diperiksa. Objek titik akhir tidak dapat diubah dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya dipilih oleh sistem perutean.

Dalam kode sebelumnya, aplikasi. Gunakan mengonfigurasi middleware sebaris.

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 titik akhir yang disediakan / .

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

Menjalankan kode ini dengan URL / tampilan:

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

Menjalankan kode ini dengan url lain yang ditampilkan:

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 adalah terminal ketika kecocokan ditemukan. Middleware terminal didefinisikan nanti dalam artikel ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan SetEndpoint metode untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan masih mendapatkan manfaat menggunakan titik akhir. 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 menunjukkan 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 sebelumnya UseRouting untuk memodifikasi data yang dioperasikan perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil perutean 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 akses ke data sensitif ke konsol. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan RequiresAuditAttribute metadata. Sampel ini menunjukkan pola keikutsertaan di mana hanya titik akhir yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem metadata titik akhir 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 middleware log audit yang lebih lengkap akan:

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

Metadata RequiresAuditAttribute kebijakan audit didefinisikan sebagai untuk penggunaan yang Attribute lebih mudah 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 adalah Path == "/" untuk middleware dan Path == "/Routing" untuk perutean.
  • Ketika kecocokan berhasil, ia menjalankan beberapa fungsionalitas dan mengembalikan, daripada 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 tempat arbitrer di alur:
  • Middleware terminal memungkinkan kode arbitrer untuk menentukan kapan middleware cocok:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Perutean menyediakan solusi langsung untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Antarmuka titik akhir dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan interfacing manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Delegasi untuk memproses permintaan.
  • Kumpulan metadata arbitrer. Metadata digunakan untuk menerapkan masalah lintas pemotongan berdasarkan kebijakan dan konfigurasi yang melekat 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 perutean sebelum menulis middleware terminal.

Middleware terminal yang ada yang terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir sadar 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 penyusun 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.

Saat middleware perutean dijalankan, middleware menetapkan Endpoint nilai rute dan 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 menginterogasi pengumpulan metadata titik akhir untuk kebijakan otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi titik akhir yang dipilih dipanggil.

Sistem perutean dalam perutean titik akhir bertanggung jawab atas semua keputusan pengiriman. 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.

Peringatan

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. Set kecocokan dapat dipersempit lebih jauh pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk titik akhir yang cocok. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL terhadap kumpulan titik akhir dan templat 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. EndpointSelector Menggunakan 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 . Ini 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 /helloURL .
  • /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 dihitung sebelumnya dari rute ambigu. Pertimbangkan contoh seperti templat /{message:alpha} rute 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 yang sama dengan keduanya.
  • Jika sistem perutean melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir kasus penggunaan yang valid ini.

Peringatan

Urutan 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 pengontrol jangka panjang tanpa sistem perutean memberikan jaminan yang sama dengan implementasi perutean yang lebih 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 mencocokkan harapan akal sehat perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa adalah kecocokan yang /Products/List lebih baik daripada /Products/{id} untuk jalur /Products/ListURL . 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 catch-all 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.

Perutean titik akhir LinkGenerator mencakup API. 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 Pembantu Tag, Pembantu HTML, dan Hasil Tindakan, gunakan LinkGenerator API secara internal untuk menyediakan kemampuan pembuatan tautan.

Generator tautan didukung oleh konsep skema alamat dan 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 ini GetPath* paling mirip Url.Action dengan dan Url.Page, karena mereka menghasilkan URI yang berisi jalur absolut. Metode selalu GetUri* menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima menghasilkan HttpContext URI dalam konteks permintaan yang dijalankan. Nilai rute sekitar , jalur dasar URL, skema, dan host dari permintaan eksekusi digunakan kecuali ditimpa.

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

  1. Alamat terikat ke daftar titik akhir yang cocok dengan alamat.
  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 Deskripsi
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Peringatan

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan GetUri* metode ekstensi dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi Host header permintaan masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak tepercaya dapat dikirim kembali ke klien di URI dalam tampilan atau halaman. Sebaiknya semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi Host header 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.");
    }
}

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. 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 peka huruf besar/kecil dan berdasarkan representasi yang didekodekan dari jalur URL. Untuk mencocokkan pemisah { parameter rute literal atau }, keluar dari pemisah dengan mengulangi karakter. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

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

Peringatan

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

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

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

Perbaikan keikutsertaan untuk bug ini terkandung dalam .NET Core 3.1.301 SDK dan yang lebih baru. Kode berikut menetapkan sakelar internal yang memperbaiki 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 lolos dari karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter pemisah / jalur. Misalnya, rute foo/{*path} dengan nilai { path = "my/path" } rute 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 cocok dengan nilai rute yang terikat dari URL. : Menambahkan dan membatasi nama 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 nama lain : dan batasan.

Nama batasan dan argumen diteruskan ke IInlineConstraintResolver layanan untuk membuat instans yang IRouteConstraint 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 mungkin juga memiliki transformator parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti batasan, transformator parameter dapat ditambahkan sebaris ke parameter rute dengan menambahkan : nama transformator dan setelah nama parameter rute. Misalnya, templat blog/{article:slugify} rute menentukan slugify transformator. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Transformer parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Templat Rute Contoh URI yang Cocok Permintaan URI...
hello /hello Hanya cocok dengan jalur /hellotunggal .
{Page=Home} / Cocok dan diatur Page ke Home.
{Page=Home} /Contact Cocok dan diatur Page ke Contact.
{controller}/{action}/{id?} /Products/List Peta ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Peta ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Peta ke Home pengontrol dan Index metode. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Peta ke Products pengontrol dan Index metode. id diabaikan.

Menggunakan templat 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.

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

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

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 /aabcdURL . | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 kontrol yang jauh lebih besar atas perilaku pencocokan mereka.

Pencocokan serakah, juga dikenal sebagai upaya pencocokan maksimal untuk 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.

Perutean dengan karakter khusus

Perutean 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 Dikodekan
/ %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.

Peringatan

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:

Kendala Contoh Contoh Kecocokan Catatan
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Cocok atau false. Tidak peka huruf besar/kecil
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 Cocok dengan nilai yang valid Guid
long {ticks:long} 123456789, -123456789 Cocok dengan nilai yang valid long
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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Beberapa batasan yang dibatasi 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) { }

Peringatan

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke jenis int CLR 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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Ekspresi reguler dapat ditentukan sebagai batasan sebaris menggunakan batasan regex(...) rute. 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.
  • Literal string verbatim.

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

Ekspresi reguler Ekspresi reguler yang lolos
^\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 diakhir $ dengan karakter dan cocok dengan akhir string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. ^ Tanpa karakter dan $ , ekspresi reguler cocok dengan substring apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Ekspresi String Cocokkan Komentar
[a-z]{2} halo Ya Kecocokan substring
[a-z]{2} 123abc456 Ya Kecocokan substring
[a-z]{2} Mz Ya Ekspresi yang cocok
[a-z]{2} MZ Ya Tidak peka huruf besar/kecil
^[a-z]{2}$ halo 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 cocok dengan action nilai rute 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.

Batasan rute kustom

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 baik sebagai Program.cs bagian AddRouting dari panggilan 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);
    }
}

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Kode sebelumnya:

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

Kode berikut adalah pendekatan yang lebih baik untuk mencegah yang id berisi 0 diproses:

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

Transformer parameter

Transformator parameter:

Misalnya, transformator parameter kustom slugify dalam pola blog\{article:slugify} rute 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 menggunakannya ConstraintMap di 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") output /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, contoh paling kompleks pembuatan URL menggunakan pengontrol atau Razor Halaman. Lihat perutean di pengontrol untuk informasi tambahan.

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.

Alamat

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 MVC.
    • IEndpointNameMetadata Menggunakan jenis metadata.
    • 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 Razor pembuatan URL lama 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 sekitar. 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 sekitar 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 dalam 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.
  • UrlIUrlHelpermendapatkan .
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama dan route nilai yang ditentukanaction.

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 diatur url ke /Edit/17 saat Halaman Edit Razor berisi arahan halaman berikut:

@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 sekitar.
  • IUrlHelper.Action selalu menyalin nilai saat ini action dan controller rute sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali ditimpa.
  • IUrlHelper.Page selalu mengambil alih nilai rute saat ini handler dengan null sebagai nilai eksplisit kecuali ditimpa.

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, , pagecontroller, 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. Pembatalan nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai sekitar yang harus digunakan dan yang harus diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai sekitar adalah mereka mencoba menyimpan pengetikan 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 pengembalian tersebut null biasanya disebabkan oleh tidak memahami pembatalan 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 sekitar 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 sekitar untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai sekitar 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 sekitar untuk controller, action nilai dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai sekitar untuk controller, action nilai dan id 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 khusus 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 diperlukan yang memungkinkan pembatalan nilai rute berfungsi.

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 nilai eksplisit adalah, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika nilai sekitar dan nilai eksplisit ada, dan kedua nilai berbeda, tolak nilai sekitar dan semua nilai sekitar 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 batasan semua lolos, operasi berlanjut.

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

  • Dari kiri-ke-kanan.
  • Setiap parameter memiliki nilai yang diterima diganti.
  • 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 templat {controller}/{action}/{id?}rute .

Nilai Sekitar Nilai Eksplisit Hasil
pengontrol = "Home" action = "About" /Home/About
pengontrol = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
pengontrol = "Home" action = "About", color = "Red" /Home/About?color=Red

Urutan parameter rute opsional

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

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 pembuatan URL yang tidak didukung oleh perutean:

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 tindakan menggunakan templat api/Products/{id} rute dan memiliki Name :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, AddRelatedProduct tindakan mengekstrak id nilai rute dari jalur URL. Misalnya, dengan jalur /api/Products/1URL , relatedProductId nilai diatur 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 kartubebas: *.domain.com, cocok www.domain.com, , subdomain.domain.comatau 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 , www.domain.com, dan subdomain.domain.comdomain.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();
}

[Host] Ketika atribut diterapkan ke pengontrol dan metode tindakan:

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Peringatan

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

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

Grup rute

Metode MapGroup ekstensi membantu mengatur grup titik akhir dengan awalan umum. Ini mengurangi kode berulang dan memungkinkan 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 hanya akan cocok dengan permintaan yang diawali dan /public/todos dapat diakses tanpa autentikasi apa pun. Grup titik akhir kedua hanya akan cocok dengan permintaan yang diawali dan /private/todos memerlukan autentikasi.

Pabrik QueryPrivateTodos filter titik akhir adalah fungsi lokal yang memodifikasi parameter handler TodoDb rute untuk memungkinkan mengakses dan menyimpan data todo privat.

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

Awalan juga dapat kosong. 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 berulah dengan cara yang sama seperti menambahkannya satu per satu ke setiap titik akhir sebelum menambahkan filter atau metadata tambahan yang mungkin telah ditambahkan ke 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 di atas, filter luar akan mencatat permintaan masuk sebelum filter dalam meskipun ditambahkan kedua. Karena filter diterapkan ke grup yang berbeda, urutan yang ditambahkan relatif satu sama lain tidak masalah. Filter pesanan ditambahkan tidak masalah jika diterapkan ke grup yang sama atau titik akhir tertentu.

Permintaan untuk /outer/inner/ mencatat hal berikut:

/outer group filter
/inner group filter
MapGet filter

Panduan performa untuk perutean

Ketika aplikasi mengalami masalah performa, perutean sering dicurigai sebagai masalahnya. 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 umum untuk mengasumsikan perutean adalah penyebabnya.

Perutean diuji performanya 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.");

Perutean waktu:

  • Interleave setiap middleware dengan salinan middleware 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. Time 2 Mengurangi dari Time 1 laporan waktu yang dihabiskan di dalam UseRouting middleware.

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 perutean yang berpotensi mahal

Daftar berikut ini memberikan beberapa wawasan tentang fitur perutean yang relatif 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: Banyak aplikasi kompleks memiliki akses database sebagai bagian dari peruteannya. Gunakan titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext, yang asinkron.

Panduan untuk tabel rute besar

Secara default ASP.NET Core menggunakan algoritma perutean yang memperdagangkan 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 daripada yang dapat menyebabkan pengaktifan aplikasi yang lambat. Periksa kondisi di bawah ini untuk secara akurat menentukan aplikasi mengalami situasi ini.
    • Aplikasi ini mengonsumsi banyak memori selama startup dan cadangan memori menunjukkan sejumlah Microsoft.AspNetCore.Routing.Matching.DfaNode besar instans.

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 secara internal untuk pencocokan 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 ini ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) dapat secara opsional mengambil kode status.

MapShortCircuit Gunakan metode untuk menyiapkan sirkuit pendek untuk beberapa rute sekaligus, dengan meneruskannya ke sana array param awalan URL. Misalnya, browser dan bot sering memeriksa server untuk jalur 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");

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

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

Untuk melihat efek middleware sirkuit pendek, atur kategori pengelogan "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 diproduksi dengan menjalankan / titik akhir. 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 /short-circuit titik akhir. 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 perutean. Asumsikan perutean ada dan akrab bagi 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 disusupi.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan jenis Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka kerja Anda sendiri ke penyusun. 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 berjalan dan metadata akhir pada titik akhir yang dikelompokkan. Misalnya, ini memungkinkan implementasi kustom EndpointDataSource untuk menjalankan filter titik akhir yang ditambahkan ke 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 itu UseEndpoints adalah tempat untuk mendaftarkan titik akhir.

Membuat middleware terintegrasi perutean

PERTIMBANGKAN untuk menentukan jenis 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 keberadaan 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 dan tanpa perutean:

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

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

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

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

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

Men-debug diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan pengembangan, 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. Menggunakan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang memetakan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Pengontrol
  • Razor Pages
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambda yang terdaftar dengan 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.
    • Hello World! ditulis ke 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 dan UseRoutingUseEndpoints menjalankan 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 untuk app.Use mendaftarkan middleware kustom yang berjalan di awal alur.
  • Panggilan untuk UseRouting mengonfigurasi middleware pencocokan rute untuk dijalankan setelah middleware kustom.
  • Titik akhir yang terdaftar dengan MapGet eksekusi di akhir alur.

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

Titik akhir

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, , MapPostdan metode serupa menghubungkan delegasi permintaan ke sistem perutean. 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 kesehatan dan otorisasi:

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

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

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan perutean.
  • 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 sadar 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. Propertinya dapat diperiksa. Objek titik akhir tidak dapat diubah dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya dipilih oleh sistem perutean.

Dalam kode sebelumnya, aplikasi. Gunakan mengonfigurasi middleware sebaris.

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 titik akhir yang disediakan / .

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

Menjalankan kode ini dengan URL / tampilan:

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

Menjalankan kode ini dengan url lain yang ditampilkan:

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 adalah terminal ketika kecocokan ditemukan. Middleware terminal didefinisikan nanti dalam artikel ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan SetEndpoint metode untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan masih mendapatkan manfaat menggunakan titik akhir. 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 menunjukkan 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 sebelumnya UseRouting untuk memodifikasi data yang dioperasikan perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil perutean 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 akses ke data sensitif ke konsol. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan RequiresAuditAttribute metadata. Sampel ini menunjukkan pola keikutsertaan di mana hanya titik akhir yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem metadata titik akhir 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 middleware log audit yang lebih lengkap akan:

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

Metadata RequiresAuditAttribute kebijakan audit didefinisikan sebagai untuk penggunaan yang Attribute lebih mudah 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 adalah Path == "/" untuk middleware dan Path == "/Routing" untuk perutean.
  • Ketika kecocokan berhasil, ia menjalankan beberapa fungsionalitas dan mengembalikan, daripada 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 tempat arbitrer di alur:
  • Middleware terminal memungkinkan kode arbitrer untuk menentukan kapan middleware cocok:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Perutean menyediakan solusi langsung untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Antarmuka titik akhir dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan interfacing manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Delegasi untuk memproses permintaan.
  • Kumpulan metadata arbitrer. Metadata digunakan untuk menerapkan masalah lintas pemotongan berdasarkan kebijakan dan konfigurasi yang melekat 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 perutean sebelum menulis middleware terminal.

Middleware terminal yang ada yang terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir sadar 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 penyusun 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.

Saat middleware perutean dijalankan, middleware menetapkan Endpoint nilai rute dan 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 perutean dapat memeriksa titik akhir dan mengambil tindakan. Misalnya, middleware otorisasi dapat menginterogasi pengumpulan metadata titik akhir untuk kebijakan otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi titik akhir yang dipilih dipanggil.

Sistem perutean dalam perutean titik akhir bertanggung jawab atas semua keputusan pengiriman. 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.

Peringatan

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. Set kecocokan dapat dipersempit lebih jauh pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk titik akhir yang cocok. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL terhadap kumpulan titik akhir dan templat 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. EndpointSelector Menggunakan 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 . Ini 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 /helloURL .
  • /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 dihitung sebelumnya dari rute ambigu. Pertimbangkan contoh seperti templat /{message:alpha} rute 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 yang sama dengan keduanya.
  • Jika sistem perutean melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir kasus penggunaan yang valid ini.

Peringatan

Urutan 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 pengontrol jangka panjang tanpa sistem perutean memberikan jaminan yang sama dengan implementasi perutean yang lebih 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 mencocokkan harapan akal sehat perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa adalah kecocokan yang /Products/List lebih baik daripada /Products/{id} untuk jalur /Products/ListURL . 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 catch-all 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.

Perutean titik akhir LinkGenerator mencakup API. 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 Pembantu Tag, Pembantu HTML, dan Hasil Tindakan, gunakan LinkGenerator API secara internal untuk menyediakan kemampuan pembuatan tautan.

Generator tautan didukung oleh konsep skema alamat dan 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 ini GetPath* paling mirip Url.Action dengan dan Url.Page, karena mereka menghasilkan URI yang berisi jalur absolut. Metode selalu GetUri* menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima menghasilkan HttpContext URI dalam konteks permintaan yang dijalankan. Nilai rute sekitar , jalur dasar URL, skema, dan host dari permintaan eksekusi digunakan kecuali ditimpa.

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

  1. Alamat terikat ke daftar titik akhir yang cocok dengan alamat.
  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 Deskripsi
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Peringatan

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan GetUri* metode ekstensi dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi Host header permintaan masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak tepercaya dapat dikirim kembali ke klien di URI dalam tampilan atau halaman. Sebaiknya semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi Host header 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.");
    }
}

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. 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 peka huruf besar/kecil dan berdasarkan representasi yang didekodekan dari jalur URL. Untuk mencocokkan pemisah { parameter rute literal atau }, keluar dari pemisah dengan mengulangi karakter. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

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

Peringatan

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

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

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

Perbaikan keikutsertaan untuk bug ini terkandung dalam .NET Core 3.1.301 SDK dan yang lebih baru. Kode berikut menetapkan sakelar internal yang memperbaiki 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 lolos dari karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter pemisah / jalur. Misalnya, rute foo/{*path} dengan nilai { path = "my/path" } rute 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 cocok dengan nilai rute yang terikat dari URL. : Menambahkan dan membatasi nama 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 nama lain : dan batasan.

Nama batasan dan argumen diteruskan ke IInlineConstraintResolver layanan untuk membuat instans yang IRouteConstraint 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 mungkin juga memiliki transformator parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti batasan, transformator parameter dapat ditambahkan sebaris ke parameter rute dengan menambahkan : nama transformator dan setelah nama parameter rute. Misalnya, templat blog/{article:slugify} rute menentukan slugify transformator. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Transformer parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Templat Rute Contoh URI yang Cocok Permintaan URI...
hello /hello Hanya cocok dengan jalur /hellotunggal .
{Page=Home} / Cocok dan diatur Page ke Home.
{Page=Home} /Contact Cocok dan diatur Page ke Contact.
{controller}/{action}/{id?} /Products/List Peta ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Peta ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Peta ke Home pengontrol dan Index metode. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Peta ke Products pengontrol dan Index metode. id diabaikan.

Menggunakan templat 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.

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

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

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 /aabcdURL . | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 kontrol yang jauh lebih besar atas perilaku pencocokan mereka.

Pencocokan serakah, juga dikenal sebagai pencocokan malas, cocok dengan string terbesar yang mungkin. Tidak serakah cocok dengan string sekecil mungkin.

Perutean dengan karakter khusus

Perutean 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 Dikodekan
/ %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.

Peringatan

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:

Kendala Contoh Contoh Kecocokan Catatan
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Cocok atau false. Tidak peka huruf besar/kecil
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 Cocok dengan nilai yang valid Guid
long {ticks:long} 123456789, -123456789 Cocok dengan nilai yang valid long
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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Beberapa batasan yang dibatasi 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) { }

Peringatan

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke jenis int CLR 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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Ekspresi reguler dapat ditentukan sebagai batasan sebaris menggunakan batasan regex(...) rute. 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.
  • Literal string verbatim.

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

Ekspresi reguler Ekspresi reguler yang lolos
^\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 diakhir $ dengan karakter dan cocok dengan akhir string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. ^ Tanpa karakter dan $ , ekspresi reguler cocok dengan substring apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Ekspresi String Cocokkan Komentar
[a-z]{2} halo Ya Kecocokan substring
[a-z]{2} 123abc456 Ya Kecocokan substring
[a-z]{2} Mz Ya Ekspresi yang cocok
[a-z]{2} MZ Ya Tidak peka huruf besar/kecil
^[a-z]{2}$ halo 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 cocok dengan action nilai rute 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.

Batasan rute kustom

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 baik sebagai Program.cs bagian AddRouting dari panggilan 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);
    }
}

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Kode sebelumnya:

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

Kode berikut adalah pendekatan yang lebih baik untuk mencegah yang id berisi 0 diproses:

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

Transformer parameter

Transformator parameter:

Misalnya, transformator parameter kustom slugify dalam pola blog\{article:slugify} rute 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 menggunakannya ConstraintMap di 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") output /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, contoh paling kompleks pembuatan URL menggunakan pengontrol atau Razor Halaman. Lihat perutean di pengontrol untuk informasi tambahan.

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.

Alamat

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 MVC.
    • IEndpointNameMetadata Menggunakan jenis metadata.
    • 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 Razor pembuatan URL lama 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 sekitar. 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 sekitar 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 dalam 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.
  • UrlIUrlHelpermendapatkan .
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama dan route nilai yang ditentukanaction.

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 diatur url ke /Edit/17 saat Halaman Edit Razor berisi arahan halaman berikut:

@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 sekitar.
  • IUrlHelper.Action selalu menyalin nilai saat ini action dan controller rute sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali ditimpa.
  • IUrlHelper.Page selalu mengambil alih nilai rute saat ini handler dengan null sebagai nilai eksplisit kecuali ditimpa.

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, , pagecontroller, 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. Pembatalan nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai sekitar yang harus digunakan dan yang harus diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai sekitar adalah mereka mencoba menyimpan pengetikan 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 pengembalian tersebut null biasanya disebabkan oleh tidak memahami pembatalan 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 sekitar 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 sekitar untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai sekitar 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 sekitar untuk controller, action nilai dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai sekitar untuk controller, action nilai dan id 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 khusus 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 diperlukan yang memungkinkan pembatalan nilai rute berfungsi.

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 nilai eksplisit adalah, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika nilai sekitar dan nilai eksplisit ada, dan kedua nilai berbeda, tolak nilai sekitar dan semua nilai sekitar 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 batasan semua lolos, operasi berlanjut.

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

  • Dari kiri-ke-kanan.
  • Setiap parameter memiliki nilai yang diterima diganti.
  • 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 templat {controller}/{action}/{id?}rute .

Nilai Sekitar Nilai Eksplisit Hasil
pengontrol = "Home" action = "About" /Home/About
pengontrol = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
pengontrol = "Home" action = "About", color = "Red" /Home/About?color=Red

Urutan parameter rute opsional

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

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 pembuatan URL yang tidak didukung oleh perutean:

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 tindakan menggunakan templat api/Products/{id} rute dan memiliki Name :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, AddRelatedProduct tindakan mengekstrak id nilai rute dari jalur URL. Misalnya, dengan jalur /api/Products/1URL , relatedProductId nilai diatur 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 kartubebas: *.domain.com, cocok www.domain.com, , subdomain.domain.comatau 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 , www.domain.com, dan subdomain.domain.comdomain.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();
}

[Host] Ketika atribut diterapkan ke pengontrol dan metode tindakan:

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Grup rute

Metode MapGroup ekstensi membantu mengatur grup titik akhir dengan awalan umum. Ini mengurangi kode berulang dan memungkinkan 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 hanya akan cocok dengan permintaan yang diawali dan /public/todos dapat diakses tanpa autentikasi apa pun. Grup titik akhir kedua hanya akan cocok dengan permintaan yang diawali dan /private/todos memerlukan autentikasi.

Pabrik QueryPrivateTodos filter titik akhir adalah fungsi lokal yang memodifikasi parameter handler TodoDb rute untuk memungkinkan mengakses dan menyimpan data todo privat.

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

Awalan juga dapat kosong. 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 berulah dengan cara yang sama seperti menambahkannya satu per satu ke setiap titik akhir sebelum menambahkan filter atau metadata tambahan yang mungkin telah ditambahkan ke 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 di atas, filter luar akan mencatat permintaan masuk sebelum filter dalam meskipun ditambahkan kedua. Karena filter diterapkan ke grup yang berbeda, urutan yang ditambahkan relatif satu sama lain tidak masalah. Filter pesanan ditambahkan tidak masalah jika diterapkan ke grup yang sama atau titik akhir tertentu.

Permintaan untuk /outer/inner/ mencatat hal berikut:

/outer group filter
/inner group filter
MapGet filter

Panduan performa untuk perutean

Ketika aplikasi mengalami masalah performa, perutean sering dicurigai sebagai masalahnya. 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 umum untuk mengasumsikan perutean adalah penyebabnya.

Perutean diuji performanya 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.");

Perutean waktu:

  • Interleave setiap middleware dengan salinan middleware 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. Time 2 Mengurangi dari Time 1 laporan waktu yang dihabiskan di dalam UseRouting middleware.

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 perutean yang berpotensi mahal

Daftar berikut ini memberikan beberapa wawasan tentang fitur perutean yang relatif 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: Banyak aplikasi kompleks memiliki akses database sebagai bagian dari peruteannya. Gunakan titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext, yang asinkron.

Panduan untuk tabel rute besar

Secara default ASP.NET Core menggunakan algoritma perutean yang memperdagangkan 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 daripada yang dapat menyebabkan pengaktifan aplikasi yang lambat. Periksa kondisi di bawah ini untuk secara akurat menentukan aplikasi mengalami situasi ini.
    • Aplikasi ini mengonsumsi banyak memori selama startup dan cadangan memori menunjukkan sejumlah Microsoft.AspNetCore.Routing.Matching.DfaNode besar instans.

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 secara internal untuk pencocokan 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 perutean. Asumsikan perutean ada dan akrab bagi 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 disusupi.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan jenis Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka kerja Anda sendiri ke penyusun. 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 berjalan dan metadata akhir pada titik akhir yang dikelompokkan. Misalnya, ini memungkinkan implementasi kustom EndpointDataSource untuk menjalankan filter titik akhir yang ditambahkan ke 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 itu UseEndpoints adalah tempat untuk mendaftarkan titik akhir.

Membuat middleware terintegrasi perutean

PERTIMBANGKAN untuk menentukan jenis 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 keberadaan 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 dan tanpa perutean:

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

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

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

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

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

Men-debug diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan pengembangan, 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. Menggunakan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang memetakan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Pengontrol
  • Razor Pages
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambda yang terdaftar dengan 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.
    • Hello World! ditulis ke 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 dan UseRoutingUseEndpoints menjalankan 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 untuk app.Use mendaftarkan middleware kustom yang berjalan di awal alur.
  • Panggilan untuk UseRouting mengonfigurasi middleware pencocokan rute untuk dijalankan setelah middleware kustom.
  • Titik akhir yang terdaftar dengan MapGet eksekusi di akhir alur.

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

Titik akhir

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, , MapPostdan metode serupa menghubungkan delegasi permintaan ke sistem perutean. 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 kesehatan dan otorisasi:

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

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

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan perutean.
  • 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 sadar 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. Propertinya dapat diperiksa. Objek titik akhir tidak dapat diubah dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya dipilih oleh sistem perutean.

Dalam kode sebelumnya, aplikasi. Gunakan mengonfigurasi middleware sebaris.

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 titik akhir yang disediakan / .

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

Menjalankan kode ini dengan URL / tampilan:

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

Menjalankan kode ini dengan url lain yang ditampilkan:

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 adalah terminal ketika kecocokan ditemukan. Middleware terminal didefinisikan nanti dalam artikel ini.
  • Middleware setelah UseEndpoints dijalankan hanya ketika tidak ada kecocokan yang ditemukan.

Middleware UseRouting menggunakan SetEndpoint metode untuk melampirkan titik akhir ke konteks saat ini. Dimungkinkan untuk mengganti UseRouting middleware dengan logika kustom dan masih mendapatkan manfaat menggunakan titik akhir. 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 menunjukkan 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 sebelumnya UseRouting untuk memodifikasi data yang dioperasikan perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil perutean 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 akses ke data sensitif ke konsol. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan RequiresAuditAttribute metadata. Sampel ini menunjukkan pola keikutsertaan di mana hanya titik akhir yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem metadata titik akhir 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 middleware log audit yang lebih lengkap akan:

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

Metadata RequiresAuditAttribute kebijakan audit didefinisikan sebagai untuk penggunaan yang Attribute lebih mudah 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 adalah Path == "/" untuk middleware dan Path == "/Routing" untuk perutean.
  • Ketika kecocokan berhasil, ia menjalankan beberapa fungsionalitas dan mengembalikan, daripada 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 tempat arbitrer di alur:
  • Middleware terminal memungkinkan kode arbitrer untuk menentukan kapan middleware cocok:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Perutean menyediakan solusi langsung untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Antarmuka titik akhir dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan interfacing manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Delegasi untuk memproses permintaan.
  • Kumpulan metadata arbitrer. Metadata digunakan untuk menerapkan masalah lintas pemotongan berdasarkan kebijakan dan konfigurasi yang melekat 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 perutean sebelum menulis middleware terminal.

Middleware terminal yang ada yang terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir sadar 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 penyusun 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.

Saat middleware perutean dijalankan, middleware menetapkan Endpoint nilai rute dan 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 perutean dapat memeriksa titik akhir dan mengambil tindakan. Misalnya, middleware otorisasi dapat menginterogasi pengumpulan metadata titik akhir untuk kebijakan otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi titik akhir yang dipilih dipanggil.

Sistem perutean dalam perutean titik akhir bertanggung jawab atas semua keputusan pengiriman. 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.

Peringatan

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. Set kecocokan dapat dipersempit lebih jauh pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk titik akhir yang cocok. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL terhadap kumpulan titik akhir dan templat 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. EndpointSelector Menggunakan 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 . Ini 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 /helloURL .
  • /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 dihitung sebelumnya dari rute ambigu. Pertimbangkan contoh seperti templat /{message:alpha} rute 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 yang sama dengan keduanya.
  • Jika sistem perutean melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir kasus penggunaan yang valid ini.

Peringatan

Urutan 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 pengontrol jangka panjang tanpa sistem perutean memberikan jaminan yang sama dengan implementasi perutean yang lebih 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 mencocokkan harapan akal sehat perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa adalah kecocokan yang /Products/List lebih baik daripada /Products/{id} untuk jalur /Products/ListURL . 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 catch-all 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.

Perutean titik akhir LinkGenerator mencakup API. 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 Pembantu Tag, Pembantu HTML, dan Hasil Tindakan, gunakan LinkGenerator API secara internal untuk menyediakan kemampuan pembuatan tautan.

Generator tautan didukung oleh konsep skema alamat dan 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 ini GetPath* paling mirip Url.Action dengan dan Url.Page, karena mereka menghasilkan URI yang berisi jalur absolut. Metode selalu GetUri* menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima menghasilkan HttpContext URI dalam konteks permintaan yang dijalankan. Nilai rute sekitar , jalur dasar URL, skema, dan host dari permintaan eksekusi digunakan kecuali ditimpa.

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

  1. Alamat terikat ke daftar titik akhir yang cocok dengan alamat.
  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 Deskripsi
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Peringatan

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan GetUri* metode ekstensi dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi Host header permintaan masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak tepercaya dapat dikirim kembali ke klien di URI dalam tampilan atau halaman. Sebaiknya semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi Host header 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.");
    }
}

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. 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 peka huruf besar/kecil dan berdasarkan representasi yang didekodekan dari jalur URL. Untuk mencocokkan pemisah { parameter rute literal atau }, keluar dari pemisah dengan mengulangi karakter. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

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

Peringatan

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

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

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

Perbaikan keikutsertaan untuk bug ini terkandung dalam .NET Core 3.1.301 SDK dan yang lebih baru. Kode berikut menetapkan sakelar internal yang memperbaiki 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 lolos dari karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter pemisah / jalur. Misalnya, rute foo/{*path} dengan nilai { path = "my/path" } rute 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 cocok dengan nilai rute yang terikat dari URL. : Menambahkan dan membatasi nama 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 nama lain : dan batasan.

Nama batasan dan argumen diteruskan ke IInlineConstraintResolver layanan untuk membuat instans yang IRouteConstraint 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 mungkin juga memiliki transformator parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti batasan, transformator parameter dapat ditambahkan sebaris ke parameter rute dengan menambahkan : nama transformator dan setelah nama parameter rute. Misalnya, templat blog/{article:slugify} rute menentukan slugify transformator. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Transformer parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Templat Rute Contoh URI yang Cocok Permintaan URI...
hello /hello Hanya cocok dengan jalur /hellotunggal .
{Page=Home} / Cocok dan diatur Page ke Home.
{Page=Home} /Contact Cocok dan diatur Page ke Contact.
{controller}/{action}/{id?} /Products/List Peta ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Peta ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Peta ke Home pengontrol dan Index metode. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Peta ke Products pengontrol dan Index metode. id diabaikan.

Menggunakan templat 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.

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

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

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 /aabcdURL . | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 kontrol yang jauh lebih besar atas perilaku pencocokan mereka.

Pencocokan serakah, juga dikenal sebagai pencocokan malas, cocok dengan string terbesar yang mungkin. Tidak serakah cocok dengan string sekecil mungkin.

Perutean dengan karakter khusus

Perutean 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 Dikodekan
/ %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.

Peringatan

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:

Kendala Contoh Contoh Kecocokan Catatan
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Cocok atau false. Tidak peka huruf besar/kecil
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 Cocok dengan nilai yang valid Guid
long {ticks:long} 123456789, -123456789 Cocok dengan nilai yang valid long
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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Beberapa batasan yang dibatasi 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) { }

Peringatan

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke jenis int CLR 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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Ekspresi reguler dapat ditentukan sebagai batasan sebaris menggunakan batasan regex(...) rute. 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.
  • Literal string verbatim.

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

Ekspresi reguler Ekspresi reguler yang lolos
^\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 diakhir $ dengan karakter dan cocok dengan akhir string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. ^ Tanpa karakter dan $ , ekspresi reguler cocok dengan substring apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Ekspresi String Cocokkan Komentar
[a-z]{2} halo Ya Kecocokan substring
[a-z]{2} 123abc456 Ya Kecocokan substring
[a-z]{2} Mz Ya Ekspresi yang cocok
[a-z]{2} MZ Ya Tidak peka huruf besar/kecil
^[a-z]{2}$ halo 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 cocok dengan action nilai rute 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.

Batasan rute kustom

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 baik sebagai Program.cs bagian AddRouting dari panggilan 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);
    }
}

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Kode sebelumnya:

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

Kode berikut adalah pendekatan yang lebih baik untuk mencegah yang id berisi 0 diproses:

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

Transformer parameter

Transformator parameter:

Misalnya, transformator parameter kustom slugify dalam pola blog\{article:slugify} rute 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 menggunakannya ConstraintMap di 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") output /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, contoh paling kompleks pembuatan URL menggunakan pengontrol atau Razor Halaman. Lihat perutean di pengontrol untuk informasi tambahan.

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.

Alamat

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 MVC.
    • IEndpointNameMetadata Menggunakan jenis metadata.
    • 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 Razor pembuatan URL lama 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 sekitar. 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 sekitar 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 dalam 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.
  • UrlIUrlHelpermendapatkan .
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama dan route nilai yang ditentukanaction.

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 diatur url ke /Edit/17 saat Halaman Edit Razor berisi arahan halaman berikut:

@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 sekitar.
  • IUrlHelper.Action selalu menyalin nilai saat ini action dan controller rute sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali ditimpa.
  • IUrlHelper.Page selalu mengambil alih nilai rute saat ini handler dengan null sebagai nilai eksplisit kecuali ditimpa.

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, , pagecontroller, 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. Pembatalan nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai sekitar yang harus digunakan dan yang harus diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai sekitar adalah mereka mencoba menyimpan pengetikan 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 pengembalian tersebut null biasanya disebabkan oleh tidak memahami pembatalan 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 sekitar 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 sekitar untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai sekitar 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 sekitar untuk controller, action nilai dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai sekitar untuk controller, action nilai dan id 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 khusus 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 diperlukan yang memungkinkan pembatalan nilai rute berfungsi.

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 nilai eksplisit adalah, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika nilai sekitar dan nilai eksplisit ada, dan kedua nilai berbeda, tolak nilai sekitar dan semua nilai sekitar 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 batasan semua lolos, operasi berlanjut.

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

  • Dari kiri-ke-kanan.
  • Setiap parameter memiliki nilai yang diterima diganti.
  • 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 templat {controller}/{action}/{id?}rute .

Nilai Sekitar Nilai Eksplisit Hasil
pengontrol = "Home" action = "About" /Home/About
pengontrol = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
pengontrol = "Home" action = "About", color = "Red" /Home/About?color=Red

Masalah dengan pembatalan nilai rute

Kode berikut menunjukkan contoh skema pembuatan URL yang tidak didukung oleh perutean:

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 tindakan menggunakan templat api/Products/{id} rute dan memiliki Name :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, AddRelatedProduct tindakan mengekstrak id nilai rute dari jalur URL. Misalnya, dengan jalur /api/Products/1URL , relatedProductId nilai diatur 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 kartubebas: *.domain.com, cocok www.domain.com, , subdomain.domain.comatau 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 , www.domain.com, dan subdomain.domain.comdomain.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();
}

[Host] Ketika atribut diterapkan ke pengontrol dan metode tindakan:

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Panduan performa untuk perutean

Ketika aplikasi mengalami masalah performa, perutean sering dicurigai sebagai masalahnya. 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 umum untuk mengasumsikan perutean adalah penyebabnya.

Perutean diuji performanya 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.");

Perutean waktu:

  • Interleave setiap middleware dengan salinan middleware 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. Time 2 Mengurangi dari Time 1 laporan waktu yang dihabiskan di dalam UseRouting middleware.

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 perutean yang berpotensi mahal

Daftar berikut ini memberikan beberapa wawasan tentang fitur perutean yang relatif 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: Banyak aplikasi kompleks memiliki akses database sebagai bagian dari peruteannya. Gunakan titik ekstensibilitas seperti MatcherPolicy dan EndpointSelectorContext, yang asinkron.

Panduan untuk tabel rute besar

Secara default ASP.NET Core menggunakan algoritma perutean yang memperdagangkan 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 daripada yang dapat menyebabkan pengaktifan aplikasi yang lambat. Periksa kondisi di bawah ini untuk secara akurat menentukan aplikasi mengalami situasi ini.
    • Aplikasi ini mengonsumsi banyak memori selama startup dan cadangan memori menunjukkan sejumlah Microsoft.AspNetCore.Routing.Matching.DfaNode besar instans.

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 secara internal untuk pencocokan 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 perutean. Asumsikan perutean ada dan akrab bagi 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 disusupi.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan jenis Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka kerja Anda sendiri ke penyusun. 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 itu UseEndpoints adalah tempat untuk mendaftarkan titik akhir.

Membuat middleware terintegrasi perutean

PERTIMBANGKAN untuk menentukan jenis 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 keberadaan 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 dan tanpa perutean:

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

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

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

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

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

Men-debug diagnostik

Untuk output diagnostik perutean terperinci, atur Logging:LogLevel:Microsoft ke Debug. Di lingkungan pengembangan, 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. Menggunakan informasi titik akhir dari aplikasi, perutean juga dapat menghasilkan URL yang memetakan ke titik akhir.

Aplikasi dapat mengonfigurasi perutean menggunakan:

  • Pengontrol
  • Razor Pages
  • SignalR
  • Layanan gRPC
  • Middleware yang diaktifkan titik akhir seperti Pemeriksaan Kesehatan.
  • Delegasi dan lambda yang terdaftar dengan 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 dan yang lebih baru. Untuk informasi tentang sistem perutean sebelumnya berdasarkan IRouter, pilih versi ASP.NET Core 2.1 menggunakan 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.
    • Hello World! ditulis ke 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.

Titik akhir

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, , MapPostdan metode serupa menghubungkan delegasi permintaan ke sistem perutean. 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 pada 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 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!");
        });
    });
}

Jika Anda ingin melihat komentar kode yang diterjemahkan ke bahasa selain bahasa Inggris, beri tahu kami dalam masalah diskusi GitHub ini.

Contoh sebelumnya menunjukkan cara:

  • Middleware otorisasi dapat digunakan dengan perutean.
  • 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 sadar 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. Propertinya dapat diperiksa. Objek titik akhir tidak dapat diubah dan tidak dapat diubah setelah pembuatan. Jenis titik akhir yang paling umum adalah RouteEndpoint. RouteEndpoint termasuk informasi yang memungkinkannya dipilih oleh sistem perutean.

Dalam kode sebelumnya, aplikasi. Gunakan 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 titik akhir yang disediakan / .

Menjalankan kode ini dengan URL / tampilan:

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

Menjalankan kode ini dengan url lain yang ditampilkan:

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 adalah terminal 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 masih mendapatkan manfaat menggunakan titik akhir. 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 menunjukkan 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 sebelumnya UseRouting untuk memodifikasi data yang dioperasikan perutean.
  • Middleware dapat berjalan antara UseRouting dan UseEndpoints untuk memproses hasil perutean 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 akses ke data sensitif ke konsol. Middleware dapat dikonfigurasi untuk mengaudit titik akhir dengan AuditPolicyAttribute metadata. Sampel ini menunjukkan pola keikutsertaan di mana hanya titik akhir yang ditandai sebagai sensitif yang diaudit. Dimungkinkan untuk menentukan logika ini secara terbalik, mengaudit semua yang tidak ditandai sebagai aman, misalnya. Sistem metadata titik akhir 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 middleware log audit yang lebih lengkap akan:

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

Metadata AuditPolicyAttribute kebijakan audit didefinisikan sebagai untuk penggunaan yang Attribute lebih mudah 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 adalah Path == "/" untuk middleware dan Path == "/Movie" untuk perutean.
  • Ketika kecocokan berhasil, ia menjalankan beberapa fungsionalitas dan mengembalikan, daripada 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 tempat arbitrer di alur:
  • Middleware terminal memungkinkan kode arbitrer untuk menentukan kapan middleware cocok:
    • Kode pencocokan rute kustom bisa verbose dan sulit ditulis dengan benar.
    • Perutean menyediakan solusi langsung untuk aplikasi umum. Sebagian besar aplikasi tidak memerlukan kode pencocokan rute kustom.
  • Antarmuka titik akhir dengan middleware seperti UseAuthorization dan UseCors.
    • Menggunakan middleware terminal dengan UseAuthorization atau UseCors memerlukan interfacing manual dengan sistem otorisasi.

Titik akhir mendefinisikan keduanya:

  • Delegasi untuk memproses permintaan.
  • Kumpulan metadata arbitrer. Metadata digunakan untuk menerapkan masalah lintas pemotongan berdasarkan kebijakan dan konfigurasi yang melekat 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 perutean sebelum menulis middleware terminal.

Middleware terminal yang ada yang terintegrasi dengan Peta atau MapWhen biasanya dapat diubah menjadi titik akhir sadar 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 penyusun 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.

Saat middleware perutean dijalankan, middleware menetapkan Endpoint nilai rute dan 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 perutean dapat memeriksa titik akhir dan mengambil tindakan. Misalnya, middleware otorisasi dapat menginterogasi pengumpulan metadata titik akhir untuk kebijakan otorisasi. Setelah semua middleware dalam alur pemrosesan permintaan dijalankan, delegasi titik akhir yang dipilih dipanggil.

Sistem perutean dalam perutean titik akhir bertanggung jawab atas semua keputusan pengiriman. 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.

Peringatan

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:

Pencocokan URL beroperasi dalam serangkaian fase yang dapat dikonfigurasi. Dalam setiap fase, output adalah sekumpulan kecocokan. Set kecocokan dapat dipersempit lebih jauh pada fase berikutnya. Implementasi perutean tidak menjamin urutan pemrosesan untuk titik akhir yang cocok. Semua kemungkinan kecocokan diproses sekaligus. Fase pencocokan URL terjadi dalam urutan berikut. ASP.NET Core:

  1. Memproses jalur URL terhadap kumpulan titik akhir dan templat 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 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 . Ini 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 /helloURL .
  • /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 dihitung sebelumnya dari rute ambigu. Pertimbangkan contoh seperti templat /{message:alpha} rute 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 yang sama dengan keduanya.
  • Jika sistem perutean melaporkan kesalahan ambiguitas saat startup, sistem akan memblokir kasus penggunaan yang valid ini.

Peringatan

Urutan 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 pengontrol jangka panjang tanpa sistem perutean memberikan jaminan yang sama dengan implementasi perutean yang lebih 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 dan yang lebih baru:

  • 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 mencocokkan harapan akal sehat perilaku perutean.

Misalnya, pertimbangkan templat /Products/List dan /Products/{id}. Akan masuk akal untuk mengasumsikan bahwa adalah kecocokan yang /Products/List lebih baik daripada /Products/{id} untuk jalur /Products/ListURL . 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 catch-all 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.

Perutean titik akhir LinkGenerator mencakup API. 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 Pembantu Tag, Pembantu HTML, dan Hasil Tindakan, gunakan LinkGenerator API secara internal untuk menyediakan kemampuan pembuatan tautan.

Generator tautan didukung oleh konsep skema alamat dan 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 ini GetPath* paling mirip Url.Action dengan dan Url.Page, karena mereka menghasilkan URI yang berisi jalur absolut. Metode selalu GetUri* menghasilkan URI absolut yang berisi skema dan host. Metode yang menerima menghasilkan HttpContext URI dalam konteks permintaan yang dijalankan. Nilai rute sekitar , jalur dasar URL, skema, dan host dari permintaan eksekusi digunakan kecuali ditimpa.

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

  1. Alamat terikat ke daftar titik akhir yang cocok dengan alamat.
  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 Deskripsi
GetPathByAddress Menghasilkan URI dengan jalur absolut berdasarkan nilai yang disediakan.
GetUriByAddress Menghasilkan URI absolut berdasarkan nilai yang disediakan.

Peringatan

Perhatikan implikasi metode panggilan LinkGenerator berikut:

  • Gunakan GetUri* metode ekstensi dengan hati-hati dalam konfigurasi aplikasi yang tidak memvalidasi Host header permintaan masuk. Host Jika header permintaan masuk tidak divalidasi, input permintaan yang tidak tepercaya dapat dikirim kembali ke klien di URI dalam tampilan atau halaman. Sebaiknya semua aplikasi produksi mengonfigurasi server mereka untuk memvalidasi Host header 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 peka huruf besar/kecil dan berdasarkan representasi yang didekodekan dari jalur URL. Untuk mencocokkan pemisah { parameter rute literal atau }, keluar dari pemisah dengan mengulangi karakter. Misalnya {{ atau }}.

Tanda bintang * atau tanda bintang **ganda :

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

Peringatan

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

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

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

Perbaikan keikutsertaan untuk bug ini terkandung dalam .NET Core 3.1.301 SDK dan yang lebih baru. Kode berikut menetapkan sakelar internal yang memperbaiki 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 lolos dari karakter yang sesuai saat rute digunakan untuk menghasilkan URL, termasuk karakter pemisah / jalur. Misalnya, rute foo/{*path} dengan nilai { path = "my/path" } rute 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 cocok dengan nilai rute yang terikat dari URL. : Menambahkan dan membatasi nama 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 nama lain : dan batasan.

Nama batasan dan argumen diteruskan ke IInlineConstraintResolver layanan untuk membuat instans yang IRouteConstraint 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 mungkin juga memiliki transformator parameter. Transformator parameter mengubah nilai parameter saat membuat tautan dan mencocokkan tindakan dan halaman menjadi URL. Seperti batasan, transformator parameter dapat ditambahkan sebaris ke parameter rute dengan menambahkan : nama transformator dan setelah nama parameter rute. Misalnya, templat blog/{article:slugify} rute menentukan slugify transformator. Untuk informasi selengkapnya tentang transformator parameter, lihat bagian Referensi transformator parameter.

Tabel berikut menunjukkan contoh templat rute dan perilakunya:

Templat Rute Contoh URI yang Cocok Permintaan URI...
hello /hello Hanya cocok dengan jalur /hellotunggal .
{Page=Home} / Cocok dan diatur Page ke Home.
{Page=Home} /Contact Cocok dan diatur Page ke Contact.
{controller}/{action}/{id?} /Products/List Peta ke Products pengontrol dan List tindakan.
{controller}/{action}/{id?} /Products/Details/123 Peta ke Products pengontrol dan Details tindakan denganid diatur ke 123.
{controller=Home}/{action=Index}/{id?} / Peta ke Home pengontrol dan Index metode. id diabaikan.
{controller=Home}/{action=Index}/{id?} /Products Peta ke Products pengontrol dan Index metode. id diabaikan.

Menggunakan templat 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.

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

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

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /ab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 /aabcdURL . | digunakan untuk membantu memvisualisasikan cara kerja algoritma. Kasus ini bukan kecocokan, yang dijelaskan oleh algoritma yang sama:

  • Literal pertama, 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 {d}rute .
  • Harfiah berikutnya, kanan ke kiri, adalah a. Jadi /aab|c|d dicari mulai di mana kita tinggalkan, kemudian a ditemukan /a|a|b|c|d.
  • Nilai di sebelah kanan (b) sekarang cocok dengan parameter {b}rute .
  • 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 kontrol yang jauh lebih besar atas perilaku pencocokan mereka.

Pencocokan serakah, juga dikenal sebagai pencocokan malas, cocok dengan string terbesar yang 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.

Peringatan

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:

Kendala Contoh Contoh Kecocokan Catatan
int {id:int} 123456789, -123456789 Cocok dengan bilangan bulat apa pun
bool {active:bool} true, FALSE true Cocok atau false. Tidak peka huruf besar/kecil
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 Cocok dengan nilai yang valid Guid
long {ticks:long} 123456789, -123456789 Cocok dengan nilai yang valid long
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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Beberapa batasan yang dibatasi 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) { }

Peringatan

Batasan rute yang memverifikasi URL dan dikonversi ke jenis CLR selalu menggunakan budaya invarian. Misalnya, konversi ke jenis int CLR 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

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Ekspresi reguler dapat ditentukan sebagai batasan sebaris menggunakan batasan regex(...) rute. 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.
  • Literal string verbatim.

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

Ekspresi reguler Ekspresi reguler yang lolos
^\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 diakhir $ dengan karakter dan cocok dengan akhir string. Karakter ^ dan $ memastikan bahwa ekspresi reguler cocok dengan seluruh nilai parameter rute. ^ Tanpa karakter dan $ , ekspresi reguler cocok dengan substring apa pun dalam string, yang sering kali tidak diinginkan. Tabel berikut ini menyediakan contoh dan menjelaskan mengapa tabel cocok atau gagal dicocokkan:

Ekspresi String Cocokkan Komentar
[a-z]{2} halo Ya Kecocokan substring
[a-z]{2} 123abc456 Ya Kecocokan substring
[a-z]{2} Mz Ya Ekspresi yang cocok
[a-z]{2} MZ Ya Tidak peka huruf besar/kecil
^[a-z]{2}$ halo 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 cocok dengan action nilai rute 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.

Batasan rute kustom

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 salah satu bagian dari layanan. Menambahkan Panggilan atau dengan mengonfigurasi RouteOptions 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;
    }
}

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna jahat dapat memberikan masukan ke RegularExpressions yang menyebabkan Penolakan Serangan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Kode sebelumnya:

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

Kode berikut adalah pendekatan yang lebih baik untuk mencegah yang id berisi 0 diproses:

[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 transformator parameter

Transformator parameter:

Misalnya, transformator parameter kustom slugify dalam pola blog\{article:slugify} rute 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 menggunakannya ConstraintMap di 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") output /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, contoh paling kompleks pembuatan URL menggunakan pengontrol atau Razor Halaman. Lihat perutean di pengontrol untuk informasi tambahan.

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.

Alamat

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 MVC.
    • IEndpointNameMetadata Menggunakan jenis metadata.
    • 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 Razor pembuatan URL lama 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 sekitar. 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 sekitar 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 dalam 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.
  • UrlIUrlHelpermendapatkan .
  • Action menghasilkan URL dengan jalur absolut untuk metode tindakan. URL berisi nama dan route nilai yang ditentukanaction.

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 diatur url ke /Edit/17 saat Halaman Edit Razor berisi arahan halaman berikut:

@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 sekitar.
  • IUrlHelper.Action selalu menyalin nilai saat ini action dan controller rute sebagai nilai eksplisit kecuali ditimpa oleh pengembang.
  • IUrlHelper.Page selalu menyalin nilai rute saat ini page sebagai nilai eksplisit kecuali ditimpa.
  • IUrlHelper.Page selalu mengambil alih nilai rute saat ini handler dengan null sebagai nilai eksplisit kecuali ditimpa.

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, , pagecontroller, 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. Pembatalan nilai rute adalah proses di mana perutean memutuskan nilai rute mana dari nilai sekitar yang harus digunakan dan yang harus diabaikan. Setiap nilai sekitar dipertimbangkan dan dikombinasikan dengan nilai eksplisit, atau diabaikan.

Cara terbaik untuk memikirkan peran nilai sekitar adalah mereka mencoba menyimpan pengetikan 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 pengembalian tersebut null biasanya disebabkan oleh tidak memahami pembatalan 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 sekitar 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 sekitar untuk controller dan action dapat digunakan.
  • Jika nilai eksplisit berisi nilai untuk action, nilai sekitar apa pun untuk action diabaikan. Nilai sekitar 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 sekitar untuk controller, action nilai dan id tidak akan digunakan. Jika nilai eksplisit untuk controller sama dengan nilai sekitar untuk controller, action nilai dan id 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 khusus 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 diperlukan yang memungkinkan pembatalan nilai rute berfungsi.

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 nilai eksplisit adalah, tolak nilai sekitar dan semua nilai sekitar berikutnya.
    • Jika nilai sekitar dan nilai eksplisit ada, dan kedua nilai berbeda, tolak nilai sekitar dan semua nilai sekitar 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 batasan semua lolos, operasi berlanjut.

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

  • Dari kiri-ke-kanan.
  • Setiap parameter memiliki nilai yang diterima diganti.
  • 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 templat {controller}/{action}/{id?}rute .

Nilai Sekitar Nilai Eksplisit Hasil
pengontrol = "Home" action = "About" /Home/About
pengontrol = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
pengontrol = "Home" action = "About", color = "Red" /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 warisan.

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 kartubebas: *.domain.com, cocok www.domain.com, , subdomain.domain.comatau 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 , www.domain.com, dan subdomain.domain.comdomain.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();
    }
}

[Host] Ketika atribut diterapkan ke pengontrol dan metode tindakan:

  • Atribut pada tindakan digunakan.
  • Atribut pengontrol diabaikan.

Panduan performa untuk perutean

Sebagian besar perutean diperbarui di ASP.NET Core 3.0 untuk meningkatkan performa.

Ketika aplikasi mengalami masalah performa, perutean sering dicurigai sebagai masalahnya. 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 umum untuk mengasumsikan perutean adalah penyebabnya.

Perutean diuji performanya 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.");
        });
    });
}

Perutean waktu:

  • Interleave setiap middleware dengan salinan middleware 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. Time 2 Mengurangi dari Time 1 laporan waktu yang dihabiskan di dalam UseRouting middleware.

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 perutean yang berpotensi mahal

Daftar berikut ini memberikan beberapa wawasan tentang fitur perutean yang relatif 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: Banyak aplikasi kompleks memiliki akses database sebagai bagian dari peruteannya. ASP.NET Core 2.2 dan perutean yang lebih lama mungkin tidak memberikan 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 perutean. Asumsikan perutean ada dan akrab bagi 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 komposabilitas metadata.
  • Ditargetkan oleh berbagai metode ekstensi.

Mendeklarasikan jenis Anda sendiri memungkinkan Anda menambahkan fungsionalitas khusus kerangka kerja Anda sendiri ke penyusun. 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 itu UseEndpoints adalah tempat untuk mendaftarkan titik akhir.

Membuat middleware terintegrasi perutean

PERTIMBANGKAN untuk menentukan jenis 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 keberadaan 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 dan 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 meneruskan kebijakan fallback. Kebijakan fallback, jika ditentukan, berlaku untuk keduanya:

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

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

Men-debug diagnostik

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

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