Freigeben über


Routingkonventionen in ASP.NET-Web-API 2 Odata

In diesem Artikel werden die Routingkonventionen beschrieben, die die Web-API 2 in ASP.NET 4.x für OData-Endpunkte verwendet.

Wenn die Web-API eine OData-Anforderung abruft, ordnet sie die Anforderung einem Controllernamen und einem Aktionsnamen zu. Die Zuordnung basiert auf der HTTP-Methode und dem URI. Wird z. GET /odata/Products(1) B. zugeordnet ProductsController.GetProduct.

In Teil 1 dieses Artikels beschreibe ich die integrierten OData-Routingkonventionen. Diese Konventionen sind speziell für OData-Endpunkte konzipiert und ersetzen das Standardmäßige Web-API-Routingsystem. (Die Ersetzung erfolgt, wenn Sie MapODataRoute aufrufen.)

In Teil 2 zeige ich, wie sie benutzerdefinierte Routingkonventionen hinzufügen. Derzeit decken die integrierten Konventionen nicht den gesamten Bereich der OData-URIs ab, aber Sie können sie erweitern, um zusätzliche Fälle zu behandeln.

Integrierte Routingkonventionen

Bevor ich die OData-Routingkonventionen in der Web-API beschreibe, ist es hilfreich, OData-URIs zu verstehen. Ein OData-URI besteht aus:

  • Der Dienststamm
  • Der Ressourcenpfad
  • Abfrageoptionen

Screenshot, der zeigt, wie die O-Datenroutingkonventionen aussehen, wobei Dienststamm, Ressourcenpfad und Abfrageoptionen von links nach rechts angezeigt werden.

Für das Routing ist der Ressourcenpfad der wichtige Teil. Der Ressourcenpfad ist in Segmente unterteilt. Verfügt beispielsweise /Products(1)/Supplier über drei Segmente:

  • Products verweist auf einen Entitätssatz namens "Products".
  • 1 ist ein Entitätsschlüssel, der eine einzelne Entität aus dem Satz auswählt.
  • Supplier ist eine Navigationseigenschaft, die eine verknüpfte Entität auswählt.

Dieser Weg wählt also den Lieferanten von Produkt 1 aus.

Hinweis

OData-Pfadsegmente entsprechen nicht immer URI-Segmenten. Beispielsweise wird "1" als Pfadsegment betrachtet.

Controllernamen. Der Controllername wird immer von der Entität abgeleitet, die am Stamm des Ressourcenpfads festgelegt ist. Wenn der Ressourcenpfad beispielsweise lautet /Products(1)/Supplier, sucht die Web-API nach einem Controller mit dem Namen ProductsController.

Aktionsnamen. Aktionsnamen werden aus den Pfadsegmenten und dem Entitätsdatenmodell (EDM) abgeleitet, wie in den folgenden Tabellen aufgeführt. In einigen Fällen haben Sie zwei Optionen für den Aktionsnamen. Beispiel: "Get" oder "GetProducts".

Abfragen von Entitäten

Anforderung Beispiel-URI Aktionsname Beispielaktion
GET /entityset /Produkte GetEntitySet oder Get GetProducts
GET /entityset(key) /Products(1) GetEntityType oder Get GetProduct
GET /entityset(key)/cast /Products(1)/Models.Book GetEntityType oder Get GetBook

Weitere Informationen finden Sie unter Erstellen eines Read-Only OData-Endpunkts.

Erstellen, Aktualisieren und Löschen von Entitäten

Anforderung Beispiel-URI Aktionsname Beispielaktion
POST /entityset /Produkte PostEntityType oder Post PostProduct
PUT /entityset(key) /Products(1) PutEntityType oder Put PutProduct
PUT /entityset(key)/cast /Products(1)/Models.Book PutEntityType oder Put PutBook
PATCH /entityset(key) /Products(1) PatchEntityType oder Patch PatchProduct
PATCH /entityset(key)/cast /Products(1)/Models.Book PatchEntityType oder Patch PatchBook
DELETE /entityset(key) /Products(1) DeleteEntityType oder Delete DeleteProduct
DELETE /entityset(key)/cast /Products(1)/Models.Book DeleteEntityType oder Delete DeleteBook

Abfragen einer Navigationseigenschaft

Anforderung Beispiel-URI Aktionsname Beispielaktion
GET /entityset(key)/navigation /Products(1)/Lieferant GetNavigationFromEntityType oder GetNavigation GetSupplierFromProduct
GET /entityset(key)/cast/navigation /Products(1)/Models.Book/Author GetNavigationFromEntityType oder GetNavigation GetAuthorFromBook

Weitere Informationen finden Sie unter Arbeiten mit Entitätsbeziehungen.

Erstellen und Löschen von Links

Anforderung Beispiel-URI Aktionsname
POST /entityset(key)/$links/navigation /Products(1)/$links/Lieferant Createlink
PUT /entityset(key)/$links/navigation /Products(1)/$links/Lieferant Createlink
DELETE /entityset(key)/$links/navigation /Products(1)/$links/Lieferant DeleteLink
DELETE /entityset(key)/$links/navigation(relatedKey) /Products/(1)/$links/Lieferanten(1) DeleteLink

Weitere Informationen finden Sie unter Arbeiten mit Entitätsbeziehungen.

Eigenschaften

Erfordert Web-API 2

Anforderung Beispiel-URI Aktionsname Beispielaktion
GET /entityset(key)/property /Products(1)/Name GetPropertyFromEntityType oder GetProperty GetNameFromProduct
GET /entityset(key)/cast/property /Products(1)/Models.Book/Author GetPropertyFromEntityType oder GetProperty GetTitleFromBook

Aktionen

Anforderung Beispiel-URI Aktionsname Beispielaktion
POST /entityset(key)/action /Products(1)/Rate ActionNameOnEntityType oder ActionName RateOnProduct
POST /entityset(key)/cast/action /Products(1)/Models.Book/CheckOut ActionNameOnEntityType oder ActionName CheckOutOnBook

Weitere Informationen finden Sie unter OData-Aktionen.

Methodensignaturen

Hier sind einige Regeln für die Methodensignaturen:

  • Wenn der Pfad einen Schlüssel enthält, sollte die Aktion über einen Parameter namens key verfügen.
  • Wenn der Pfad einen Schlüssel in einer Navigationseigenschaft enthält, sollte die Aktion über einen Parameter namens relatedKey verfügen.
  • Dekorieren Sie key- und relatedKey-Parameter mit dem Parameter [FromODataUri].
  • POST- und PUT-Anforderungen verwenden einen Parameter des Entitätstyps.
  • PATCH-Anforderungen verwenden einen Parameter vom Typ Delta<T>, wobei T der Entitätstyp ist.

Als Referenz finden Sie hier ein Beispiel, das Methodensignaturen für jede integrierte OData-Routingkonvention zeigt.

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

Konventionen für benutzerdefiniertes Routing

Derzeit decken die integrierten Konventionen nicht alle möglichen OData-URIs ab. Sie können neue Konventionen hinzufügen, indem Sie die IODataRoutingConvention-Schnittstelle implementieren. Diese Schnittstelle verfügt über zwei Methoden:

string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, 
    ILookup<string, HttpActionDescriptor> actionMap);
  • SelectController gibt den Namen des Controllers zurück.
  • SelectAction gibt den Namen der Aktion zurück.

Wenn die Konvention für beide Methoden nicht für diese Anforderung gilt, sollte die Methode NULL zurückgeben.

Der ODataPath-Parameter stellt den analysierten OData-Ressourcenpfad dar. Sie enthält eine Liste von ODataPathSegment-Instanzen , eine für jedes Segment des Ressourcenpfads. ODataPathSegment ist eine abstrakte Klasse. Jeder Segmenttyp wird durch eine Klasse dargestellt, die von ODataPathSegment abgeleitet wird.

Die ODataPath.TemplatePath-Eigenschaft ist eine Zeichenfolge, die die Verkettung aller Pfadsegmente darstellt. Wenn der URI beispielsweise lautet, lautet /Products(1)/Supplierdie Pfadvorlage "~/entityset/key/navigation". Beachten Sie, dass die Segmente nicht direkt mit URI-Segmenten übereinstimmen. Beispielsweise wird der Entitätsschlüssel (1) als eigenes ODataPathSegment dargestellt.

In der Regel führt eine Implementierung von IODataRoutingConvention folgendes aus:

  1. Vergleichen Sie die Pfadvorlage, um festzustellen, ob diese Konvention für die aktuelle Anforderung gilt. Wenn sie nicht zutrifft, geben Sie NULL zurück.
  2. Wenn die Konvention zutrifft, verwenden Sie eigenschaften der ODataPathSegment-Instanzen , um Controller- und Aktionsnamen abzuleiten.
  3. Fügen Sie für Aktionen dem Routenwörterbuch alle Werte hinzu, die an die Aktionsparameter (in der Regel Entitätsschlüssel) gebunden werden sollen.

Sehen wir uns ein bestimmtes Beispiel an. Die integrierten Routingkonventionen unterstützen die Indizierung in eine Navigationssammlung nicht. Mit anderen Worten, es gibt keine Konvention für URIs wie die folgenden:

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

Hier finden Sie eine benutzerdefinierte Routingkonvention, um diese Art von Abfrage zu behandeln.

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

Hinweise:

  1. Ich leite von EntitySetRoutingConvention ab, da die SelectController-Methode in dieser Klasse für diese neue Routingkonvention geeignet ist. Das bedeutet, dass ich SelectController nicht erneut implementieren muss.
  2. Die Konvention gilt nur für GET-Anforderungen und nur, wenn die Pfadvorlage "~/entityset/key/navigation/key" lautet.
  3. Der Aktionsname lautet "Get{EntityType}", wobei {EntityType} der Typ der Navigationssammlung ist. Beispiel: "GetSupplier". Sie können eine beliebige Benennungskonvention verwenden– stellen Sie einfach sicher, dass Ihre Controlleraktionen übereinstimmen.
  4. Die Aktion verwendet zwei Parameter mit dem Namen key und relatedKey. (Eine Liste mit einigen vordefinierten Parameternamen finden Sie unter ODataRouteConstants.)

Im nächsten Schritt wird die neue Konvention der Liste der Routingkonventionen hinzugefügt. Dies geschieht während der Konfiguration, wie im folgenden Code gezeigt:

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

        }
    }
}

Im Folgenden finden Sie einige weitere Beispielroutingkonventionen, die für die Untersuchung nützlich sind:

Und natürlich ist die Web-API selbst Open-Source, sodass Sie den Quellcode für die integrierten Routingkonventionen sehen können. Diese werden im Namespace System.Web.Http.OData.Routing.Conventions definiert.