Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Routing ist der Prozess, bei dem die Web-API eine URI einer Aktion zuordnet. Web-API 2 unterstützt einen neuen Routingtyp, der als Attributrouting bezeichnet wird. Wie der Name schon sagt, verwendet Attributrouting Attribute zum Definieren von Routen. Das Attributrouting bietet Ihnen mehr Kontrolle über die URIs in Ihrer Web-API. Sie können z. B. ganz einfach URIs erstellen, die Hierarchien von Ressourcen beschreiben.
Der frühere Routingstil, der als konventionsbasiertes Routing bezeichnet wird, wird weiterhin vollständig unterstützt. Tatsächlich können Sie beide Techniken im selben Projekt kombinieren.
In diesem Thema wird gezeigt, wie Sie das Attributrouting aktivieren und die verschiedenen Optionen für das Attributrouting beschreiben. Ein End-to-End-Lernprogramm, das Attributrouting verwendet, finden Sie unter Erstellen einer REST-API mit Attributrouting in Web API 2.
Voraussetzungen
Visual Studio 2017 Community-, Professional- oder Enterprise-Edition
Alternativ können Sie den NuGet-Paket-Manager verwenden, um die erforderlichen Pakete zu installieren. Wählen Sie im Menü "Extras" in Visual Studio "NuGet Paket-Manager" und dann Paket-Manager Konsole aus. Geben Sie den folgenden Befehl im Paket-Manager-Konsolenfenster ein:
Install-Package Microsoft.AspNet.WebApi.WebHost
Warum Attribut-Routing?
Die erste Version der Web-API verwendet konventionsbasiertes Routing. In diesem Routingtyp definieren Sie eine oder mehrere Routenvorlagen, die im Grunde parametrisierte Zeichenfolgen sind. Wenn das Framework eine Anforderung empfängt, gleicht es den URI mit der Routenvorlage ab. Weitere Informationen zum konventionsbasierten Routing finden Sie unter Routing in ASP.NET Web-API.
Ein Vorteil des konventionsbasierten Routings besteht darin, dass Vorlagen an einem zentralen Ort definiert sind und die Routingregeln für alle Controller einheitlich angewendet werden. Leider erschwert das konventionsbasierte Routen es, bestimmte URI-Muster zu unterstützen, die in RESTful-APIs üblich sind. Ressourcen enthalten z. B. häufig untergeordnete Ressourcen: Kunden haben Aufträge, Filme haben Schauspieler, Bücher haben Autoren usw. Es ist natürlich, URIs zu erstellen, die diese Beziehungen widerspiegeln:
/customers/1/orders
Diese Art von URI ist schwierig, mithilfe von konventionsbasiertem Routing zu erstellen. Obwohl dies möglich ist, werden die Ergebnisse nicht gut skaliert, wenn Sie über viele Controller oder Ressourcentypen verfügen.
Beim Attributrouting ist es trivial, eine Route für diesen URI zu definieren. Sie fügen der Controlleraktion einfach ein Attribut hinzu:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Hier sind einige andere Muster, die das Routing von Attributen vereinfachen.
API-Versionsverwaltung
In diesem Beispiel würde "/api/v1/products" an einen anderen Controller als "/api/v2/products" weitergeleitet.
/api/v1/products
/api/v2/products
Überladene URI-Segmente
In diesem Beispiel ist "1" eine Bestellnummer, aber "ausstehend" entspricht einer Sammlung.
/orders/1
/orders/pending
Mehrere Parametertypen
In diesem Beispiel ist "1" eine Bestellnummer, aber "2013/06/16" gibt ein Datum an.
/orders/1
/orders/2013/06/16
Aktivieren des Attributroutings
Rufen Sie mapHttpAttributeRoutes während der Konfiguration auf, um das Attributrouting zu aktivieren. Diese Erweiterungsmethode wird in der System.Web.Http.HttpConfigurationExtensions-Klasse definiert.
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
Attributrouting kann mit konventionsbasiertem Routing kombiniert werden. Rufen Sie die MapHttpRoute-Methode auf, um konventionsbasierte Routen zu definieren.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Weitere Informationen zum Konfigurieren der Web-API finden Sie unter Konfigurieren ASP.NET Web-API 2.
Hinweis: Migrieren von Web-API 1
Vor Web-API 2 generierten Web-API-Projektvorlagen code wie folgt:
protected void Application_Start()
{
// WARNING - Not compatible with attribute routing.
WebApiConfig.Register(GlobalConfiguration.Configuration);
}
Wenn das Attributrouting aktiviert ist, löst dieser Code eine Ausnahme aus. Wenn Sie ein vorhandenes Web-API-Projekt aktualisieren, um das Attributrouting zu verwenden, müssen Sie diesen Konfigurationscode wie folgt aktualisieren:
protected void Application_Start()
{
// Pass a delegate to the Configure method.
GlobalConfiguration.Configure(WebApiConfig.Register);
}
Hinweis
Weitere Informationen finden Sie unter Konfigurieren der Web-API mit ASP.NET Hosting.
Hinzufügen von Routenattributen
Nachfolgend sehen Sie ein Beispiel für eine Route, die mithilfe eines Attributs definiert wurde:
public class OrdersController : ApiController
{
[Route("customers/{customerId}/orders")]
[HttpGet]
public IEnumerable<Order> FindOrdersByCustomer(int customerId) { ... }
}
Die Zeichenfolge "customers/{customerId}/orders" ist die URI-Vorlage für die Route. Die Web-API versucht, den Anforderungs-URI mit der Vorlage abzugleichen. In diesem Beispiel sind "Customers" und "orders" Literalsegmente, und "{customerId}" ist ein variabler Parameter. Die folgenden URIs entsprechen dieser Vorlage:
http://localhost/customers/1/ordershttp://localhost/customers/bob/ordershttp://localhost/customers/1234-5678/orders
Sie können den Abgleich mithilfe von Einschränkungen einschränken, die weiter unten in diesem Thema beschrieben werden.
Beachten Sie, dass der Parameter "{customerId}" in der Routenvorlage mit dem Namen des customerId-Parameters in der Methode übereinstimmt. Wenn die Web-API die Controlleraktion aufruft, versucht sie, die Routenparameter zu binden. Wenn der URI beispielsweise lautet http://example.com/customers/1/orders, versucht die Web-API, den Wert "1" an den Parameter "customerId " in der Aktion zu binden.
Eine URI-Vorlage kann mehrere Parameter aufweisen:
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
Alle Controllermethoden, die nicht über ein Routenattribute verfügen, verwenden konventionsbasiertes Routing. Auf diese Weise können Sie beide Arten von Routing im selben Projekt kombinieren.
HTTP-Methoden
Die Web-API wählt auch Aktionen basierend auf der HTTP-Methode der Anforderung (GET, POST usw.) aus. Standardmäßig sucht die Web-API nach einer übereinstimmung unabhängig von der Groß-/Kleinschreibung mit dem Beginn des Namens der Controllermethode. Eine Controllermethode mit dem Namen PutCustomers entspricht beispielsweise einer HTTP PUT-Anforderung.
Sie können diese Konvention außer Kraft setzen, indem Sie die Methode mit einem der folgenden Attribute versehen:
- [HttpDelete]
- [HttpGet]
- [HttpHead]
- [HttpOptions]
- [HttpPatch]
- [HttpPost]
- [HttpPut]
Im folgenden Beispiel ordnet die Web-API die CreateBook-Methode HTTP POST-Anforderungen zu.
[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }
Verwenden Sie für alle anderen HTTP-Methoden, einschließlich nicht standardmäßiger Methoden, das AcceptVerbs-Attribut , das eine Liste von HTTP-Methoden verwendet.
// WebDAV method
[Route("api/books")]
[AcceptVerbs("MKCOL")]
public void MakeCollection() { }
Routenpräfixe
Häufig beginnen die Routen in einem Controller alle mit demselben Präfix. Beispiel:
public class BooksController : ApiController
{
[Route("api/books")]
public IEnumerable<Book> GetBooks() { ... }
[Route("api/books/{id:int}")]
public Book GetBook(int id) { ... }
[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }
}
Sie können ein allgemeines Präfix für einen gesamten Controller festlegen, indem Sie das Attribut [RoutePrefix] verwenden:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/books
[Route("")]
public IEnumerable<Book> Get() { ... }
// GET api/books/5
[Route("{id:int}")]
public Book Get(int id) { ... }
// POST api/books
[Route("")]
public HttpResponseMessage Post(Book book) { ... }
}
Verwenden Sie eine Tilde (~) für das Methodenattribute, um das Routenpräfix außer Kraft zu setzen:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET /api/authors/1/books
[Route("~/api/authors/{authorId:int}/books")]
public IEnumerable<Book> GetByAuthor(int authorId) { ... }
// ...
}
Das Routenpräfix kann Parameter enthalten:
[RoutePrefix("customers/{customerId}")]
public class OrdersController : ApiController
{
// GET customers/1/orders
[Route("orders")]
public IEnumerable<Order> Get(int customerId) { ... }
}
Routeneinschränkungen
Mit Routeneinschränkungen können Sie einschränken, wie die Parameter in der Routenvorlage übereinstimmen. Die allgemeine Syntax lautet "{parameter:constraint}". Beispiel:
[Route("users/{id:int}")]
public User GetUserById(int id) { ... }
[Route("users/{name}")]
public User GetUserByName(string name) { ... }
Hier wird die erste Route nur ausgewählt, wenn das Segment "id" des URI eine ganze Zahl ist. Andernfalls wird die zweite Route ausgewählt.
In der folgenden Tabelle sind die einschränkungen aufgeführt, die unterstützt werden.
| Constraint | Beschreibung | Beispiel |
|---|---|---|
| Alpha | Entspricht Buchstaben des lateinischen Alphabets in Groß- oder Kleinschreibung (a-z, A-Z) | {x:alpha} |
| bool | Entspricht einem booleschen Wert. | {x:bool} |
| Datum/Uhrzeit | Entspricht einem DateTime-Wert . | {x:datetime} |
| Dezimalzahl | Entspricht einem Dezimalwert. | {x:decimal} |
| doppelt | Entspricht einem 64-Bit-Gleitkommawert. | {x:double} |
| Schweben | Entspricht einem 32-Bit-Gleitkommawert. | {x:float} |
| GUID (Globally Unique Identifier) | Entspricht einem GUID-Wert. | {x:guid} |
| INT | Entspricht einem ganzzahligen 32-Bit-Wert. | {x:int} |
| length | Entspricht einer Zeichenfolge mit der angegebenen Länge oder innerhalb eines angegebenen Längenbereichs. | {x:length(6)} {x:length(1,20)} |
| lang | Entspricht einem ganzzahligen 64-Bit-Wert. | {x:long} |
| max | Gleicht eine ganze Zahl mit einem Maximalwert ab. | {x:max(10)} |
| maxlength | Entspricht einer Zeichenfolge mit einer maximalen Länge. | {x:maxlength(10)} |
| min | Gleicht eine ganze Zahl mit einem Minimalwert ab. | {x:min(10)} |
| minlength | Entspricht einer Zeichenfolge mit einer Mindestlänge. | {x:minlength(10)} |
| range | Gleicht eine ganze Zahl innerhalb eines Wertebereichs ab. | {x:range(10,50)} |
| Regex | Entspricht einem regulären Ausdruck. | {x:regex(^\d{3}-\d{3}-\d{4}$)} |
Beachten Sie, dass einige der Einschränkungen, z. B. "min", Argumente in Klammern verwenden. Sie können mehrere Einschränkungen auf einen Parameter anwenden, getrennt durch einen Doppelpunkt.
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { ... }
Benutzerdefinierte Routeneinschränkungen
Sie können benutzerdefinierte Routeneinschränkungen erstellen, indem Sie die IHttpRouteConstraint-Schnittstelle implementieren. Die folgende Einschränkung schränkt beispielsweise einen Parameter auf einen Ganzzahlwert ungleich Null ein.
public class NonZeroConstraint : IHttpRouteConstraint
{
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
long longValue;
if (value is long)
{
longValue = (long)value;
return longValue != 0;
}
string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
if (Int64.TryParse(valueString, NumberStyles.Integer,
CultureInfo.InvariantCulture, out longValue))
{
return longValue != 0;
}
}
return false;
}
}
Der folgende Code zeigt, wie die Einschränkung registriert wird:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
}
}
Jetzt können Sie die Einschränkung in Ihren Routen anwenden:
[Route("{id:nonzero}")]
public HttpResponseMessage GetNonZero(int id) { ... }
Sie können auch die gesamte DefaultInlineConstraintResolver-Klasse ersetzen, indem Sie die IInlineConstraintResolver-Schnittstelle implementieren . Dadurch werden alle integrierten Einschränkungen ersetzt, es sei denn, Ihre Implementierung von IInlineConstraintResolver fügt sie speziell hinzu.
Optionale URI-Parameter und Standardwerte
Sie können einen URI-Parameter optional machen, indem Sie dem Routenparameter ein Fragezeichen hinzufügen. Wenn ein Routenparameter optional ist, müssen Sie einen Standardwert für den Methodenparameter definieren.
public class BooksController : ApiController
{
[Route("api/books/locale/{lcid:int?}")]
public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
}
Geben Sie in diesem Beispiel /api/books/locale/1033/api/books/locale dieselbe Ressource zurück.
Alternativ können Sie einen Standardwert in der Routenvorlage wie folgt angeben:
public class BooksController : ApiController
{
[Route("api/books/locale/{lcid:int=1033}")]
public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }
}
Dies ist fast identisch mit dem vorherigen Beispiel, aber es gibt einen leichten Unterschied des Verhaltens, wenn der Standardwert angewendet wird.
- Im ersten Beispiel ("{lcid:int?}") wird der Standardwert 1033 direkt dem Methodenparameter zugewiesen, sodass der Parameter diesen exakten Wert aufweist.
- Im zweiten Beispiel ("{lcid:int=1033}") durchläuft der Standardwert "1033" den Modellbindungsprozess. Der Standardmodellordner konvertiert "1033" in den numerischen Wert 1033. Sie können jedoch einen benutzerdefinierten Modellbinder verwenden, der möglicherweise eine andere Funktion erfüllt.
(In den meisten Fällen sind die beiden Formulare gleichwertig, es sei denn, Sie verfügen über benutzerdefinierte Modellordner in Ihrer Pipeline.)
Routennamen
In der Web-API hat jede Route einen Namen. Routennamen sind nützlich zum Generieren von Links, sodass Sie einen Link in eine HTTP-Antwort einfügen können.
Um den Routennamen anzugeben, legen Sie die Name-Eigenschaft für das Attribut fest. Im folgenden Beispiel wird gezeigt, wie der Routenname festgelegt wird und wie der Routenname beim Generieren einer Verknüpfung verwendet wird.
public class BooksController : ApiController
{
[Route("api/books/{id}", Name="GetBookById")]
public BookDto GetBook(int id)
{
// Implementation not shown...
}
[Route("api/books")]
public HttpResponseMessage Post(Book book)
{
// Validate and add book to database (not shown)
var response = Request.CreateResponse(HttpStatusCode.Created);
// Generate a link to the new book and set the Location header in the response.
string uri = Url.Link("GetBookById", new { id = book.BookId });
response.Headers.Location = new Uri(uri);
return response;
}
}
Routenreihenfolge
Wenn das Framework versucht, einen URI mit einer Route abzugleichen, wertet es die Routen in einer bestimmten Reihenfolge aus. Um die Reihenfolge anzugeben, legen Sie die Order-Eigenschaft für das Route-Attribut fest. Niedrigere Werte werden zuerst ausgewertet. Der Standardwert der Reihenfolge ist Null.
Hier erfahren Sie, wie die Gesamtbestellung bestimmt wird:
Vergleichen Sie die Order-Eigenschaft des Route-Attributs.
Sehen Sie sich die einzelnen URI-Segmente in der Routenvorlage an. Für jedes Segment wie folgt anordnen:
- Literalsegmente.
- Routenparameter mit Einschränkungen.
- Routenparameter ohne Einschränkungen.
- Wildcard-Parametersegmente mit Einschränkungen.
- Segmente von Wildcardparametern ohne Einschränkungen.
Im Falle eines Gleichstands werden Routen durch einen fallunabhängigen Zeichenfolgen-Ordnung-Vergleich (OrdinalIgnoreCase) der Routenvorlagen sortiert.
Beispiel: Angenommen, Sie definieren den folgenden Controller:
[RoutePrefix("orders")]
public class OrdersController : ApiController
{
[Route("{id:int}")] // constrained parameter
public HttpResponseMessage Get(int id) { ... }
[Route("details")] // literal
public HttpResponseMessage GetDetails() { ... }
[Route("pending", RouteOrder = 1)]
public HttpResponseMessage GetPending() { ... }
[Route("{customerName}")] // unconstrained parameter
public HttpResponseMessage GetByCustomer(string customerName) { ... }
[Route("{*date:datetime}")] // wildcard
public HttpResponseMessage Get(DateTime date) { ... }
}
Diese Routen werden wie folgt sortiert.
- Bestellungen/Details
- Bestellungen/{id}
- bestellungen/{customerName}
- bestellungen/{*date}
- bestellungen/ausstehend
Beachten Sie, dass "Details" ein Literalsegment ist und vor "{id}" angezeigt wird, aber "ausstehend" wird zuletzt angezeigt, weil die Order-Eigenschaft 1 ist. (In diesem Beispiel wird davon ausgegangen, dass es keine Kunden mit dem Namen "Details" oder "ausstehend" gibt. Versuchen Sie im Allgemeinen, mehrdeutige Routen zu vermeiden. In diesem Beispiel ist eine bessere Routenvorlage für GetByCustomer "customers/{customerName}" )