Udostępnij za pośrednictwem


Konwencje routingu w internetowym interfejsie API ASP.NET 2 Odata

W tym artykule opisano konwencje routingu używane przez internetowy interfejs API 2 w ASP.NET 4.x dla punktów końcowych OData.

Gdy internetowy interfejs API pobiera żądanie OData, mapuje żądanie na nazwę kontrolera i nazwę akcji. Mapowanie jest oparte na metodzie HTTP i identyfikatorze URI. Na przykład GET /odata/Products(1) mapuje wartość na ProductsController.GetProduct.

W części 1 tego artykułu opisano wbudowane konwencje routingu OData. Te konwencje są przeznaczone specjalnie dla punktów końcowych OData i zastępują domyślny system routingu interfejsu API sieci Web. (Zastąpienie odbywa się po wywołaniu usługi MapODataRoute).

W części 2 pokazano, jak dodać niestandardowe konwencje routingu. Obecnie wbudowane konwencje nie obejmują całego zakresu identyfikatorów URI OData, ale można je rozszerzyć w celu obsługi dodatkowych przypadków.

Wbudowane konwencje routingu

Zanim opiszę konwencje routingu OData w internetowym interfejsie API, warto zrozumieć identyfikatory URI OData. Identyfikator URI OData składa się z:

  • Katalog główny usługi
  • Ścieżka zasobu
  • Opcje zapytań

Zrzut ekranu pokazujący, jak wyglądają konwencje routingu danych O, wyświetlając opcje katalogu głównego usługi, ścieżki zasobu i zapytania od lewej do prawej.

W przypadku routingu ważną częścią jest ścieżka zasobu. Ścieżka zasobu jest podzielona na segmenty. Na przykład ma /Products(1)/Supplier trzy segmenty:

  • Products odwołuje się do zestawu jednostek o nazwie "Products".
  • 1 jest kluczem jednostki, wybierając pojedynczą jednostkę z zestawu.
  • Supplier to właściwość nawigacji, która wybiera powiązaną jednostkę.

Dlatego ta ścieżka wybiera dostawcę produktu 1.

Uwaga

Segmenty ścieżek OData nie zawsze odpowiadają segmentom identyfikatora URI. Na przykład "1" jest uważany za segment ścieżki.

Nazwy kontrolerów. Nazwa kontrolera jest zawsze pochodna od jednostki ustawionej w katalogu głównym ścieżki zasobu. Jeśli na przykład ścieżka zasobu to /Products(1)/Supplier, internetowy interfejs API szuka kontrolera o nazwie ProductsController.

Nazwy akcji. Nazwy akcji pochodzą z segmentów ścieżki oraz modelu danych jednostki (EDM), jak opisano w poniższych tabelach. W niektórych przypadkach masz dwie opcje nazwy akcji. Na przykład "Pobierz" lub "GetProducts".

Wykonywanie zapytań o jednostki

Żądanie Przykładowy identyfikator URI Nazwa akcji Przykładowa akcja
GET /entityset /Produktów GetEntitySet lub Get GetProducts
GET /entityset(klucz) /Products(1) GetEntityType lub Get GetProduct
GET /entityset(key)/cast /Products(1)/Models.Book GetEntityType lub Get GetBook

Aby uzyskać więcej informacji, zobacz Tworzenie Read-Only punktu końcowego OData.

Tworzenie, aktualizowanie i usuwanie jednostek

Żądanie Przykładowy identyfikator URI Nazwa akcji Przykładowa akcja
POST /entityset /Produktów PostEntityType lub Post PostProduct
PUT /entityset(klucz) /Products(1) PutEntityType lub Put PutProduct
PUT /entityset(key)/cast /Products(1)/Models.Book PutEntityType lub Put PutBook
PATCH /entityset(key) /Products(1) PatchEntityType lub Patch PatchProduct
PATCH /entityset(key)/cast /Products(1)/Models.Book PatchEntityType lub Patch PatchBook
DELETE /entityset(key) /Products(1) DeleteEntityType lub Delete DeleteProduct (Usuń produkt)
DELETE /entityset(key)/cast /Products(1)/Models.Book DeleteEntityType lub Delete DeleteBook

Wykonywanie zapytań względem właściwości nawigacji

Żądanie Przykładowy identyfikator URI Nazwa akcji Przykładowa akcja
GET /entityset(klucz)/nawigacja /Products(1)/Dostawca GetNavigationFromEntityType lub GetNavigation GetSupplierFromProduct
GET /entityset(key)/cast/navigation /Products(1)/Models.Book/Author GetNavigationFromEntityType lub GetNavigation GetAuthorFromBook

Aby uzyskać więcej informacji, zobacz Praca z relacjami jednostek.

Tworzenie i usuwanie łączy

Żądanie Przykładowy identyfikator URI Nazwa akcji
POST /entityset(klucz)/$links/nawigacja /Products(1)/$links/Supplier Createlink
PUT /entityset(klucz)/$links/nawigacja /Products(1)/$links/Supplier Createlink
DELETE /entityset(klucz)/$links/nawigacja /Products(1)/$links/Supplier DeleteLink
DELETE /entityset(klucz)/$links/navigation(relatedKey) /Products/(1)/$links/Suppliers(1) DeleteLink

Aby uzyskać więcej informacji, zobacz Praca z relacjami jednostek.

Właściwości

Wymaga internetowego interfejsu API 2

Żądanie Przykładowy identyfikator URI Nazwa akcji Przykładowa akcja
GET /entityset(key)/property /Products(1)/Name GetPropertyFromEntityType lub GetProperty GetNameFromProduct
GET /entityset(key)/cast/property /Products(1)/Models.Book/Author GetPropertyFromEntityType lub GetProperty GetTitleFromBook

Akcje

Żądanie Przykładowy identyfikator URI Nazwa akcji Przykładowa akcja
POST /entityset(klucz)/action /Products(1)/Rate ActionNameOnEntityType lub ActionName RateOnProduct
POST /entityset(klucz)/cast/action /Products(1)/Models.Book/CheckOut ActionNameOnEntityType lub ActionName CheckOutOnBook

Aby uzyskać więcej informacji, zobacz OData Actions (Akcje OData).

Sygnatury metod

Poniżej przedstawiono niektóre reguły dotyczące podpisów metod:

  • Jeśli ścieżka zawiera klucz, akcja powinna mieć parametr o nazwie key.
  • Jeśli ścieżka zawiera klucz do właściwości nawigacji, akcja powinna mieć parametr o nazwie relatedKey.
  • Dekoruj parametry klucza i relatedKey za pomocą parametru [FromODataUri].
  • Żądania POST i PUT przyjmują parametr typu jednostki.
  • Żądania PATCH przyjmują parametr typu Delta<T>, gdzie T jest typem jednostki.

Poniżej przedstawiono przykład pokazujący podpisy metod dla każdej wbudowanej konwencji routingu OData.

public class ProductsController : ODataController
{
    // GET /odata/Products
    public IQueryable<Product> Get()

    // GET /odata/Products(1)
    public Product Get([FromODataUri] int key)

    // GET /odata/Products(1)/ODataRouting.Models.Book
    public Book GetBook([FromODataUri] int key)

    // POST /odata/Products 
    public HttpResponseMessage Post(Product item)

    // PUT /odata/Products(1)
    public HttpResponseMessage Put([FromODataUri] int key, Product item)

    // PATCH /odata/Products(1)
    public HttpResponseMessage Patch([FromODataUri] int key, Delta<Product> item)

    // DELETE /odata/Products(1)
    public HttpResponseMessage Delete([FromODataUri] int key)

    // PUT /odata/Products(1)/ODataRouting.Models.Book
    public HttpResponseMessage PutBook([FromODataUri] int key, Book item)

    // PATCH /odata/Products(1)/ODataRouting.Models.Book
    public HttpResponseMessage PatchBook([FromODataUri] int key, Delta<Book> item)

    // DELETE /odata/Products(1)/ODataRouting.Models.Book
    public HttpResponseMessage DeleteBook([FromODataUri] int key)

    //  GET /odata/Products(1)/Supplier
    public Supplier GetSupplierFromProduct([FromODataUri] int key)

    // GET /odata/Products(1)/ODataRouting.Models.Book/Author
    public Author GetAuthorFromBook([FromODataUri] int key)

    // POST /odata/Products(1)/$links/Supplier
    public HttpResponseMessage CreateLink([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)

    // DELETE /odata/Products(1)/$links/Supplier
    public HttpResponseMessage DeleteLink([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)

    // DELETE /odata/Products(1)/$links/Parts(1)
    public HttpResponseMessage DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty)

    // GET odata/Products(1)/Name
    // GET odata/Products(1)/Name/$value
    public HttpResponseMessage GetNameFromProduct([FromODataUri] int key)

    // GET /odata/Products(1)/ODataRouting.Models.Book/Title
    // GET /odata/Products(1)/ODataRouting.Models.Book/Title/$value
    public HttpResponseMessage GetTitleFromBook([FromODataUri] int key)
}

Niestandardowe konwencje routingu

Obecnie wbudowane konwencje nie obejmują wszystkich możliwych identyfikatorów URI OData. Możesz dodać nowe konwencje, implementując interfejs IODataRoutingConvention . Ten interfejs ma dwie metody:

string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, 
    ILookup<string, HttpActionDescriptor> actionMap);
  • Funkcja SelectController zwraca nazwę kontrolera.
  • Funkcja SelectAction zwraca nazwę akcji.

W przypadku obu metod, jeśli konwencja nie ma zastosowania do tego żądania, metoda powinna zwrócić wartość null.

Parametr ODataPath reprezentuje przeanalizowaną ścieżkę zasobu OData. Zawiera listę wystąpień ODataPathSegment , po jednym dla każdego segmentu ścieżki zasobu. ODataPathSegment jest abstrakcyjną klasą; każdy typ segmentu jest reprezentowany przez klasę, która pochodzi z ODataPathSegment.

Właściwość ODataPath.TemplatePath jest ciągiem reprezentującym łączenie wszystkich segmentów ścieżki. Jeśli na przykład identyfikator URI to /Products(1)/Supplier, szablon ścieżki to "~/entityset/key/navigation". Zwróć uwagę, że segmenty nie odpowiadają bezpośrednio segmentom identyfikatora URI. Na przykład klucz jednostki (1) jest reprezentowany jako własny element ODataPathSegment.

Zazwyczaj implementacja interfejsu IODataRoutingConvention wykonuje następujące czynności:

  1. Porównaj szablon ścieżki, aby sprawdzić, czy ta konwencja ma zastosowanie do bieżącego żądania. Jeśli nie ma zastosowania, zwróć wartość null.
  2. Jeśli konwencja ma zastosowanie, użyj właściwości wystąpień ODataPathSegment , aby uzyskać nazwy kontrolera i akcji.
  3. W przypadku akcji dodaj wszystkie wartości do słownika tras, które powinny być powiązane z parametrami akcji (zazwyczaj kluczami jednostek).

Przyjrzyjmy się konkretnego przykładu. Wbudowane konwencje routingu nie obsługują indeksowania w kolekcji nawigacji. Innymi słowy, nie ma konwencji dla identyfikatorów URI, takich jak następujące:

/odata/Products(1)/Suppliers(1)

Oto niestandardowa konwencja routingu do obsługi tego typu zapytania.

using Microsoft.Data.Edm;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData.Routing;
using System.Web.Http.OData.Routing.Conventions;

namespace ODataRouting
{
    public class NavigationIndexRoutingConvention : EntitySetRoutingConvention
    {
        public override string SelectAction(ODataPath odataPath, HttpControllerContext context, 
            ILookup<string, HttpActionDescriptor> actionMap)
        {
            if (context.Request.Method == HttpMethod.Get && 
                odataPath.PathTemplate == "~/entityset/key/navigation/key")
            {
                NavigationPathSegment navigationSegment = odataPath.Segments[2] as NavigationPathSegment;
                IEdmNavigationProperty navigationProperty = navigationSegment.NavigationProperty.Partner;
                IEdmEntityType declaringType = navigationProperty.DeclaringType as IEdmEntityType;

                string actionName = "Get" + declaringType.Name;
                if (actionMap.Contains(actionName))
                {
                    // Add keys to route data, so they will bind to action parameters.
                    KeyValuePathSegment keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
                    context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;

                    KeyValuePathSegment relatedKeySegment = odataPath.Segments[3] as KeyValuePathSegment;
                    context.RouteData.Values[ODataRouteConstants.RelatedKey] = relatedKeySegment.Value;

                    return actionName;
                }
            }
            // Not a match.
            return null;
        }
    }
}

Uwagi:

  1. Pochodzim z klasy EntitySetRoutingConvention, ponieważ metoda SelectController w tej klasie jest odpowiednia dla tej nowej konwencji routingu. Oznacza to, że nie muszę ponownie implementować selektora SelectController.
  2. Konwencja ma zastosowanie tylko do żądań GET i tylko wtedy, gdy szablon ścieżki to "~/entityset/key/navigation/key".
  3. Nazwa akcji to "Get{EntityType}", gdzie {EntityType} jest typem kolekcji nawigacji. Na przykład "GetSupplier". Możesz użyć dowolnej konwencji nazewnictwa — wystarczy upewnić się, że akcje kontrolera są zgodne.
  4. Akcja przyjmuje dwa parametry o nazwie key i relatedKey. (Aby uzyskać listę niektórych wstępnie zdefiniowanych nazw parametrów, zobacz ODataRouteConstants).

Następnym krokiem jest dodanie nowej konwencji do listy konwencji routingu. Dzieje się tak podczas konfiguracji, jak pokazano w poniższym kodzie:

using ODataRouting.Models;
using System.Web.Http;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Routing;
using System.Web.Http.OData.Routing.Conventions;

namespace ODataRouting
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
            // Create EDM (not shown).

            // Create the default collection of built-in conventions.
            var conventions = ODataRoutingConventions.CreateDefault();
            // Insert the custom convention at the start of the collection.
            conventions.Insert(0, new NavigationIndexRoutingConvention());

            config.Routes.MapODataRoute(routeName: "ODataRoute",
                routePrefix: "odata",
                model: modelBuilder.GetEdmModel(),
                pathHandler: new DefaultODataPathHandler(),
                routingConventions: conventions);

        }
    }
}

Oto kilka innych przykładowych konwencji routingu, które są przydatne do zbadania:

Oczywiście sam internetowy interfejs API jest typu open source, dzięki czemu można zobaczyć kod źródłowy wbudowanych konwencji routingu. Są one zdefiniowane w przestrzeni nazw System.Web.Http.OData.Routing.Conventions .