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
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)/Supplier
die 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:
- Vergleichen Sie die Pfadvorlage, um festzustellen, ob diese Konvention für die aktuelle Anforderung gilt. Wenn sie nicht zutrifft, geben Sie NULL zurück.
- Wenn die Konvention zutrifft, verwenden Sie eigenschaften der ODataPathSegment-Instanzen , um Controller- und Aktionsnamen abzuleiten.
- 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:
- 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.
- Die Konvention gilt nur für GET-Anforderungen und nur, wenn die Pfadvorlage "~/entityset/key/navigation/key" lautet.
- 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.
- 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.