Bagikan melalui


Pemilihan Perutean dan Tindakan di API Web ASP.NET

Artikel ini menjelaskan cara ASP.NET Web API merutekan permintaan HTTP ke tindakan tertentu pada pengontrol.

Catatan

Untuk gambaran umum perutean tingkat tinggi, lihat Perutean di ASP.NET Web API.

Artikel ini melihat detail proses perutean. Jika Anda membuat proyek Web API dan menemukan bahwa beberapa permintaan tidak dirutekan seperti yang Anda harapkan, mudah-mudahan artikel ini akan membantu.

Perutean memiliki tiga fase utama:

  1. Mencocokkan URI dengan templat rute.
  2. Memilih pengontrol.
  3. Memilih tindakan.

Anda dapat mengganti beberapa bagian proses dengan perilaku kustom Anda sendiri. Dalam artikel ini, saya menjelaskan perilaku default. Pada akhirnya, saya mencatat tempat-tempat di mana Anda dapat menyesuaikan perilaku.

Templat Rute

Templat rute terlihat mirip dengan jalur URI, tetapi dapat memiliki nilai tempat penampung, yang ditunjukkan dengan kurung kurawal:

"api/{controller}/public/{category}/{id}"

Saat membuat rute, Anda dapat memberikan nilai default untuk beberapa atau semua tempat penampung:

defaults: new { category = "all" }

Anda juga dapat memberikan batasan, yang membatasi bagaimana segmen URI dapat cocok dengan tempat penampung:

constraints: new { id = @"\d+" }   // Only matches if "id" is one or more digits.

Kerangka kerja mencoba mencocokkan segmen di jalur URI ke templat. Literal dalam templat harus sama persis. Tempat penampung cocok dengan nilai apa pun, kecuali Anda menentukan batasan. Kerangka kerja tidak cocok dengan bagian lain dari URI, seperti nama host atau parameter kueri. Kerangka kerja memilih rute pertama dalam tabel rute yang cocok dengan URI.

Ada dua tempat penampung khusus: "{controller}" dan "{action}".

  • "{controller}" menyediakan nama pengontrol.
  • "{action}" memberikan nama tindakan. Di WEB API, konvensi yang biasa adalah menghilangkan "{action}".

Default

Jika Anda memberikan default, rute akan cocok dengan URI yang kehilangan segmen tersebut. Contohnya:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{category}",
    defaults: new { category = "all" }
);

URI http://localhost/api/products/all dan http://localhost/api/products cocok dengan rute sebelumnya. Dalam URI terakhir, segmen yang hilang {category} diberi nilai alldefault .

Kamus Rute

Jika kerangka kerja menemukan kecocokan untuk URI, kerangka kerja akan membuat kamus yang berisi nilai untuk setiap tempat penampung. Kuncinya adalah nama tempat penampung, tidak termasuk kurung kurawal. Nilai diambil dari jalur URI atau dari default. Kamus disimpan dalam objek IHttpRouteData .

Selama fase pencocokan rute ini, tempat penampung "{controller}" dan "{action}" khusus diperlakukan seperti tempat penampung lainnya. Mereka hanya disimpan dalam kamus dengan nilai lainnya.

Default dapat memiliki nilai khusus RouteParameter.Optional. Jika tempat penampung diberi nilai ini, nilai tidak ditambahkan ke kamus rute. Contohnya:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{category}/{id}",
    defaults: new { category = "all", id = RouteParameter.Optional }
);

Untuk jalur URI "api/products", kamus rute akan berisi:

  • pengontrol: "produk"
  • kategori: "semua"

Namun, untuk "api/products/toys/123", kamus rute akan berisi:

  • pengontrol: "produk"
  • kategori: "mainan"
  • id: "123"

Default juga dapat menyertakan nilai yang tidak muncul di mana saja dalam templat rute. Jika rute cocok, nilai tersebut disimpan dalam kamus. Contohnya:

routes.MapHttpRoute(
    name: "Root",
    routeTemplate: "api/root/{id}",
    defaults: new { controller = "customers", id = RouteParameter.Optional }
);

Jika jalur URI adalah "api/root/8", kamus akan berisi dua nilai:

  • pengontrol: "pelanggan"
  • id: "8"

Memilih Pengontrol

Pilihan pengontrol ditangani oleh metode IHttpControllerSelector.SelectController . Metode ini mengambil instans HttpRequestMessage dan mengembalikan HttpControllerDescriptor. Implementasi default disediakan oleh kelas DefaultHttpControllerSelector . Kelas ini menggunakan algoritma langsung:

  1. Lihat di kamus rute untuk kunci "pengontrol".
  2. Ambil nilai untuk kunci ini dan tambahkan string "Controller" untuk mendapatkan nama jenis pengontrol.
  3. Cari pengontrol API Web dengan nama jenis ini.

Misalnya, jika kamus rute berisi pasangan kunci-nilai "controller" = "products", maka jenis pengontrol adalah "ProductsController". Jika tidak ada jenis yang cocok, atau beberapa kecocokan, kerangka kerja mengembalikan kesalahan kepada klien.

Untuk langkah 3, DefaultHttpControllerSelector menggunakan antarmuka IHttpControllerTypeResolver untuk mendapatkan daftar tipe pengontrol API Web. Implementasi default IHttpControllerTypeResolver mengembalikan semua kelas publik yang (a) menerapkan IHttpController, (b) tidak abstrak, dan (c) memiliki nama yang berakhiran "Pengontrol".

Pemilihan Tindakan

Setelah memilih pengontrol, kerangka kerja memilih tindakan dengan memanggil metode IHttpActionSelector.SelectAction . Metode ini mengambil HttpControllerContext dan mengembalikan HttpActionDescriptor.

Implementasi default disediakan oleh kelas ApiControllerActionSelector . Untuk memilih tindakan, tindakan akan melihat hal berikut:

  • Metode HTTP permintaan.
  • Tempat penampung "{action}" di templat rute, jika ada.
  • Parameter tindakan pada pengontrol.

Sebelum melihat algoritma pemilihan, kita perlu memahami beberapa hal tentang tindakan pengontrol.

Metode mana pada pengontrol yang dianggap "tindakan"? Saat memilih tindakan, kerangka kerja hanya melihat metode instans publik pada pengontrol. Selain itu, ini tidak termasuk metode "nama khusus" (konstruktor, peristiwa, kelebihan beban operator, dan sebagainya), dan metode yang diwarisi dari kelas ApiController .

Metode HTTP. Kerangka kerja hanya memilih tindakan yang cocok dengan metode HTTP permintaan, yang ditentukan sebagai berikut:

  1. Anda dapat menentukan metode HTTP dengan atribut: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, atau HttpPut.
  2. Jika tidak, jika nama metode pengontrol dimulai dengan "Get", "Post", "Put", "Delete", "Head", "Options", atau "Patch", maka dengan konvensi tindakan mendukung metode HTTP tersebut.
  3. Jika tidak ada hal di atas, metode ini mendukung POST.

Pengikatan Parameter. Pengikatan parameter adalah cara Api Web membuat nilai untuk parameter. Berikut adalah aturan default untuk pengikatan parameter:

  • Jenis sederhana diambil dari URI.
  • Jenis kompleks diambil dari isi permintaan.

Jenis sederhana mencakup semua jenis primitif .NET Framework, ditambah DateTime, Desimal, Guid, String, dan TimeSpan. Untuk setiap tindakan, paling banyak satu parameter dapat membaca isi permintaan.

Catatan

Dimungkinkan untuk mengambil alih aturan pengikatan default. Lihat Pengikatan Parameter WebAPI di bawah tenda.

Dengan latar belakang itu, berikut adalah algoritma pemilihan tindakan.

  1. Buat daftar semua tindakan pada pengontrol yang cocok dengan metode permintaan HTTP.

  2. Jika kamus rute memiliki entri "tindakan", hapus tindakan yang namanya tidak cocok dengan nilai ini.

  3. Cobalah untuk mencocokkan parameter tindakan dengan URI, sebagai berikut:

    1. Untuk setiap tindakan, dapatkan daftar parameter yang merupakan jenis sederhana, di mana pengikatan mendapatkan parameter dari URI. Mengecualikan parameter opsional.
    2. Dari daftar ini, coba temukan kecocokan untuk setiap nama parameter, baik dalam kamus rute atau di string kueri URI. Kecocokan tidak peka huruf besar/kecil dan tidak bergantung pada urutan parameter.
    3. Pilih tindakan di mana setiap parameter dalam daftar memiliki kecocokan di URI.
    4. Jika lebih dari satu tindakan tersebut memenuhi kriteria ini, pilih tindakan dengan parameter terbanyak yang cocok.
  4. Abaikan tindakan dengan atribut [NonAction ].

Langkah #3 mungkin yang paling membingungkan. Ide dasarnya adalah bahwa parameter bisa mendapatkan nilainya baik dari URI, dari isi permintaan, atau dari pengikatan kustom. Untuk parameter yang berasal dari URI, kami ingin memastikan bahwa URI benar-benar berisi nilai untuk parameter tersebut, baik di jalur (melalui kamus rute) atau dalam string kueri.

Misalnya, pertimbangkan tindakan berikut:

public void Get(int id)

Parameter id mengikat ke URI. Oleh karena itu, tindakan ini hanya dapat mencocokkan URI yang berisi nilai untuk "id", baik di kamus rute atau dalam string kueri.

Parameter opsional adalah pengecualian, karena bersifat opsional. Untuk parameter opsional, tidak apa-apa jika pengikatan tidak bisa mendapatkan nilai dari URI.

Jenis kompleks adalah pengecualian karena alasan yang berbeda. Jenis kompleks hanya dapat mengikat URI melalui pengikatan kustom. Tetapi dalam hal ini, kerangka kerja tidak dapat mengetahui terlebih dahulu apakah parameter akan mengikat URI tertentu. Untuk mengetahuinya, perlu memanggil pengikatan. Tujuan dari algoritma pemilihan adalah untuk memilih tindakan dari deskripsi statis, sebelum memanggil pengikatan apa pun. Oleh karena itu, jenis kompleks dikecualikan dari algoritma yang cocok.

Setelah tindakan dipilih, semua pengikatan parameter dipanggil.

Ringkasan:

  • Tindakan harus cocok dengan metode HTTP permintaan.
  • Nama tindakan harus cocok dengan entri "tindakan" dalam kamus rute, jika ada.
  • Untuk setiap parameter tindakan, jika parameter diambil dari URI, maka nama parameter harus ditemukan baik di kamus rute atau dalam string kueri URI. (Parameter dan parameter opsional dengan jenis kompleks dikecualikan.)
  • Cobalah untuk mencocokkan jumlah parameter terbanyak. Kecocokan terbaik mungkin adalah metode tanpa parameter.

Contoh yang Diperluas

Rute:

routes.MapHttpRoute(
    name: "ApiRoot",
    routeTemplate: "api/root/{id}",
    defaults: new { controller = "products", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

Controller:

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAll() {}
    public Product GetById(int id, double version = 1.0) {}
    [HttpGet]
    public void FindProductsByName(string name) {}
    public void Post(Product value) {}
    public void Put(int id, Product value) {}
}

Permintaan HTTP:

GET http://localhost:34701/api/products/1?version=1.5&details=1

Pencocokan Rute

URI cocok dengan rute bernama "DefaultApi". Kamus rute berisi entri berikut:

  • pengontrol: "produk"
  • id: "1"

Kamus rute tidak berisi parameter string kueri, "versi" dan "detail", tetapi ini masih akan dipertimbangkan selama pemilihan tindakan.

Pilihan Pengontrol

Dari entri "pengontrol" dalam kamus rute, jenis pengontrol adalah ProductsController.

Pemilihan Tindakan

Permintaan HTTP adalah permintaan GET. Tindakan pengontrol yang mendukung GET adalah GetAll, , GetByIddan FindProductsByName. Kamus rute tidak berisi entri untuk "tindakan", jadi kita tidak perlu mencocokkan nama tindakan.

Selanjutnya, kita mencoba mencocokkan nama parameter untuk tindakan, hanya melihat tindakan GET.

Tindakan Parameter yang Cocok
GetAll tidak ada
GetById "id"
FindProductsByName "name"

Perhatikan bahwa parameter GetByIdversi tidak dipertimbangkan, karena ini adalah parameter opsional.

Metode ini GetAll cocok secara sepele. Metode ini GetById juga cocok, karena kamus rute berisi "id". Metode FindProductsByName tidak cocok.

Metode GetById ini menang, karena cocok dengan satu parameter, versus tidak ada parameter untuk GetAll. Metode ini dipanggil dengan nilai parameter berikut:

  • id = 1
  • versi = 1.5

Perhatikan bahwa meskipun versi tidak digunakan dalam algoritma pemilihan, nilai parameter berasal dari string kueri URI.

Titik Ekstensi

WEB API menyediakan titik ekstensi untuk beberapa bagian dari proses perutean.

Antarmuka Deskripsi
IHttpControllerSelector Memilih pengontrol.
IHttpControllerTypeResolver Mendapatkan daftar jenis pengontrol. DefaultHttpControllerSelector memilih tipe pengontrol dari daftar ini.
IAssembliesResolver Mendapatkan daftar rakitan proyek. Antarmuka IHttpControllerTypeResolver menggunakan daftar ini untuk menemukan tipe pengontrol.
IHttpControllerActivator Membuat instans pengontrol baru.
IHttpActionSelector Memilih tindakan.
IHttpActionInvoker Memanggil tindakan.

Untuk menyediakan implementasi Anda sendiri untuk salah satu antarmuka ini, gunakan koleksi Layanan pada objek HttpConfiguration :

var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));