Wybieranie routingu i akcji w internetowym interfejsie API ASP.NET

W tym artykule opisano, jak ASP.NET internetowy interfejs API kieruje żądanie HTTP do określonej akcji na kontrolerze.

Uwaga

Aby zapoznać się z ogólnym omówieniem routingu, zobacz Routing w ASP.NET internetowym interfejsie API.

W tym artykule przedstawiono szczegóły procesu routingu. Jeśli tworzysz projekt internetowego interfejsu API i okaże się, że niektóre żądania nie są kierowane zgodnie z oczekiwaniami, miejmy nadzieję, że ten artykuł pomoże.

Routing ma trzy główne fazy:

  1. Dopasowywanie identyfikatora URI do szablonu trasy.
  2. Wybieranie kontrolera.
  3. Wybieranie akcji.

Niektóre części procesu można zastąpić własnymi zachowaniami niestandardowymi. W tym artykule opisano zachowanie domyślne. Na końcu zanotuję miejsca, w których można dostosować zachowanie.

Szablony tras

Szablon trasy wygląda podobnie do ścieżki identyfikatora URI, ale może zawierać wartości zastępcze oznaczone nawiasami klamrowymi:

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

Podczas tworzenia trasy można podać wartości domyślne dla niektórych lub wszystkich symboli zastępczych:

defaults: new { category = "all" }

Możesz również podać ograniczenia, które ograniczają sposób dopasowania segmentu identyfikatora URI do symbolu zastępczego:

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

Struktura próbuje dopasować segmenty w ścieżce identyfikatora URI do szablonu. Literały w szablonie muszą być dokładnie zgodne. Symbol zastępczy jest zgodny z dowolną wartością, chyba że określono ograniczenia. Struktura nie pasuje do innych części identyfikatora URI, takich jak nazwa hosta lub parametry zapytania. Struktura wybiera pierwszą trasę w tabeli tras, która jest zgodna z identyfikatorem URI.

Istnieją dwa specjalne symbole zastępcze: "{controller}" i "{action}".

  • Element "{controller}" zawiera nazwę kontrolera.
  • Element "{action}" zawiera nazwę akcji. W internetowym interfejsie API zwykle pomija się "{action}".

Wartość domyślna

Jeśli podasz wartości domyślne, trasa będzie zgodna z identyfikatorem URI, który nie ma tych segmentów. Na przykład:

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

Identyfikatory http://localhost/api/products/all URI i http://localhost/api/products pasują do poprzedniej trasy. W ostatnim identyfikatorze URI brakujący {category} segment ma przypisaną wartość alldomyślną .

Słownik tras

Jeśli struktura znajdzie dopasowanie dla identyfikatora URI, tworzy słownik zawierający wartość dla każdego symbolu zastępczego. Klucze to nazwy symboli zastępczych, w tym nawiasy klamrowe. Wartości są pobierane ze ścieżki identyfikatora URI lub z wartości domyślnych. Słownik jest przechowywany w obiekcie IHttpRouteData .

W tej fazie dopasowywania tras symbole zastępcze "{controller}" i "{action}" są traktowane podobnie jak inne symbole zastępcze. Są one po prostu przechowywane w słowniku z innymi wartościami.

Wartość domyślna może mieć wartość specjalną RouteParameter.Optional. Jeśli symbol zastępczy zostanie przypisany do tej wartości, wartość nie zostanie dodana do słownika tras. Na przykład:

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

W przypadku ścieżki identyfikatora URI "api/products" słownik tras będzie zawierać następujące elementy:

  • kontroler: "products"
  • category: "all"

Jednak w przypadku "api/products/toys/123" słownik tras będzie zawierać następujące elementy:

  • kontroler: "products"
  • category: "toys"
  • id: "123"

Wartości domyślne mogą również zawierać wartość, która nie jest wyświetlana w żadnym miejscu w szablonie trasy. Jeśli trasa jest zgodna, ta wartość jest przechowywana w słowniku. Na przykład:

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

Jeśli ścieżka identyfikatora URI to "api/root/8", słownik będzie zawierać dwie wartości:

  • kontroler: "klienci"
  • id: "8"

Wybieranie kontrolera

Wybór kontrolera jest obsługiwany przez metodę IHttpControllerSelector.SelectController . Ta metoda przyjmuje wystąpienie httpRequestMessage i zwraca klasę HttpControllerDescriptor. Domyślna implementacja jest dostarczana przez klasę DefaultHttpControllerSelector . Ta klasa używa prostego algorytmu:

  1. Wyszukaj w słowniku tras klucz "kontroler".
  2. Pobierz wartość tego klucza i dołącz ciąg "Kontroler", aby uzyskać nazwę typu kontrolera.
  3. Poszukaj kontrolera internetowego interfejsu API o tej nazwie typu.

Jeśli na przykład słownik tras zawiera parę klucz-wartość "controller" = "products", typ kontrolera to "ProductsController". Jeśli nie ma pasującego typu lub wielu dopasowań, platforma zwraca błąd do klienta.

W kroku 3 element DefaultHttpControllerSelector używa interfejsu IHttpControllerTypeResolver , aby uzyskać listę typów kontrolerów internetowego interfejsu API. Domyślna implementacja interfejsu IHttpControllerTypeResolver zwraca wszystkie klasy publiczne, które (a) implementują interfejs IHttpController, (b) nie są abstrakcyjne, a (c) mają nazwę kończącą się ciągiem "Controller".

Wybór akcji

Po wybraniu kontrolera platforma wybiera akcję, wywołując metodę IHttpActionSelector.SelectAction . Ta metoda przyjmuje obiekt HttpControllerContext i zwraca klasę HttpActionDescriptor.

Domyślna implementacja jest dostarczana przez klasę ApiControllerActionSelector . Aby wybrać akcję, przyjrzyj się następującym elementom:

  • Metoda HTTP żądania.
  • Symbol zastępczy "{action}" w szablonie trasy, jeśli istnieje.
  • Parametry akcji na kontrolerze.

Zanim przyjrzymy się algorytmowi wyboru, musimy zrozumieć pewne kwestie dotyczące akcji kontrolera.

Które metody na kontrolerze są uznawane za "akcje"? Podczas wybierania akcji platforma analizuje tylko metody wystąpień publicznych na kontrolerze. Ponadto wyklucza metody "specjalnej nazwy" (konstruktory, zdarzenia, przeciążenia operatorów itd.) i metody dziedziczone z klasy ApiController .

Metody HTTP. Platforma wybiera tylko akcje zgodne z metodą HTTP żądania określone w następujący sposób:

  1. Metodę HTTP można określić za pomocą atrybutu : AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost lub HttpPut.
  2. W przeciwnym razie, jeśli nazwa metody kontrolera rozpoczyna się od "Get", "Post", "Put", "Delete", "Head", "Options" lub "Patch", zgodnie z konwencją akcja obsługuje tę metodę HTTP.
  3. Jeśli żadna z powyższych metod nie obsługuje metody POST.

Powiązania parametrów. Powiązanie parametrów to sposób tworzenia wartości dla parametru przez internetowy interfejs API. Oto domyślna reguła powiązania parametrów:

  • Proste typy są pobierane z identyfikatora URI.
  • Typy złożone są pobierane z treści żądania.

Proste typy obejmują wszystkie typy pierwotne .NET Framework oraz DateTime, Decimal, Guid, String i TimeSpan. Dla każdej akcji co najwyżej jeden parametr może odczytać treść żądania.

Uwaga

Istnieje możliwość zastąpienia domyślnych reguł powiązań. Zobacz Powiązanie parametru WebAPI pod maską.

W tym kontekście znajduje się algorytm wyboru akcji.

  1. Utwórz listę wszystkich akcji na kontrolerze, które są zgodne z metodą żądania HTTP.

  2. Jeśli słownik tras ma wpis "akcja", usuń akcje, których nazwa nie jest zgodna z tą wartością.

  3. Spróbuj dopasować parametry akcji do identyfikatora URI w następujący sposób:

    1. Dla każdej akcji pobierz listę parametrów, które są prostym typem, gdzie powiązanie pobiera parametr z identyfikatora URI. Wyklucz parametry opcjonalne.
    2. Na tej liście spróbuj znaleźć dopasowanie dla każdej nazwy parametru w słowniku tras lub w ciągu zapytania identyfikatora URI. Dopasowania są bez uwzględniania wielkości liter i nie zależą od kolejności parametrów.
    3. Wybierz akcję, w której każdy parametr na liście ma dopasowanie w identyfikatorze URI.
    4. Jeśli więcej tej akcji spełnia te kryteria, wybierz jedną z najbardziej pasujących parametrów.
  4. Ignoruj akcje za pomocą atrybutu [NonAction].

Krok 3 jest prawdopodobnie najbardziej mylący. Podstawowym pomysłem jest to, że parametr może pobrać jego wartość z identyfikatora URI, z treści żądania lub z powiązania niestandardowego. W przypadku parametrów pochodzących z identyfikatora URI chcemy upewnić się, że identyfikator URI rzeczywiście zawiera wartość dla tego parametru w ścieżce (za pośrednictwem słownika tras) lub w ciągu zapytania.

Rozważmy na przykład następującą akcję:

public void Get(int id)

Parametr id wiąże się z identyfikatorem URI. W związku z tym ta akcja może być zgodna tylko z identyfikatorem URI zawierającym wartość "id" w słowniku tras lub w ciągu zapytania.

Parametry opcjonalne są wyjątkiem, ponieważ są opcjonalne. W przypadku opcjonalnego parametru jest ok, jeśli powiązanie nie może pobrać wartości z identyfikatora URI.

Typy złożone są wyjątkiem z innej przyczyny. Typ złożony może być powiązany tylko z identyfikatorem URI za pomocą powiązania niestandardowego. Jednak w takim przypadku struktura nie może wcześniej wiedzieć, czy parametr będzie wiązany z określonym identyfikatorem URI. Aby dowiedzieć się, należy wywołać powiązanie. Celem algorytmu wyboru jest wybranie akcji z opisu statycznego przed wywołaniem powiązań. W związku z tym typy złożone są wykluczone z zgodnego algorytmu.

Po wybraniu akcji wszystkie powiązania parametrów są wywoływane.

Podsumowanie:

  • Akcja musi być zgodna z metodą HTTP żądania.
  • Nazwa akcji musi być zgodna z wpisem "action" w słowniku tras, jeśli istnieje.
  • Dla każdego parametru akcji, jeśli parametr jest pobierany z identyfikatora URI, należy znaleźć nazwę parametru w słowniku tras lub w ciągu zapytania identyfikatora URI. (Parametry opcjonalne i parametry z typami złożonymi są wykluczone).
  • Spróbuj dopasować największą liczbę parametrów. Najlepszym dopasowaniem może być metoda bez parametrów.

Przykład rozszerzony

Trasy:

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

Kontroler:

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

Żądanie HTTP:

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

Dopasowywanie tras

Identyfikator URI jest zgodny z trasą o nazwie "DefaultApi". Słownik tras zawiera następujące wpisy:

  • kontroler: "products"
  • id: "1"

Słownik tras nie zawiera parametrów ciągu zapytania, "version" i "details", ale będą one nadal brane pod uwagę podczas wybierania akcji.

Wybór kontrolera

Z wpisu "kontroler" w słowniku tras typ kontrolera to ProductsController.

Wybór akcji

Żądanie HTTP jest żądaniem GET. Akcje kontrolera obsługujące metodę GET to GetAll, GetByIdi FindProductsByName. Słownik tras nie zawiera wpisu "action", więc nie musimy odpowiadać nazwie akcji.

Następnie próbujemy dopasować nazwy parametrów dla akcji, patrząc tylko na akcje GET.

Akcja Parametry do dopasowania
GetAll brak
GetById "id"
FindProductsByName "name"

Zwróć uwagę, że parametr wersji parametru nie jest brany GetById pod uwagę, ponieważ jest to parametr opcjonalny.

Metoda GetAll jest dopasowywana trywialnie. Metoda jest również zgodna GetById , ponieważ słownik tras zawiera wartość "id". Metoda nie jest zgodna FindProductsByName .

Metoda GetById wygrywa, ponieważ pasuje do jednego parametru, a nie parametrów dla .GetAll Metoda jest wywoływana z następującymi wartościami parametrów:

  • id = 1
  • wersja = 1.5

Zwróć uwagę, że mimo że wersja nie została użyta w algorytmie wyboru, wartość parametru pochodzi z ciągu zapytania identyfikatora URI.

Punkty rozszerzenia

Internetowy interfejs API udostępnia punkty rozszerzenia dla niektórych części procesu routingu.

Interfejs Opis
IHttpControllerSelector Wybiera kontroler.
IHttpControllerTypeResolver Pobiera listę typów kontrolerów. Selektor DefaultHttpControllerSelector wybiera typ kontrolera z tej listy.
IAssembliesResolver Pobiera listę zestawów projektów. Interfejs IHttpControllerTypeResolver używa tej listy do znajdowania typów kontrolerów.
IHttpControllerActivator Tworzy nowe wystąpienia kontrolera.
IHttpActionSelector Wybiera akcję.
IHttpActionInvoker Wywołuje akcję.

Aby zapewnić własną implementację dla dowolnego z tych interfejsów, użyj kolekcji Usługi w obiekcie HttpConfiguration :

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