Routing zu Controlleraktionen in ASP.NET Core
Von Ryan Nowak, Kirk Larkin und Rick Anderson
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
ASP.NET Core-Controller verwenden Routing-Middleware, um die URLs der eingehenden Anforderungen abzugleichen und sie Aktionen zuzuordnen. Routenvorlagen:
- Werden beim Start in
Program.cs
oder in Attributen definiert - Beschreiben, wie URL-Pfade mit Aktionen abgeglichen werden
- Werden verwendet, um URLs für Links zu generieren Die generierten Links werden in der Regel in Antworten zurückgegeben
Aktionen werden entweder herkömmlich oder über Attribute zugeordnet. Das heißt, dass eine Route auf dem Controller oder der Aktion platziert wird. Weitere Informationen finden Sie im Abschnitt Gemischtes Routing.
Dieses Dokument hat folgende Eigenschaften:
- Die Interaktionen zwischen MVC und Routing werden erläutert:
- Es wird erklärt, wie typische MVC-Apps die Routingfeatures nutzen.
- Diese beiden Themen werden behandelt:
- Herkömmliches Routing, das in der Regel mit Controllern und Ansichten verwendet wird.
- Attributrouting, das mit REST-APIs verwendet wird. Wenn Sie sich hauptsächlich für das Routing für REST-APIs interessieren, wechseln Sie zum Abschnitt Attributrouting für REST-APIs.
- Informationen zum erweiterten Routing finden Sie unter Routing.
- Das Standardroutingsystem, das als Endpunktrouting bezeichnet wird, wird erläutert. Aus Kompatibilitätsgründen ist es möglich, Controller mit der vorherigen Version des Routings zu verwenden. Anweisungen finden Sie im Migrationsleitfaden 2.2–3.0.
Einrichten einer herkömmlichen Route
Die ASP.NET Core MVC-Vorlage generiert einen herkömmlichen Routingcode, der dem folgenden ähnelt:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
MapControllerRoute wird verwendet, um eine einzelne Route zu erstellen. Die einzelne Route heißt default
-Route. Die meisten Apps mit Controllern und Ansichten verwenden eine Routenvorlage, die der default
-Route ähnelt. REST-APIs sollten Attributrouting verwenden.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Die Routenvorlage "{controller=Home}/{action=Index}/{id?}"
:
Entspricht einem URL-Pfad wie
/Products/Details/5
.Extrahiert die Routenwerte
{ controller = Products, action = Details, id = 5 }
durch Tokenisieren des Pfads. Die Extraktion von Routenwerten führt zu einer Übereinstimmung, wenn die App über einen Controller namensProductsController
und eineDetails
-Aktion verfügt:public class ProductsController : Controller { public IActionResult Details(int id) { return ControllerContext.MyDisplayRouteInfo(id); } }
MyDisplayRouteInfo wird von dem NuGet-Paket Rick.Docs.Samples.RouteInfo bereitgestellt und zeigt Routeninformationen an.
Das
/Products/Details/5
-Modell bindet den Wert vonid = 5
, um denid
-Parameter auf5
festzulegen. Weitere Informationen finden Sie unter Modellbindung.{controller=Home}
definiertHome
als Standard-controller
.{action=Index}
definiertIndex
als Standard-action
.Das
?
-Zeichen in{id?}
definiertid
als optional.- Standardmäßige und optionale Routenparameter müssen nicht im URL-Pfad vorhanden sein, damit es eine Übereinstimmung gibt. Eine ausführliche Beschreibung der Syntax der Routenvorlage finden Sie unter Routenvorlagenreferenz.
Es besteht eine Übereinstimmung mit dem URL-Pfad
/
.Die Routenwerte
{ controller = Home, action = Index }
werden erzeugt.
Die Werte für controller
und action
verwenden die Standardwerte. id
erzeugt keine Werte, da kein entsprechendes Segment im URL-Pfad vorhanden ist. /
stimmt nur überein, wenn eine HomeController
- und Index
-Aktion vorhanden ist:
public class HomeController : Controller
{
public IActionResult Index() { ... }
}
Mit der vorherigen Controllerdefinition und der Routenvorlage wird die HomeController.Index
-Aktion für die folgenden URL-Pfade ausgeführt:
/Home/Index/17
/Home/Index
/Home
/
Der URL-Pfad /
verwendet die Home
-Standardcontroller und die Index
-Aktion der Routenvorlage. Der URL-Pfad /Home
verwendet die Index
-Standardaktion der Routenvorlage.
Mit der Hilfsmethode MapDefaultControllerRoute:
app.MapDefaultControllerRoute();
ersetzt Folgendes:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Wichtig
Das Routing wird mithilfe der UseRouting- und UseEndpoints-Middleware konfiguriert. Führen Sie folgende Aktionen durch, um Controller zu verwenden:
- Rufen Sie MapControllers zum Zuordnen von über Attribute zugeordneten Controllern auf.
- Rufen Sie MapControllerRoute oder MapAreaControllerRoute auf, um sowohl herkömmlich als auch über Attribute zugeordnete Controller zuzuordnen.
Apps müssen UseRouting
oder UseEndpoints
normalerweise nicht aufrufen. WebApplicationBuilder konfiguriert eine Middlewarepipeline, die die in Program.cs
hinzugefügte Middleware mit UseRouting
und UseEndpoints
umschließt. Weitere Informationen finden Sie unter Routing in ASP.NET Core.
Herkömmliches Routing
Herkömmliches Routing wird mit Controllern und Ansichten verwendet. Die default
-Route:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Das vorangehende Beispiel ist ein Beispiel für eine herkömmliche Route. Das herkömmliche (auch konventionelle) Routing heißt so, weil dabei eine Konvention für URL-Pfade erstellt wird:
- Das erste Pfadsegment (
{controller=Home}
) entspricht dem Namen des Controllers. - Das zweite Segment (
{action=Index}
) entspricht dem Namen der Aktion. - Das dritte Segment (
{id?}
) wird für eine optionaleid
verwendet. Die?
in{id?}
macht es optional.id
wird für die Zuordnung zu einer Modellentität verwendet.
Bei Verwendung dieser default
-Route gilt der URL-Pfad:
/Products/List
wird derProductsController.List
-Aktion zugeordnet./Blog/Article/17
wirdBlogController.Article
zugeordnet, und das Modell bindet denid
-Parameter üblicherweise an 17.
Für diese Zuordnung gilt Folgendes:
- Sie basiert nur auf den Controller- und Aktionsnamen.
- Sie basiert nicht auf Namespaces, Quelldateispeicherorten oder Methodenparametern.
Die Kombination aus herkömmlichem Routing und Standardrouting ermöglicht es Ihnen, die App zu erstellen, ohne für jede Aktion ein neues URL-Muster entwerfen zu müssen. Für eine App mit Aktionen im CRUD-Stil bewirkt die Konsistenz für die URLs zwischen Controllern Folgendes:
- Sie vereinfacht den Code.
- Sie macht die Benutzeroberfläche vorhersagbarer.
Warnung
Die id
im vorherigen Code wird von der Routenvorlage als optional definiert. Aktionen können ohne die optionale ID ausgeführt werden, die als Teil der URL angegeben wird. Im Allgemeinen gilt Folgendes, wenn id
in der URL nicht angegeben wird:
id
wird durch die Modellbindung auf0
festgelegt.- Es wurde keine Entität in der Datenbank gefunden, die mit
id == 0
übereinstimmt.
Mit dem Attributrouting können Sie präzise steuern, für welche Aktionen die ID erforderlich ist und für welche nicht. Gemäß der Konvention enthält die Dokumentation optionale Parameter wie id
, wenn sie wahrscheinlich in der richtigen Verwendung angezeigt werden.
Für die meisten Apps sollte eine grundlegendes und beschreibendes Routingschema ausgewählt werden, um lesbare und aussagekräftige URLs zu erhalten. Für die konventionelle Standardroute {controller=Home}/{action=Index}/{id?}
gilt:
- Sie unterstützt ein grundlegendes und beschreibendes Routingschema.
- Sie stellt einen nützlichen Startpunkt für benutzeroberflächenbasierte Apps dar.
- Ist die einzige Routenvorlage, die für viele Apps mit Webbenutzeroberfläche benötigt wird. Für größere Apps mit Webbenutzeroberfläche genügt häufig eine andere Route, die Bereiche verwendet.
MapControllerRoute und MapAreaRoute:
- Diese weisen ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden.
Endpunktrouting in ASP.NET Core:
- Weist kein Konzept für Routen auf.
- Bietet keine Reihenfolgegarantie für die Ausführung der Erweiterbarkeit, da alle Endpunkte gleichzeitig verarbeitet werden.
Wenn Sie die Protokollierung aktivieren, erfahren Sie, wie die integrierten Routingimplementierungen (z.B. Route) Zuordnungen für Anforderungen ermitteln.
Das Attributrouting wird weiter unten in diesem Dokument erläutert.
Mehrere herkömmliche Routen
Mehrere herkömmliche Routen können konfiguriert werden, indem weitere Aufrufe für MapControllerRoute und MapAreaControllerRoute hinzugefügt werden. So können Sie mehrere Konventionen definieren oder herkömmlichen Routen hinzufügen, die einer bestimmten Aktion zugeordnet sind, z. B.:
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Die blog
-Route im vorherigen Code ist eine dedizierte herkömmliche Route. Sie wird aus diesen Gründen als dedizierte herkömmliche Route bezeichnet:
- Sie verwendet herkömmliches Routing.
- Sie ist für eine bestimmte Aktion bestimmt.
Da controller
und action
nicht als Parameter in der Routenvorlage "blog/{*article}"
angezeigt werden, gilt Folgendes:
- Sie können nur über die
{ controller = "Blog", action = "Article" }
-Standardwerte verfügen. - Diese Route wird immer der Aktion
BlogController.Article
zugeordnet.
/Blog
, /Blog/Article
und /Blog/{any-string}
sind die einzigen URL-Pfade, die der Blogroute entsprechen.
Für das vorherige Beispiel gilt Folgendes:
- Die
blog
-Route hat eine höhere Priorität für Übereinstimmungen als diedefault
-Route, da sie zuerst hinzugefügt wird. - Es ist ein Beispiel für Slugrouting, bei dem es typisch ist, einen Artikelnamen als Teil der URL zu haben.
Warnung
In ASP.NET Core erfolgt durch das Routing Folgendes nicht:
- Es wird kein Konzept namens Route definiert.
UseRouting
fügt der Middlewarepipeline einen Routenabgleich hinzu. DieseUseRouting
-Middleware prüft die in der App definierten Endpunkte und wählt anhand der Anforderung die beste Endpunktübereinstimmung aus. - Geben Sie Garantien für die Ausführungsreihenfolge von Erweiterungen wie IRouteConstraint oder IActionConstraint an.
Referenzmaterial zum Routing finden Sie unter Routing.
Reihenfolge des herkömmlichen Routings
Herkömmliches Routing passt nur zu einer Kombination aus Aktion und Controller, die von der App definiert wird. Dieser Vorgang soll Szenarios vereinfachen, bei denen sich herkömmliche Routen überschneiden.
Das Hinzufügen von Routen mit MapControllerRoute, MapDefaultControllerRoute und MapAreaControllerRoute weist ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Übereinstimmungen aus einer zuvor angezeigten Route haben eine höhere Priorität. Beim herkömmlichen Routing ist die Reihenfolge wichtig. Routen mit Bereichen werden im Allgemeinen früher aufgeführt als die spezifischeren Routen ohne Bereich. Dedizierte herkömmliche Routen mit Catch-All-Routenparametern wie {*article}
können eine Route zu „gierig“ machen, was bedeutet, dass sie mit URLs übereinstimmt, die eigentlich mit anderen Routen übereinstimmen sollten. Fügen Sie die „gierigen“ Routen später in die Routingtabelle ein, um solche Übereinstimmungen zu verhindern.
Warnung
Ein catch-all-Parameter kann aufgrund eines Fehlers beim Routing nicht ordnungsgemäß mit Routen übereinstimmen. Apps, die von diesem Fehler betroffen sind, weisen die folgenden Merkmale auf:
- Eine catch-all-Route, zum Beispiel
{**slug}"
- Die catch-all-Route kann nicht mit Anforderungen abgeglichen werden, die abgeglichen werden sollen.
- Durch das Entfernen anderer Routen funktioniert die catch-all-Route.
Weitere Beispiele zu diesem Fehler finden Sie in den GitHub-Issues 18677 und 16579.
Eine Opt-in-Behebung für diesen Fehler ist im .NET Core 3.1.301 SDK und höher enthalten. Der folgende Code legt einen internen Switch fest, mit dem dieser Fehler behoben wird:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Auflösen von mehrdeutigen Aktionen
Wenn zwei Endpunkte über das Routing übereinstimmen, muss das Routing eine der folgenden Aktionen ausführen:
- Den besten Kandidaten auswählen
- Löst eine Ausnahme aus.
Beispiel:
public class Products33Controller : Controller
{
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}
Der vorherige Controller definiert zwei Aktionen, die mit Folgendem übereinstimmen:
- Dem URL-Pfad von
/Products33/Edit/17
- Routendaten
{ controller = Products33, action = Edit, id = 17 }
Dies ist ein typisches Muster für MVC-Controller:
Edit(int)
zeigt ein Formular zum Bearbeiten eines Produkts an.Edit(int, Product)
verarbeitet das bereitgestellte Formular.
Diese Bedingungen müssen erfüllt sein, um die richtige Route aufzulösen:
Edit(int, Product)
wird ausgewählt, wenn die Anforderung ein HTTP-POST
ist.Edit(int)
wird ausgewählt, wenn das HTTP-Verb etwas anderes ist.Edit(int)
wird im Allgemeinen überGET
aufgerufen.
Das HttpPostAttribute ([HttpPost]
) wird für das Routing bereitgestellt, sodass es basierend auf der HTTP-Methode der Anforderung eine Auswahl treffen kann. Das HttpPostAttribute
macht Edit(int, Product)
zu einer besseren Übereinstimmung als Edit(int)
.
Es ist wichtig, die Rolle von Attributen wie HttpPostAttribute
zu verstehen. Ähnliche Attribute werden für andere HTTP-Verben definiert. Beim herkömmlichen Routing nutzen Aktionen oft denselben Aktionsnamen, wenn sie Teil eines Formularworkflows sind. Weitere Informationen finden Sie beispielsweise unter Untersuchen der beiden Edit-Aktionsmethoden.
Wenn das Routing keinen geeigneten Kandidaten auswählen kann, wird eine AmbiguousMatchException ausgelöst, in dem die verschiedenen übereinstimmenden Endpunkte aufgelistet werden.
Namen herkömmlicher Routen
Die Zeichenfolgen "blog"
und "default"
in den folgenden Beispielen sind herkömmliche Routennamen:
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Die Routennamen geben der Route einen logischen Namen. Die benannte Route kann bei der URL-Generierung verwendet werden. Durch das Verwenden einer benannten Route wird die URL-Erstellung erheblich vereinfacht, obwohl die Reihenfolge der Routen die URL-Generierung verkomplizieren sollte. Routennamen müssen anwendungsweit eindeutig sein.
Routennamen:
- Haben keine Auswirkungen auf den URL-Abgleich oder die Verarbeitung von Anforderungen
- Werden nur für die URL-Generierung verwendet
Das Routennamenkonzept wird im Routing als IEndpointNameMetadata dargestellt. Für die Begriffe Routenname und Endpunktname gilt Folgendes:
- Sie können austauschbar verwendet werden.
- Welcher in der Dokumentation und im Code verwendet wird, hängt von der beschriebenen API ab.
Attributrouting für REST-APIs
REST-APIs sollten das Attributrouting verwenden, um die Funktionalität der App als eine Gruppe von Ressourcen zu modellieren, bei denen Vorgänge durch HTTP-Verben dargestellt werden.
Beim Attributrouting werden Aktionen mithilfe von Attributen direkt Routenvorlagen zugeordnet. Der folgende Code ist typisch für eine REST-API und wird im nächsten Beispiel verwendet:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Im vorherigen Code wird MapControllers aufgerufen, um über Attribute zugeordnete Controller des Attributs zuzuordnen.
Im folgenden Beispiel:
HomeController
entspricht einer Gruppe von URLs, ähnlich wie die herkömmliche Standardroute{controller=Home}/{action=Index}/{id?}
.
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Die Aktion HomeController.Index
wird für jeden der URL-Pfade /
, /Home
, /Home/Index
oder /Home/Index/3
ausgeführt.
In diesem Beispiel wird ein wichtiger Unterschied beim Programmieren zwischen dem Attributrouting und dem herkömmlichen Routing hervorgehoben. Attributrouting erfordert mehr Eingaben, um eine Route anzugeben. Die herkömmliche Standardroute verarbeitet auch kürzere Routen. Attributrouting ermöglicht jedoch (und erfordert auch) die präzise Kontrolle der Routenvorlagen, die für die einzelnen Aktionen gelten.
Beim Attributrouting haben die Namen des Controllers und der Aktion keinen Einfluss darauf, welche Aktion übereinstimmt, es sei denn, es wird die Tokenersetzung verwendet. Im folgenden Beispiel werden dieselben URLs wie im vorherigen Beispiel verwendet:
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Der folgende Code verwendet die Tokenersetzung für action
und controller
:
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("[controller]/[action]")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Der folgende Code wendet [Route("[controller]/[action]")]
auf den Controller an:
[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Im vorangehenden Code müssen die Index
-Methodenvorlagen den Routenvorlagen /
oder ~/
voranstellen. Routenvorlagen, die auf eine Aktion angewendet werden, die mit einem /
oder ~/
beginnen, können nicht mit Routenvorlagen kombiniert werden, die auf den Controller angewendet werden.
Informationen zur Auswahl von Routenvorlagen finden Sie unter Priorität der Routenvorlage.
Reservierte Routingnamen
Die folgenden Schlüsselwörter sind reservierte Routenparameternamen bei der Verwendung von Controllern oder Razor Pages:
action
area
controller
handler
page
Die Verwendung von page
als Routenparameter mit Attributrouting ist ein häufiger Fehler. Dies führt zu inkonsistentem und verwirrendem Verhalten bei der URL-Generierung.
public class MyDemo2Controller : Controller
{
[Route("/articles/{page}")]
public IActionResult ListArticles(int page)
{
return ControllerContext.MyDisplayRouteInfo(page);
}
}
Die speziellen Parameternamen werden von der URL-Generierung verwendet, um zu bestimmen, ob sich ein URL-Generierungsvorgang auf Razor Pages oder einen Controller bezieht.
Die folgenden Schlüsselwörter sind im Kontext einer Razor-Ansicht oder einer Razor-Seite reserviert:
page
using
namespace
inject
section
inherits
model
addTagHelper
removeTagHelper
Diese Schlüsselwörter sollten nicht für Linkgenerierungen, modellgebundene Parameter oder Eigenschaften der obersten Ebene verwendet werden.
HTTP-Verbvorlagen
ASP.NET Core verfügt über die folgenden HTTP-Verbvorlagen:
Routenvorlagen
ASP.NET Core verfügt über die folgenden Routenvorlagen:
- Alle HTTP-Verbvorlagen sind Routenvorlagen.
- [Route]
Attributrouting mit Http[Verb]-Attributen
Betrachten Sie den folgenden Controller:
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Für den Code oben gilt:
- Jede Aktion enthält das
[HttpGet]
-Attribut, das nur den Abgleich mit HTTP GET-Anforderungen einschränkt. - Die
GetProduct
-Aktion enthält die"{id}"
-Vorlage, daher wirdid
an die"api/[controller]"
-Vorlage auf dem Controller angehängt. Die Methodenvorlage ist"api/[controller]/{id}"
. Daher passt diese Aktion nur auf GET-Anforderungen für das Formular/api/test2/xyz
,/api/test2/123
,/api/test2/{any string}
, usw.[HttpGet("{id}")] // GET /api/test2/xyz public IActionResult GetProduct(string id) { return ControllerContext.MyDisplayRouteInfo(id); }
- Die
GetIntProduct
-Aktion enthält die"int/{id:int}"
-Vorlage. Der:int
-Teil der Vorlage beschränkt dieid
-Routenwerte auf Zeichenfolgen, die in eine ganze Zahl konvertiert werden können. Für eine GET-Anforderung an/api/test2/int/abc
gilt:- Sie entspricht dieser Aktion nicht.
- Sie gibt den Fehler 404 Nicht gefunden zurück.
[HttpGet("int/{id:int}")] // GET /api/test2/int/3 public IActionResult GetIntProduct(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
- Die
GetInt2Product
-Aktion enthält{id}
in der Vorlage, beschränktid
aber nicht auf die Werte, die in eine ganze Zahl konvertiert werden können. Für eine GET-Anforderung an/api/test2/int2/abc
gilt:- Sie entspricht dieser Route.
- Die Modellbindung kann
abc
nicht in eine ganze Zahl konvertieren. Derid
-Parameter der Methode ist eine ganze Zahl. - Sie gibt 400 Ungültige Anforderung zurück, da die Modellbindung
abc
nicht in eine ganze Zahl konvertieren konnte.[HttpGet("int2/{id}")] // GET /api/test2/int2/3 public IActionResult GetInt2Product(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
Beim Attributrouting können HttpMethodAttribute-Attribute wie HttpPostAttribute, HttpPutAttribute und HttpDeleteAttribute verwendet werden. Alle HTTP-Verbattribute akzeptieren eine Routenvorlage. Im folgenden Beispiel werden zwei Aktionen gezeigt, die derselben Routenvorlage zugeordnet sind:
[ApiController]
public class MyProductsController : ControllerBase
{
[HttpGet("/products3")]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpPost("/products3")]
public IActionResult CreateProduct(MyProduct myProduct)
{
return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
}
}
Für die Verwendung des URL-Pfads /products3
gilt:
- Die
MyProductsController.ListProducts
-Aktion wird ausgeführt, wenn das HTTP-VerbGET
ist. - Die
MyProductsController.CreateProduct
-Aktion wird ausgeführt, wenn das HTTP-VerbPOST
ist.
Beim Erstellen einer REST-API wird [Route(...)]
selten für eine Aktionsmethode verwendet, die Aktion alle HTTP-Methoden akzeptiert. Es ist besser, das spezifischere HTTP-Verbattribut zu nutzen, um präzise anzugeben, was Ihre API unterstützt. REST-API-Clients sollten wissen, welche Pfade und HTTP-Verben bestimmten logischen Operationen entsprechen.
REST-APIs sollten das Attributrouting verwenden, um die Funktionalität der App als eine Gruppe von Ressourcen zu modellieren, bei denen Vorgänge durch HTTP-Verben dargestellt werden. Dies bedeutet, dass viele Vorgänge, z. B. GET und POST, für dieselbe logische Ressource dieselbe URL verwenden. Das Attributrouting bietet eine Ebene der Steuerung, die für einen sorgfältigen Entwurf des öffentlichen Endpunktlayouts einer API erforderlich ist.
Da eine Attributroute für eine bestimmte Aktion gilt, ist es einfach, Parameter als Teil der Routenvorlagendefinition erforderlich festzulegen. Im folgenden Beispiel ist id
als Teil des URL-Pfads erforderlich.
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Für die Products2ApiController.GetProduct(int)
-Aktion gilt:
- Sie wird mit einem URL-Pfad wie
/products2/3
ausgeführt. - Sie wird nicht mit einem URL-Pfad wie
/products2
ausgeführt.
Das [Consumes]-Attribut ermöglicht es einer Aktion, die unterstützten Anforderungsinhaltstypen einzuschränken. Weitere Informationen finden Sie unter Definieren unterstützter Anforderungsinhaltstypen mit dem [Consumes]-Attribut.
Eine vollständige Beschreibung und Routenvorlagen und dazugehörige Optionen finden Sie unter Routing in ASP.NET Core.
Weitere Informationen zu [ApiController]
finden Sie unter ApiController-Attribut.
Routenname
Im folgenden Code wird ein Routenname von Products_List
definiert:
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Routennamen können verwendet werden, um basierend auf einer bestimmten Route eine URL zu generieren. Routennamen:
- Sie haben keine Auswirkungen auf das URL-Abgleichsverhalten des Routings.
- Sie werden nur für die URL-Generierung verwendet.
Routennamen müssen anwendungsweit eindeutig sein.
Vergleichen Sie den vorherigen Code mit der herkömmlichen Standardroute, die den id
-Parameter als optional definiert ({id?}
). APIs präzise angeben zu können, hat Vorteile, z. B. können /products
und /products/5
an unterschiedliche Aktionen gesendet werden.
Kombinieren von Attributrouten
Um Attributrouting weniger repetitiv zu gestalten, werden Routenattribute auf dem Controller mit Routenattributen auf den einzelnen Aktionen kombiniert. Alle auf dem Controller definierten Routenvorlagen werden den Routenvorlagen auf den Aktionen vorangestellt. Wenn Routenattribute auf dem Controller platziert werden, verwenden alle Aktionen im Controller Attributrouting.
[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
[HttpGet]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Im vorherigen Beispiel:
- Der URL-Pfad
/products
kann mitProductsApi.ListProducts
übereinstimmen. - Der URL-Pfad
/products/5
kann mitProductsApi.GetProduct(int)
übereinstimmen.
Beide Aktionen gleichen nur „HTTP GET
“ ab, weil sie mit dem [HttpGet]
-Attribut markiert sind.
Routenvorlagen, die auf eine Aktion angewendet werden, die mit einem /
oder ~/
beginnen, können nicht mit Routenvorlagen kombiniert werden, die auf den Controller angewendet werden. Im folgenden Beispiel werden mehrere URL-Pfade zugeordnet, die der Standardroute ähneln.
[Route("Home")]
public class HomeController : Controller
{
[Route("")]
[Route("Index")]
[Route("/")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("About")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
In der folgenden Tabelle werden die [Route]
-Attribute im vorherigen Code erläutert:
attribute | Kann mit [Route("Home")] kombiniert werden |
Definiert die Routenvorlage |
---|---|---|
[Route("")] |
Ja | "Home" |
[Route("Index")] |
Ja | "Home/Index" |
[Route("/")] |
Nein | "" |
[Route("About")] |
Ja | "Home/About" |
Reihenfolge der Attributrouten
Beim Routing wird eine Struktur erstellt, die alle Endpunkte gleichzeitig abgleicht:
- Der Vorgang wird also ausgeführt, als ob die Routeneinträge in der idealen Reihenfolge platziert worden wären.
- Die spezifischsten Routen können also vor den allgemeineren ausgeführt werden.
Eine Attributroute wie blog/search/{topic}
ist beispielsweise spezifischer als eine Attributroute wie blog/{*article}
. Die blog/search/{topic}
-Route hat standardmäßig eine höhere Priorität, da sie spezifischer ist. Beim herkömmlichen Routing ist der*die Entwickler*in verantwortlich dafür, die Routen in die gewünschte Reihenfolge zu bringen.
Attributrouten können die Reihenfolge mit der Eigenschaft Order konfigurieren. Alle vom Framework bereitgestellten Routenattribute enthalten Order
. Routen werden entsprechend einer aufsteigenden Reihenfolge der Order
-Eigenschaft verarbeitet. Die Standardreihenfolge ist 0
. Das Festlegen einer Route mit Order = -1
wird vor Routen ausgeführt, die keine Reihenfolge vorgeben. Das Festlegen einer Route mit Order = 1
wird nach der Standardreihenfolge der Routen ausgeführt.
Vermeiden Sie eine Abhängigkeit von Order
. Wenn der URL-Raum einer App explizite Reihenfolgenwerte erfordert, um korrekt weiterzuleiten, ist es wahrscheinlich auch für Clients verwirrend. Beim Attributrouting wird im Allgemeinen mithilfe der URL-Zuordnung die richtige Route ausgewählt. Wenn die für die URL-Generierung verwendete Standardreihenfolge nicht funktioniert, ist es meist einfacher, Routennamen als Außerkraftsetzung zu verwenden, statt die Order
-Eigenschaft anzuwenden.
Betrachten Sie die folgenden beiden Controller, die beide den Routenabgleich /home
definieren:
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Beim Anfordern von /home
mit dem vorherigen Code wird eine Ausnahme ausgelöst, die der folgenden ähnelt:
AmbiguousMatchException: The request matched multiple endpoints. Matches:
WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex
Das Hinzufügen von Order
zu einem der Routenattribute löst die Mehrdeutigkeit auf:
[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
return ControllerContext.MyDisplayRouteInfo();
}
Mit dem vorherigen Code führt /home
den HomeController.Index
-Endpunkt aus. Um zu MyDemoController.MyIndex
zu gelangen, fordern Sie /home/MyIndex
. Hinweis:
- Der vorangehende Code ist ein Beispiel für ein schlechtes Routingdesign. Es wurde verwendet, um die Eigenschaft
Order
zu veranschaulichen. - Die
Order
-Eigenschaft löst nur die Mehrdeutigkeit auf. Diese Vorlage kann nicht zugeordnet werden. Es wäre besser, die[Route("Home")]
-Vorlage zu entfernen.
Unter Razor Pages: Routen- und App-Konventionen: Routenreihenfolge finden Sie weitere Informationen zur Routenreihenfolge mit Razor Pages.
In einigen Fällen wird ein HTTP 500-Fehler mit mehrdeutigen Routen zurückgegeben. Verwenden Sie die Protokollierung, um zu ermitteln, welche Endpunkte die AmbiguousMatchException
verursacht haben.
Ersetzen von Token in Routenvorlagen ([controller], [action] [area])
Der Einfachheit halber unterstützen Attributrouten die Tokenersetzung, indem ein Token in eckige Klammern eingeschlossen wird ([
, ]
). Die Token [action]
, [area]
und [controller]
werden durch die Werte der Aktionsnamen, den Namen des Bereichs und des Controllers der Aktion ersetzt, in dem die Route definiert ist:
[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Für den Code oben gilt:
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
- Stimmt mit
/Products0/List
überein
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
- Stimmt mit
/Products0/Edit/{id}
überein
Die Tokenersetzung tritt im letzten Schritt der Erstellung von Attributrouten auf. Der folgende Code verhält sich genauso wie der aus dem obigen Beispiel:
public class Products20Controller : Controller
{
[HttpGet("[controller]/[action]")] // Matches '/Products20/List'
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("[controller]/[action]/{id}")] // Matches '/Products20/Edit/{id}'
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Wenn Sie dies in einer anderen Sprache als Englisch lesen, informieren Sie uns in diesem GitHub-Issue, wenn Sie die Codekommentare in der eigenen Sprache sehen möchten.
Attributrouten können auch mit Vererbung kombiniert werden. Dies ist effektiv in Kombination mit der Tokenersetzung. Tokenersetzung gilt auch für Routennamen, die durch Attributrouten definiert werden.
[Route("[controller]/[action]", Name="[controller]_[action]")]
generiert für jede Aktion einen eindeutigen Routennamen.
[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}
public class Products11Controller : MyBase2Controller
{
[HttpGet] // /api/products11/list
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Damit das Trennzeichen [
oder ]
der Tokenersetzungs-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein ([[
oder ]]
), was einem Escapezeichen entspricht.
Verwenden eines Parametertransformators zum Anpassen der Tokenersetzung
Die Tokenersetzung kann mit einem Parametertransformator angepasst werden. Ein Parametertransformator implementiert IOutboundParameterTransformer und wandelt den Wert der Parameter um. Beispielsweise ändert ein benutzerdefinierter SlugifyParameterTransformer
-Parametertransformator den Routenwert SubscriptionManagement
in subscription-management
:
using System.Text.RegularExpressions;
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value == null) { return null; }
return Regex.Replace(value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
Die RouteTokenTransformerConvention ist eine Anwendungsmodellkonvention, die Folgendes ausführt:
- Wendet einen angegebenen Parametertransformator auf alle Attributrouten in der App an.
- Passt die Tokenwerte für Attributrouten bei ihrer Ersetzung an.
public class SubscriptionManagementController : Controller
{
[HttpGet("[controller]/[action]")]
public IActionResult ListAll()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die vorherige ListAll
-Method stimmt mit /subscription-management/list-all
überein.
Die RouteTokenTransformerConvention
wird als Option registriert:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Die Definition von Slug finden Sie in der MDN-Webdokumentation zu Slug.
Warnung
Übergeben Sie ein Timeout, wenn Sie System.Text.RegularExpressions zum Verarbeiten nicht vertrauenswürdiger Eingaben verwenden. Ein böswilliger Benutzer kann Eingaben für RegularExpressions
angeben, um einen Denial-of-Service-Angriff durchzuführen. ASP.NET Core-Framework-APIs, die RegularExpressions
verwenden, übergeben ein Timeout.
Mehrere Attributrouten
Attributrouting unterstützt das Definieren mehrerer Routen, die zu derselben Aktion führen. Dies wird am häufigsten beim Imitieren des Verhaltens der herkömmlichen Standardroute verwendet. Im folgenden Beispiel wird dies gezeigt:
[Route("[controller]")]
public class Products13Controller : Controller
{
[Route("")] // Matches 'Products13'
[Route("Index")] // Matches 'Products13/Index'
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
Wenn mehrere Routenattribute auf dem Controller platziert werden, bedeutet dies, dass jedes Attribut mit den Routenattributen der Aktionsmethoden kombiniert wird:
[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
[HttpPost("Buy")] // Matches 'Products6/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products6/Checkout' and 'Store/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Alle Routeneinschränkungen von HTTP-Verben implementieren IActionConstraint
.
Wenn mehrere Routenattribute, die IActionConstraint implementieren, einer Aktion zugeordnet werden, geschieht Folgendes:
- Jede Aktionseinschränkung wird mit der Routenvorlage kombiniert, die auf den Controller angewendet wird.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
[HttpPut("Buy")] // Matches PUT 'api/Products7/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products7/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die Verwendung mehrerer Routen für Aktionen mag zwar nützlich und leistungsfähig erscheinen, es ist jedoch besser, den URL-Bereich Ihrer App einfach und klar definiert zu halten. Verwenden Sie nur mehrere Routen für Aktionen, wenn dies notwendig ist, z. B. um vorhandene Clients zu unterstützen.
Angeben von optionalen Attributroutenparametern, Standardwerten und Einschränkungen
Attributrouten unterstützen dieselbe Inline-Syntax wie herkömmliche Routen, um optionale Parameter, Standardwerte und Einschränkungen anzugeben.
public class Products14Controller : Controller
{
[HttpPost("product14/{id:int}")]
public IActionResult ShowProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
[HttpPost("product14/{id:int}")]
wendet im vorherigen Code eine Routeneinschränkung an. Die Products14Controller.ShowProduct
-Aktion wird nur mit URL-Pfaden wie /product14/3
abgeglichen. Der Routenvorlagenteil {id:int}
schränkt dieses Segment auf ganze Zahlen ein.
Eine ausführliche Beschreibung der Syntax der Routenvorlage finden Sie unter Routenvorlagenreferenz.
Benutzerdefinierte Routenattribute, die IRouteTemplateProvider verwenden
Alle Routenattribute implementieren IRouteTemplateProvider. Für die ASP.NET Core-Runtime gilt:
- Sie sucht nach Attributen für Controllerklassen und Aktionsmethoden, wenn die App gestartet wird.
- Sie verwendet die Attribute, die
IRouteTemplateProvider
implementieren, um die erste Gruppe der Routen zu erstellen.
Implementieren Sie IRouteTemplateProvider
, um benutzerdefinierte Routenattribute zu definieren. Jeder IRouteTemplateProvider
lässt Sie eine einzelne Route mit einer benutzerdefinierten Routenvorlage, Reihenfolge und einem benutzerdefinierten Namen definieren:
public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
public string Template => "api/[controller]";
public int? Order => 2;
public string Name { get; set; } = string.Empty;
}
[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die vorhergehende Get
-Methode gibt Order = 2, Template = api/MyTestApi
zurück.
Anpassen von Attributrouten mithilfe des Anwendungsmodells
Das Anwendungsmodell:
- Ist ein Objektmodell, das beim Start in
Program.cs
erstellt wurde - Enthält alle Metadaten, die von ASP.NET Core zum Weiterleiten und Ausführen der Aktionen in einer App verwendet werden
Das Anwendungsmodell enthält alle Daten, die aus Routenattributen erfasst wurden. Die Daten aus Routenattributen werden von der IRouteTemplateProvider
-Implementierung bereitgestellt. Konventionen:
- Können geschrieben werden, um das Anwendungsmodell zu ändern und das Routingverhalten anzupassen
- Werden beim Starten der App gelesen
Dieser Abschnitt enthält ein einfaches Beispiel für das Anpassen des Routings mithilfe des Anwendungsmodells. Der folgende Code sorgt dafür, dass die Routen in etwa mit der Ordnerstruktur des Projekts übereinstimmen.
public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
private readonly string _baseNamespace;
public NamespaceRoutingConvention(string baseNamespace)
{
_baseNamespace = baseNamespace;
}
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
var namespc = controller.ControllerType.Namespace;
if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]/[action]/{id?}");
foreach (var selector in controller.Selectors)
{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}
Der folgende Code verhindert, dass die namespace
-Konvention auf Controller angewendet wird, die über Attribute zugeordnet werden:
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
Der folgende Controller verwendet beispielsweise NamespaceRoutingConvention
nicht:
[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
// /managers/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
return Content($"Index- template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
Die NamespaceRoutingConvention.Apply
-Methode:
- Wird nicht ausgeführt, wenn der Controller mit einem Attribut weitergeleitet wird
- Legt die Controllervorlage basierend auf dem
namespace
fest, wobei die Basisnamespace
entfernt wurde
Die NamespaceRoutingConvention
kann in Program.cs
angewendet werden:
using My.Application.Controllers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});
var app = builder.Build();
Betrachten Sie beispielsweise den folgenden Controller:
using Microsoft.AspNetCore.Mvc;
namespace My.Application.Admin.Controllers
{
public class UsersController : Controller
{
// GET /admin/controllers/users/index
public IActionResult Index()
{
var fullname = typeof(UsersController).FullName;
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var path = Request.Path.Value;
return Content($"Path: {path} fullname: {fullname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"Path: {path} ID:{id}");
}
}
}
Für den Code oben gilt:
- Die Basis
namespace
istMy.Application
. - Der vollständige Name des vorherigen Controllers lautet
My.Application.Admin.Controllers.UsersController
. - Die
NamespaceRoutingConvention
legt die Controllervorlage aufAdmin/Controllers/Users/[action]/{id?
fest.
Die NamespaceRoutingConvention
kann auch als Attribut auf einen Controller angewendet werden:
[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
// /admin/controllers/test/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var actionname = ControllerContext.ActionDescriptor.ActionName;
return Content($"Action- {actionname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
Gemischtes Routing: Attributrouting vs. herkömmliches Routing
ASP.NET Core-Apps können herkömmliches Routing und Attributrouting kombinieren. In der Regel werden herkömmliche Routen für Controller verwendet, die für Browser-HTML-Seiten gedacht sind, und das Attributrouting für Controller für REST-APIs.
Aktionen werden entweder herkömmlich oder über Attribute zugeordnet, d.h., dass eine Route auf dem Controller oder der Aktion platziert wird. Aktionen, die Attributrouten definieren, können nicht über die herkömmliche Routen und umgekehrt erreicht werden. Ein beliebiges Routenattribut auf dem Controller bewirkt, dass alle Aktionen im Controller über Attribute zugeordnet werden.
Attributrouting und herkömmliches Routing verwenden dieselbe Routing-Engine.
Routing mit Sonderzeichen
Ein Routing mit Sonderzeichen kann zu unerwarteten Ergebnissen führen. Stellen Sie sich z. B. einen Controller mit der folgenden Aktionsmethode vor:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Wenn string id
die folgenden codierten Werte enthält, können unerwartete Ergebnisse auftreten:
ASCII | Codiert |
---|---|
/ |
%2F |
|
+ |
Routenparameter sind nicht immer URL-decodiert. Dieses Problem wird möglicherweise in Zukunft behoben. Weitere Informationen finden Sie in diesem GitHub-Issue.
URL-Generierung und Umgebungswerte
Apps können Routingfeatures zur URL-Generierung verwenden, um URL-Links zu Aktionen zu generieren. Durch das Generieren von URLs müssen URLs nicht mehr hartcodiert werden, sodass der Code robuster und leichter verwaltbar wird. Dieser Abschnitt konzentriert sich auf die von MVC bereitgestellte URL-Generierung und befasst sich nur kurz mit der Funktionsweise. Eine detaillierte Beschreibung der URL-Generierung finden Sie unter Routing in ASP.NET Core.
Die IUrlHelper-Schnittstelle ist das zugrunde liegende Element zwischen MVC und dem Routing zur URL-Generierung. Eine Instanz von IUrlHelper
ist über die Url
-Eigenschaft in Controllern, Ansichten und Ansichtskomponenten verfügbar.
Im folgenden Beispiel wird die IUrlHelper
-Schnittstelle von der Controller.Url
-Eigenschaft dazu verwendet, eine URL in einer anderen Aktion zu generieren.
public class UrlGenerationController : Controller
{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Wenn die App die herkömmliche Standardroute verwendet, ist der Wert der url
-Variable die URL-Pfadzeichenfolge /UrlGeneration/Destination
. Dieser URL-Pfad wird durch Routing erstellt, indem Folgendes kombiniert wird:
- Die Routenwerte aus der aktuellen Anforderung, die als Umgebungswerte bezeichnet werden
- Die an
Url.Action
übergebenen Werte und das Einfügen dieser Werte in die Routenvorlage:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}
result: /UrlGeneration/Destination
Der Wert eines jeden Routenparameters wird in der Routenvorlage durch die entsprechenden Namen mit den Werten und Umgebungswerten ersetzt. Ein Routenparameter, der keinen Wert aufweist, kann Folgendes tun:
- Einen Standardwert verwenden (falls vorhanden)
- Übersprungen werden (falls möglich). Als Beispiel kann die
id
aus der Routenvorlage{controller}/{action}/{id?}
genannt werden.
Die URL-Generierung schlägt fehl, wenn ein erforderlicher Routenparameter keinen entsprechenden Wert besitzt. Wenn die URL-Generierung für eine Route fehlschlägt, wird die nächste Route ausprobiert, bis alle Routen getestet wurden oder eine Übereinstimmung gefunden wurde.
Im vorherigen Beispiel Url.Action
wird von herkömmlichen Routing ausgegangen. Die URL-Generierung funktioniert ähnlich wie das Attributrouting, obwohl sich die Konzepte unterscheiden. Für das herkömmliche Routing gilt Folgendes:
- Die Routenwerte werden verwendet, um eine Vorlage zu erweitern.
- Die Routenwerte für
controller
undaction
werden in der Regel in dieser Vorlage angezeigt. Dies funktioniert, da die vom Routing abgeglichenen URLs einer Konvention entsprechen.
Im folgenden Beispiel wird Attributrouting verwendet:
public class UrlGenerationAttrController : Controller
{
[HttpGet("custom")]
public IActionResult Source()
{
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die Source
-Aktion im vorherigen Code generiert custom/url/to/destination
.
LinkGenerator wurde in ASP.NET Core 3.0 als Alternative zu IUrlHelper
hinzugefügt. LinkGenerator
bietet ähnliche, aber flexiblere Funktionen. Jede Methode für IUrlHelper
verfügt ebenfalls über eine entsprechende Gruppe von Methoden für LinkGenerator
.
Generieren von URLs nach Aktionsnamen
Url.Action, LinkGenerator.GetPathByAction und alle zugehörigen Überladungen sind alle dafür konzipiert, den Zielendpunkt durch Angabe eines Controllernamens und eines Aktionsnamens zu erzeugen.
Bei Verwendung von Url.Action
sind die aktuellen Routenwerte für controller
und action
von der Runtime angegeben:
- Die Werte von
controller
undaction
sind sowohl Teil der Umgebungswerte als auch der Werte. Die MethodeUrl.Action
verwendet immer die aktuellen Werte vonaction
undcontroller
und generiert einen URL-Pfad, der zur aktuellen Aktion weiterleitet.
Beim Routing wird versucht, mit den Werten in den Umgebungswerten Informationen auszufüllen, die Sie beim Generieren einer URL nicht bereitgestellt haben. Betrachten Sie eine Route wie {a}/{b}/{c}/{d}
mit Umgebungswerten { a = Alice, b = Bob, c = Carol, d = David }
:
- Das Routing verfügt über genügend Informationen, um eine URL ohne zusätzliche Werte zu generieren.
- Das Routing verfügt über genügend Informationen, da alle Routenparameter über einen Wert verfügen.
Wenn der Wert { d = Donovan }
hinzugefügt wird, geschieht Folgendes:
- Der
{ d = David }
-Wert wird ignoriert. - Der generierte URL-Pfad ist
Alice/Bob/Carol/Donovan
.
Warnung: URL-Pfade sind hierarchisch. Wenn der Wert { c = Cheryl }
im vorherigen Beispiel hinzugefügt wird, geschieht Folgendes:
- Beide der Werte
{ c = Carol, d = David }
werden ignoriert. - Es gibt keinen Wert mehr für
d
, und die URL-Generierung schlägt fehl. - Die gewünschten Werte von
c
undd
müssen angegeben werden, um eine URL zu generieren.
Sie können davon ausgehen, dass dieses Problem mit der Standardroute {controller}/{action}/{id?}
auftritt. Dieses Problem tritt in der Praxis selten auf, da Url.Action
immer explizit einen controller
- und action
-Wert angibt.
Mehrere Überladungen von Url.Action nehmen ein Routenwerteobjekt, um Werte für andere Routenparameter als controller
und action
bereitzustellen. Das Routenwertobjekt wird häufig mit id
verwendet. Beispiel: Url.Action("Buy", "Products", new { id = 17 })
. Für das Routenwertobjekt gilt Folgendes:
- Es ist üblicherweise ein Objekt von anonymem Typ.
- Es kann ein
IDictionary<>
oder POCO sein.
Alle zusätzlichen Routenwerte, die keinen Routenparametern zugeordnet sind, werden in der Abfragezeichenfolge platziert.
public IActionResult Index()
{
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url!);
}
Der vorangehende Code generiert /Products/Buy/17?color=red
.
Der folgende Code generiert eine absolute URL:
public IActionResult Index2()
{
var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
// Returns https://localhost:5001/Products/Buy/17
return Content(url!);
}
Verwenden Sie eine der folgenden Optionen, um eine absolute URL zu erstellen:
- Eine Überladung, die ein
protocol
akzeptiert. Beispiel: der vorangehende Code. - LinkGenerator.GetUriByAction, der standardmäßig absolute URIs generiert
Generieren von URLs nach Route
Im vorangehenden Code wurde das Generieren einer URL durch das Übergeben des Controller- und Aktionsnamens veranschaulicht. IUrlHelper
stellt außerdem die Url.RouteUrl-Methodengruppe bereit. Diese Methoden ähneln Url.Action, kopieren jedoch nicht die aktuellen Werte action
und controller
in die Routenwerte. Die häufigste Verwendung von Url.RouteUrl
:
- Sie gibt einen Routennamen an, um die URL zu generieren.
- Sie gibt im Allgemeinen keinen Controller- oder Aktionsnamen an.
public class UrlGeneration2Controller : Controller
{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
Die folgende Razor-Datei generiert einen HTML-Link zu Destination_Route
:
<h1>Test Links</h1>
<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>
Generieren von URLs in HTML und Razor
IHtmlHelper stellt die HtmlHelper-Methoden Html.BeginForm und Html.ActionLink bereit, um jeweils <form>
- und <a>
-Elemente zu generieren. Diese Methoden verwenden die Url.Action-Methode, um eine URL zu generieren, und akzeptieren ähnliche Argumente. Die Url.RouteUrl
-Begleiter für HtmlHelper
sind Html.BeginRouteForm
und Html.RouteLink
, die ähnliche Funktionen aufweisen.
Taghilfsprogramme generieren URLs mit den Taghilfsprogrammen form
und <a>
. Beide implementieren mit IUrlHelper
. Weitere Informationen finden Sie unter Taghilfsprogramme in Formularen.
In Ansichten ist IUrlHelper
über die Url
-Eigenschaft für jede Ad-hoc-URL-Generierung verfügbar, die keine der oben genannten ist.
URL-Generierung in Aktionsergebnissen
In den obigen Beispielen wurde gezeigt, wie IUrlHelper
in einem Controller verwendet wird. Diese dient üblicherweise dazu, eine URL im Rahmen eines Aktionsergebnisses zu generieren.
Die Basisklassen ControllerBase und Controller stellen Hilfsmethoden für Aktionsergebnisse bereit, die auf eine andere Aktionen verweisen. Eine typische Verwendung besteht darin, nach dem Akzeptieren einer Benutzereingabe weiterzuleiten:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
if (ModelState.IsValid)
{
// Update DB with new details.
ViewData["Message"] = $"Successful edit of customer {id}";
return RedirectToAction("Index");
}
return View(customer);
}
Die Factorymethoden der Aktionsergebnisse (wie RedirectToAction und CreatedAtAction) folgen einem ähnlichen Muster wie die Methoden für IUrlHelper
.
Sonderfall für dedizierte herkömmliche Routen
Beim herkömmlichen Routing können Sie eine bestimmte Art von Routendefinition verwenden, die als dedizierte herkömmliche Route bezeichnet wird. Die Route namens blog
im folgenden Beispiel ist eine dedizierte herkömmliche Route.
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Unter Verwendung der vorangegangenen Routendefinitionen erzeugt Url.Action("Index", "Home")
den URL-Pfad /
mit der Route default
. Aber warum ist das so? Man könnte meinen, dass die Routenwerte { controller = Home, action = Index }
ausreichen würden, um eine URL mithilfe von blog
zu generieren, und das Ergebnis wäre /blog?action=Index&controller=Home
.
Dedizierte herkömmliche Routen nutzen ein spezielles Verhalten von Standardwerten, die keinen entsprechenden Routenparameter besitzen, der verhindert, dass die Route bei der URL-Generierung „zu gierig“ wird. In diesem Fall sind die Standardwerte { controller = Blog, action = Article }
, und weder controller
noch action
werden als Routenparameter verwendet. Wenn das Routing die URL-Generierung ausführt, müssen die angegebenen Werte mit den Standardwerten übereinstimmen. Die URL-Generierung mithilfe von blog
schlägt fehl, da die Werte { controller = Home, action = Index }
nicht mit { controller = Blog, action = Article }
übereinstimmen. Routing greift dann wieder auf default
zurück, was erfolgreich ausgeführt wird.
Bereiche
Bereiche sind ein Feature von MVC, das für die Organisation von verwandten Funktionalitäten in eine Gruppe als Folgendes verwendet wird:
- Separater Routingnamespace für Controlleraktionen
- Separate Ordnerstruktur für Ansichten
Mithilfe von Bereichen kann eine App mehrere Controller mit demselben Namen haben – solange sie verschiedene Bereiche haben. Mithilfe von Bereichen wird außerdem eine Hierarchie erstellt, damit das Routing durch Hinzufügen eines anderen Routenparameters ausgeführt werden kann: area
zu controller
und action
. In diesem Abschnitt wird erläutert, wie Routing mit Bereichen interagiert. Weitere Informationen zur Verwendung von Bereichen mit Ansichten finden Sie unter Bereiche.
Im folgenden Beispiel wird MVC konfiguriert, sodass es die herkömmliche Standardroute und eine area
-Route für einen area
namens Blog
verwendet:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapAreaControllerRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
app.Run();
Im vorherigen Code wird MapAreaControllerRoute aufgerufen, um die "blog_route"
zu erstellen. Der zweite Parameter, "Blog"
, ist der Bereichsname.
Beim Abgleich mit einem URL-Pfad wie /Manage/Users/AddUser
generiert die "blog_route"
-Route die Routenwerte { area = Blog, controller = Users, action = AddUser }
. Der area
-Routenwert wird von einem Standardwert für area
erzeugt. Die von MapAreaControllerRoute
erstellte Route entspricht dem Folgenden:
app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
MapAreaControllerRoute
erstellt mit einem Standardwert und einer Einschränkung für area
sowie mit dem bereitgestellten Bereichsnamen (in diesem Fall Blog
) eine Route. Der Standardwert stellt sicher, dass die Route immer { area = Blog, ... }
erzeugt, die Einschränkung erfordert den Wert { area = Blog, ... }
für URL-Generierung.
Beim herkömmlichen Routing ist die Reihenfolge wichtig. Routen mit Bereichen werden im Allgemeinen früher aufgeführt als die spezifischeren Routen ohne Bereich.
Die Routenwerte { area = Blog, controller = Users, action = AddUser }
im vorherigen Beispiel werden der folgenden Aktion zugeordnet:
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
Das Attribut [Area] kennzeichnet einen Controller als Teil eines Bereichs. Dieser Controller befindet sich im Blog
-Bereich. Controller ohne [Area]
-Attribut gehören demnach zu keinem Bereich und stimmen nicht überein, wenn die area
-Route wird vom Routing bereitgestellt wird. Im folgenden Beispiel kann nur der erste aufgelistete Controller die Routenwerte { area = Blog, controller = Users, action = AddUser }
abgleichen.
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
// GET /zebra/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
// GET /users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
Der Namespace der einzelnen Controller wird hier zur Vollständigkeit angezeigt. Wenn die vorherigen Controller denselben Namespace verwenden, wird ein Compilerfehler generiert. Klassennamespaces haben keine Auswirkungen auf das MVC-Routing.
Die ersten beiden Controller gehören zu Bereichen und werden können nur abgleichen, wenn ihr jeweiliger Bereichsname vom area
-Routenwert bereitgestellt wird. Der dritte Controller gehört keinem Bereich an und kann nur abgleichen, wenn vom Routing kein Wert für area
bereitgestellt wird.
Im Hinblick auf das Erkennen keines Werts hat die Abwesenheit des area
-Werts dieselben Auswirkungen, wie wenn der Wert für area
0 (null) oder eine leere Zeichenfolge wäre.
Beim Ausführen einer Aktion innerhalb eines Bereichs ist der Routenwert für area
als Umgebungswert für das Routing verfügbar und kann für die URL-Generierung verwendet werden. Das bedeutet, dass Bereiche bei der URL-Generierung wie im folgenden Beispiel dargestellt standardmäßig beständig sind.
app.MapAreaControllerRoute(name: "duck_route",
areaName: "Duck",
pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
// GET /Manage/users/GenerateURLInArea
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area.
var url = Url.Action("Index", "Home");
// Returns /Manage/Home/Index
return Content(url);
}
// GET /Manage/users/GenerateURLOutsideOfArea
public IActionResult GenerateURLOutsideOfArea()
{
// Uses the empty value for area.
var url = Url.Action("Index", "Home", new { area = "" });
// Returns /Manage
return Content(url);
}
}
}
Der folgende Code erzeugt eine URL zu /Zebra/Users/AddUser
:
public class HomeController : Controller
{
public IActionResult About()
{
var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
return Content($"URL: {url}");
}
Aktionsdefinition
Öffentliche Methoden in Controllern sind Aktionen, mit Ausnahme von Methoden mit NonAction-Attributen.
Beispielcode
- MyDisplayRouteInfo wird von dem NuGet-Paket Rick.Docs.Samples.RouteInfo bereitgestellt und zeigt Routeninformationen an.
- Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Debugdiagnose
Legen Sie für eine ausführliche Routingdiagnoseausgabe Logging:LogLevel:Microsoft
auf Debug
fest. Legen Sie in der Entwicklungsumgebung die Protokollebene in appsettings.Development.json
fest:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
ASP.NET Core-Controller verwenden Routing-Middleware, um die URLs der eingehenden Anforderungen abzugleichen und sie Aktionen zuzuordnen. Routenvorlagen:
- Werden im Startcode oder in Attributen definiert
- Beschreiben, wie URL-Pfade mit Aktionen abgeglichen werden
- Werden verwendet, um URLs für Links zu generieren Die generierten Links werden in der Regel in Antworten zurückgegeben
Aktionen werden entweder herkömmlich oder über Attribute zugeordnet. Das heißt, dass eine Route auf dem Controller oder der Aktion platziert wird. Weitere Informationen finden Sie im Abschnitt Gemischtes Routing.
Dieses Dokument hat folgende Eigenschaften:
- Die Interaktionen zwischen MVC und Routing werden erläutert:
- Es wird erklärt, wie typische MVC-Apps die Routingfeatures nutzen.
- Diese beiden Themen werden behandelt:
- Herkömmliches Routing, das in der Regel mit Controllern und Ansichten verwendet wird.
- Attributrouting, das mit REST-APIs verwendet wird. Wenn Sie sich hauptsächlich für das Routing für REST-APIs interessieren, wechseln Sie zum Abschnitt Attributrouting für REST-APIs.
- Informationen zum erweiterten Routing finden Sie unter Routing.
- Bezieht sich auf das in ASP.NET Core 3.0 hinzugefügte Standardroutingsystem, das als Endpunktrouting bezeichnet wird. Aus Kompatibilitätsgründen ist es möglich, Controller mit der vorherigen Version des Routings zu verwenden. Anweisungen finden Sie im Migrationsleitfaden 2.2–3.0. Referenzmaterial zum Legacyroutingsystem finden Sie in der Version 2.2 dieses Dokuments.
Einrichten einer herkömmlichen Route
Startup.Configure
hat typischerweise einen ähnlichen Code wie den folgenden, wenn herkömmliches Routing verwendet wird:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Innerhalb des Aufrufs von UseEndpoints wird MapControllerRoute verwendet, um eine einzelne Route zu erstellen. Die einzelne Route heißt default
-Route. Die meisten Apps mit Controllern und Ansichten verwenden eine Routenvorlage, die der default
-Route ähnelt. REST-APIs sollten Attributrouting verwenden.
Die Routenvorlage "{controller=Home}/{action=Index}/{id?}"
:
Entspricht einem URL-Pfad wie
/Products/Details/5
.Extrahiert die Routenwerte
{ controller = Products, action = Details, id = 5 }
durch Tokenisieren des Pfads. Die Extraktion von Routenwerten führt zu einer Übereinstimmung, wenn die App über einen Controller namensProductsController
und eineDetails
-Aktion verfügt:public class ProductsController : Controller { public IActionResult Details(int id) { return ControllerContext.MyDisplayRouteInfo(id); } }
MyDisplayRouteInfo wird von dem NuGet-Paket Rick.Docs.Samples.RouteInfo bereitgestellt und zeigt Routeninformationen an.
Das
/Products/Details/5
-Modell bindet den Wert vonid = 5
, um denid
-Parameter auf5
festzulegen. Weitere Informationen finden Sie unter Modellbindung.{controller=Home}
definiertHome
als Standard-controller
.{action=Index}
definiertIndex
als Standard-action
.Das
?
-Zeichen in{id?}
definiertid
als optional.Standardmäßige und optionale Routenparameter müssen nicht im URL-Pfad vorhanden sein, damit es eine Übereinstimmung gibt. Eine ausführliche Beschreibung der Syntax der Routenvorlage finden Sie unter Routenvorlagenreferenz.
Es besteht eine Übereinstimmung mit dem URL-Pfad
/
.Die Routenwerte
{ controller = Home, action = Index }
werden erzeugt.
Die Werte für controller
und action
verwenden die Standardwerte. id
erzeugt keine Werte, da kein entsprechendes Segment im URL-Pfad vorhanden ist. /
stimmt nur überein, wenn eine HomeController
- und Index
-Aktion vorhanden ist:
public class HomeController : Controller
{
public IActionResult Index() { ... }
}
Mit der vorherigen Controllerdefinition und der Routenvorlage wird die HomeController.Index
-Aktion für die folgenden URL-Pfade ausgeführt:
/Home/Index/17
/Home/Index
/Home
/
Der URL-Pfad /
verwendet die Home
-Standardcontroller und die Index
-Aktion der Routenvorlage. Der URL-Pfad /Home
verwendet die Index
-Standardaktion der Routenvorlage.
Mit der Hilfsmethode MapDefaultControllerRoute:
endpoints.MapDefaultControllerRoute();
ersetzt Folgendes:
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
Wichtig
Das Routing wird mithilfe der UseRouting-, MapControllerRoute
- und MapAreaControllerRoute
-Middleware konfiguriert. Führen Sie folgende Aktionen durch, um Controller zu verwenden:
- Rufen Sie MapControllers in
UseEndpoints
zum Zuordnen von über Attribute zugeordneten Controllern auf. - Rufen Sie MapControllerRoute oder MapAreaControllerRoute auf, um sowohl herkömmlich als auch über Attribute zugeordnete Controller zuzuordnen.
Herkömmliches Routing
Herkömmliches Routing wird mit Controllern und Ansichten verwendet. Die default
-Route:
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Das vorangehende Beispiel ist ein Beispiel für eine herkömmliche Route. Das herkömmliche (auch konventionelle) Routing heißt so, weil dabei eine Konvention für URL-Pfade erstellt wird:
- Das erste Pfadsegment (
{controller=Home}
) entspricht dem Namen des Controllers. - Das zweite Segment (
{action=Index}
) entspricht dem Namen der Aktion. - Das dritte Segment (
{id?}
) wird für eine optionaleid
verwendet. Die?
in{id?}
macht es optional.id
wird für die Zuordnung zu einer Modellentität verwendet.
Bei Verwendung dieser default
-Route gilt der URL-Pfad:
/Products/List
wird derProductsController.List
-Aktion zugeordnet./Blog/Article/17
wirdBlogController.Article
zugeordnet, und das Modell bindet denid
-Parameter üblicherweise an 17.
Für diese Zuordnung gilt Folgendes:
- Sie basiert nur auf den Controller- und Aktionsnamen.
- Sie basiert nicht auf Namespaces, Quelldateispeicherorten oder Methodenparametern.
Die Kombination aus herkömmlichem Routing und Standardrouting ermöglicht es Ihnen, die App zu erstellen, ohne für jede Aktion ein neues URL-Muster entwerfen zu müssen. Für eine App mit Aktionen im CRUD-Stil bewirkt die Konsistenz für die URLs zwischen Controllern Folgendes:
- Sie vereinfacht den Code.
- Sie macht die Benutzeroberfläche vorhersagbarer.
Warnung
Die id
im vorherigen Code wird von der Routenvorlage als optional definiert. Aktionen können ohne die optionale ID ausgeführt werden, die als Teil der URL angegeben wird. Im Allgemeinen gilt Folgendes, wenn id
in der URL nicht angegeben wird:
id
wird durch die Modellbindung auf0
festgelegt.- Es wurde keine Entität in der Datenbank gefunden, die mit
id == 0
übereinstimmt.
Mit dem Attributrouting können Sie präzise steuern, für welche Aktionen die ID erforderlich ist und für welche nicht. Gemäß der Konvention enthält die Dokumentation optionale Parameter wie id
, wenn sie wahrscheinlich in der richtigen Verwendung angezeigt werden.
Für die meisten Apps sollte eine grundlegendes und beschreibendes Routingschema ausgewählt werden, um lesbare und aussagekräftige URLs zu erhalten. Für die konventionelle Standardroute {controller=Home}/{action=Index}/{id?}
gilt:
- Sie unterstützt ein grundlegendes und beschreibendes Routingschema.
- Sie stellt einen nützlichen Startpunkt für benutzeroberflächenbasierte Apps dar.
- Ist die einzige Routenvorlage, die für viele Apps mit Webbenutzeroberfläche benötigt wird. Für größere Apps mit Webbenutzeroberfläche genügt häufig eine andere Route, die Bereiche verwendet.
MapControllerRoute und MapAreaRoute:
- Diese weisen ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden.
Endpunktrouting in ASP.NET Core 3.0 und höher:
- Weist kein Konzept für Routen auf.
- Bietet keine Reihenfolgegarantie für die Ausführung der Erweiterbarkeit, da alle Endpunkte gleichzeitig verarbeitet werden.
Wenn Sie die Protokollierung aktivieren, erfahren Sie, wie die integrierten Routingimplementierungen (z.B. Route) Zuordnungen für Anforderungen ermitteln.
Das Attributrouting wird weiter unten in diesem Dokument erläutert.
Mehrere herkömmliche Routen
Mehrere herkömmliche Routen können innerhalb von UseEndpoints
hinzugefügt werden, indem weitere Aufrufe zu MapControllerRoute und MapAreaControllerRoute hinzugefügt werden. So können Sie mehrere Konventionen definieren oder herkömmlichen Routen hinzufügen, die einer bestimmten Aktion zugeordnet sind, z. B.:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Die blog
-Route im vorherigen Code ist eine dedizierte herkömmliche Route. Sie wird aus diesen Gründen als dedizierte herkömmliche Route bezeichnet:
- Sie verwendet herkömmliches Routing.
- Sie ist für eine bestimmte Aktion bestimmt.
Da controller
und action
nicht als Parameter in der Routenvorlage "blog/{*article}"
angezeigt werden, gilt Folgendes:
- Sie können nur über die
{ controller = "Blog", action = "Article" }
-Standardwerte verfügen. - Diese Route wird immer der Aktion
BlogController.Article
zugeordnet.
/Blog
, /Blog/Article
und /Blog/{any-string}
sind die einzigen URL-Pfade, die der Blogroute entsprechen.
Für das vorherige Beispiel gilt Folgendes:
- Die
blog
-Route hat eine höhere Priorität für Übereinstimmungen als diedefault
-Route, da sie zuerst hinzugefügt wird. - Es ist ein Beispiel für Slugrouting, bei dem es typisch ist, einen Artikelnamen als Teil der URL zu haben.
Warnung
In ASP.NET Core 3.0 und höher gilt Folgendes für Routing:
- Es wird kein Konzept namens Route definiert.
UseRouting
fügt der Middlewarepipeline einen Routenabgleich hinzu. DieseUseRouting
-Middleware prüft die in der App definierten Endpunkte und wählt anhand der Anforderung die beste Endpunktübereinstimmung aus. - Geben Sie Garantien für die Ausführungsreihenfolge von Erweiterungen wie IRouteConstraint oder IActionConstraint an.
Referenzmaterial zum Routing finden Sie unter Routing.
Reihenfolge des herkömmlichen Routings
Herkömmliches Routing passt nur zu einer Kombination aus Aktion und Controller, die von der App definiert wird. Dieser Vorgang soll Szenarios vereinfachen, bei denen sich herkömmliche Routen überschneiden.
Das Hinzufügen von Routen mit MapControllerRoute, MapDefaultControllerRoute und MapAreaControllerRoute weist ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Übereinstimmungen aus einer zuvor angezeigten Route haben eine höhere Priorität. Beim herkömmlichen Routing ist die Reihenfolge wichtig. Routen mit Bereichen werden im Allgemeinen früher aufgeführt als die spezifischeren Routen ohne Bereich. Dedizierte herkömmliche Routen mit Catch-All-Routenparametern wie {*article}
können eine Route zu „gierig“ machen, was bedeutet, dass sie mit URLs übereinstimmt, die eigentlich mit anderen Routen übereinstimmen sollten. Fügen Sie die „gierigen“ Routen später in die Routingtabelle ein, um solche Übereinstimmungen zu verhindern.
Warnung
Ein catch-all-Parameter kann aufgrund eines Fehlers beim Routing nicht ordnungsgemäß mit Routen übereinstimmen. Apps, die von diesem Fehler betroffen sind, weisen die folgenden Merkmale auf:
- Eine catch-all-Route, zum Beispiel
{**slug}"
- Die catch-all-Route kann nicht mit Anforderungen abgeglichen werden, die abgeglichen werden sollen.
- Durch das Entfernen anderer Routen funktioniert die catch-all-Route.
Weitere Beispiele zu diesem Fehler finden Sie in den GitHub-Issues 18677 und 16579.
Eine Opt-in-Behebung für diesen Fehler ist im .NET Core 3.1.301 SDK und höher enthalten. Der folgende Code legt einen internen Switch fest, mit dem dieser Fehler behoben wird:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Auflösen von mehrdeutigen Aktionen
Wenn zwei Endpunkte über das Routing übereinstimmen, muss das Routing eine der folgenden Aktionen ausführen:
- Den besten Kandidaten auswählen
- Löst eine Ausnahme aus.
Beispiel:
public class Products33Controller : Controller
{
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}
}
Der vorherige Controller definiert zwei Aktionen, die mit Folgendem übereinstimmen:
- Dem URL-Pfad von
/Products33/Edit/17
- Routendaten
{ controller = Products33, action = Edit, id = 17 }
Dies ist ein typisches Muster für MVC-Controller:
Edit(int)
zeigt ein Formular zum Bearbeiten eines Produkts an.Edit(int, Product)
verarbeitet das bereitgestellte Formular.
Diese Bedingungen müssen erfüllt sein, um die richtige Route aufzulösen:
Edit(int, Product)
wird ausgewählt, wenn die Anforderung ein HTTP-POST
ist.Edit(int)
wird ausgewählt, wenn das HTTP-Verb etwas anderes ist.Edit(int)
wird im Allgemeinen überGET
aufgerufen.
Das HttpPostAttribute ([HttpPost]
) wird für das Routing bereitgestellt, sodass es basierend auf der HTTP-Methode der Anforderung eine Auswahl treffen kann. Das HttpPostAttribute
macht Edit(int, Product)
zu einer besseren Übereinstimmung als Edit(int)
.
Es ist wichtig, die Rolle von Attributen wie HttpPostAttribute
zu verstehen. Ähnliche Attribute werden für andere HTTP-Verben definiert. Beim herkömmlichen Routing nutzen Aktionen oft denselben Aktionsnamen, wenn sie Teil eines Formularworkflows sind. Weitere Informationen finden Sie beispielsweise unter Untersuchen der beiden Edit-Aktionsmethoden.
Wenn das Routing keinen geeigneten Kandidaten auswählen kann, wird eine AmbiguousMatchException ausgelöst, in dem die verschiedenen übereinstimmenden Endpunkte aufgelistet werden.
Namen herkömmlicher Routen
Die Zeichenfolgen "blog"
und "default"
in den folgenden Beispielen sind herkömmliche Routennamen:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Die Routennamen geben der Route einen logischen Namen. Die benannte Route kann bei der URL-Generierung verwendet werden. Durch das Verwenden einer benannten Route wird die URL-Erstellung erheblich vereinfacht, obwohl die Reihenfolge der Routen die URL-Generierung verkomplizieren sollte. Routennamen müssen anwendungsweit eindeutig sein.
Routennamen:
- Haben keine Auswirkungen auf den URL-Abgleich oder die Verarbeitung von Anforderungen
- Werden nur für die URL-Generierung verwendet
Das Routennamenkonzept wird im Routing als IEndpointNameMetadata dargestellt. Für die Begriffe Routenname und Endpunktname gilt Folgendes:
- Sie können austauschbar verwendet werden.
- Welcher in der Dokumentation und im Code verwendet wird, hängt von der beschriebenen API ab.
Attributrouting für REST-APIs
REST-APIs sollten das Attributrouting verwenden, um die Funktionalität der App als eine Gruppe von Ressourcen zu modellieren, bei denen Vorgänge durch HTTP-Verben dargestellt werden.
Beim Attributrouting werden Aktionen mithilfe von Attributen direkt Routenvorlagen zugeordnet. Der folgende StartUp.Configure
-Code ist typisch für eine REST-API und wird im nächsten Beispiel verwendet:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Im vorherigen Code wird MapControllers innerhalb von UseEndpoints
aufgerufen, um über Attribute zugeordnete Controller des Attributs zuzuordnen.
Im folgenden Beispiel:
HomeController
entspricht einer Gruppe von URLs, ähnlich wie die herkömmliche Standardroute{controller=Home}/{action=Index}/{id?}
.
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Die Aktion HomeController.Index
wird für jeden der URL-Pfade /
, /Home
, /Home/Index
oder /Home/Index/3
ausgeführt.
In diesem Beispiel wird ein wichtiger Unterschied beim Programmieren zwischen dem Attributrouting und dem herkömmlichen Routing hervorgehoben. Attributrouting erfordert mehr Eingaben, um eine Route anzugeben. Die herkömmliche Standardroute verarbeitet auch kürzere Routen. Attributrouting ermöglicht jedoch (und erfordert auch) die präzise Kontrolle der Routenvorlagen, die für die einzelnen Aktionen gelten.
Beim Attributrouting haben die Namen des Controllers und der Aktion keinen Einfluss darauf, welche Aktion übereinstimmt, es sei denn, es wird die Tokenersetzung verwendet. Im folgenden Beispiel werden dieselben URLs wie im vorherigen Beispiel verwendet:
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Der folgende Code verwendet die Tokenersetzung für action
und controller
:
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("[controller]/[action]")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Der folgende Code wendet [Route("[controller]/[action]")]
auf den Controller an:
[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Im vorangehenden Code müssen die Index
-Methodenvorlagen den Routenvorlagen /
oder ~/
voranstellen. Routenvorlagen, die auf eine Aktion angewendet werden, die mit einem /
oder ~/
beginnen, können nicht mit Routenvorlagen kombiniert werden, die auf den Controller angewendet werden.
Informationen zur Auswahl von Routenvorlagen finden Sie unter Priorität der Routenvorlage.
Reservierte Routingnamen
Die folgenden Schlüsselwörter sind reservierte Routenparameternamen bei der Verwendung von Controllern oder Razor Pages:
action
area
controller
handler
page
Die Verwendung von page
als Routenparameter mit Attributrouting ist ein häufiger Fehler. Dies führt zu inkonsistentem und verwirrendem Verhalten bei der URL-Generierung.
public class MyDemo2Controller : Controller
{
[Route("/articles/{page}")]
public IActionResult ListArticles(int page)
{
return ControllerContext.MyDisplayRouteInfo(page);
}
}
Die speziellen Parameternamen werden von der URL-Generierung verwendet, um zu bestimmen, ob sich ein URL-Generierungsvorgang auf Razor Pages oder einen Controller bezieht.
Die folgenden Schlüsselwörter sind im Kontext einer Razor-Ansicht oder einer Razor-Seite reserviert:
page
using
namespace
inject
section
inherits
model
addTagHelper
removeTagHelper
Diese Schlüsselwörter sollten nicht für Linkgenerierungen, modellgebundene Parameter oder Eigenschaften der obersten Ebene verwendet werden.
HTTP-Verbvorlagen
ASP.NET Core verfügt über die folgenden HTTP-Verbvorlagen:
Routenvorlagen
ASP.NET Core verfügt über die folgenden Routenvorlagen:
- Alle HTTP-Verbvorlagen sind Routenvorlagen.
- [Route]
Attributrouting mit Http[Verb]-Attributen
Betrachten Sie den folgenden Controller:
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Für den Code oben gilt:
- Jede Aktion enthält das
[HttpGet]
-Attribut, das nur den Abgleich mit HTTP GET-Anforderungen einschränkt. - Die
GetProduct
-Aktion enthält die"{id}"
-Vorlage, daher wirdid
an die"api/[controller]"
-Vorlage auf dem Controller angehängt. Die Methodenvorlage ist"api/[controller]/{id}"
. Daher passt diese Aktion nur auf GET-Anforderungen für das Formular/api/test2/xyz
,/api/test2/123
,/api/test2/{any string}
, usw.[HttpGet("{id}")] // GET /api/test2/xyz public IActionResult GetProduct(string id) { return ControllerContext.MyDisplayRouteInfo(id); }
- Die
GetIntProduct
-Aktion enthält die"int/{id:int}"
-Vorlage. Der:int
-Teil der Vorlage beschränkt dieid
-Routenwerte auf Zeichenfolgen, die in eine ganze Zahl konvertiert werden können. Für eine GET-Anforderung an/api/test2/int/abc
gilt:- Sie entspricht dieser Aktion nicht.
- Sie gibt den Fehler 404 Nicht gefunden zurück.
[HttpGet("int/{id:int}")] // GET /api/test2/int/3 public IActionResult GetIntProduct(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
- Die
GetInt2Product
-Aktion enthält{id}
in der Vorlage, beschränktid
aber nicht auf die Werte, die in eine ganze Zahl konvertiert werden können. Für eine GET-Anforderung an/api/test2/int2/abc
gilt:- Sie entspricht dieser Route.
- Die Modellbindung kann
abc
nicht in eine ganze Zahl konvertieren. Derid
-Parameter der Methode ist eine ganze Zahl. - Sie gibt 400 Ungültige Anforderung zurück, da die Modellbindung
abc
nicht in eine ganze Zahl konvertieren konnte.[HttpGet("int2/{id}")] // GET /api/test2/int2/3 public IActionResult GetInt2Product(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
Beim Attributrouting können HttpMethodAttribute-Attribute wie HttpPostAttribute, HttpPutAttribute und HttpDeleteAttribute verwendet werden. Alle HTTP-Verbattribute akzeptieren eine Routenvorlage. Im folgenden Beispiel werden zwei Aktionen gezeigt, die derselben Routenvorlage zugeordnet sind:
[ApiController]
public class MyProductsController : ControllerBase
{
[HttpGet("/products3")]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpPost("/products3")]
public IActionResult CreateProduct(MyProduct myProduct)
{
return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
}
}
Für die Verwendung des URL-Pfads /products3
gilt:
- Die
MyProductsController.ListProducts
-Aktion wird ausgeführt, wenn das HTTP-VerbGET
ist. - Die
MyProductsController.CreateProduct
-Aktion wird ausgeführt, wenn das HTTP-VerbPOST
ist.
Beim Erstellen einer REST-API wird [Route(...)]
selten für eine Aktionsmethode verwendet, die Aktion alle HTTP-Methoden akzeptiert. Es ist besser, das spezifischere HTTP-Verbattribut zu nutzen, um präzise anzugeben, was Ihre API unterstützt. REST-API-Clients sollten wissen, welche Pfade und HTTP-Verben bestimmten logischen Operationen entsprechen.
REST-APIs sollten das Attributrouting verwenden, um die Funktionalität der App als eine Gruppe von Ressourcen zu modellieren, bei denen Vorgänge durch HTTP-Verben dargestellt werden. Dies bedeutet, dass viele Vorgänge, z. B. GET und POST, für dieselbe logische Ressource dieselbe URL verwenden. Das Attributrouting bietet eine Ebene der Steuerung, die für einen sorgfältigen Entwurf des öffentlichen Endpunktlayouts einer API erforderlich ist.
Da eine Attributroute für eine bestimmte Aktion gilt, ist es einfach, Parameter als Teil der Routenvorlagendefinition erforderlich festzulegen. Im folgenden Beispiel ist id
als Teil des URL-Pfads erforderlich.
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Für die Products2ApiController.GetProduct(int)
-Aktion gilt:
- Sie wird mit einem URL-Pfad wie
/products2/3
ausgeführt. - Sie wird nicht mit einem URL-Pfad wie
/products2
ausgeführt.
Das [Consumes]-Attribut ermöglicht es einer Aktion, die unterstützten Anforderungsinhaltstypen einzuschränken. Weitere Informationen finden Sie unter Definieren unterstützter Anforderungsinhaltstypen mit dem [Consumes]-Attribut.
Eine vollständige Beschreibung und Routenvorlagen und dazugehörige Optionen finden Sie unter Routing in ASP.NET Core.
Weitere Informationen zu [ApiController]
finden Sie unter ApiController-Attribut.
Routenname
Im folgenden Code wird ein Routenname von Products_List
definiert:
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Routennamen können verwendet werden, um basierend auf einer bestimmten Route eine URL zu generieren. Routennamen:
- Sie haben keine Auswirkungen auf das URL-Abgleichsverhalten des Routings.
- Sie werden nur für die URL-Generierung verwendet.
Routennamen müssen anwendungsweit eindeutig sein.
Vergleichen Sie den vorherigen Code mit der herkömmlichen Standardroute, die den id
-Parameter als optional definiert ({id?}
). APIs präzise angeben zu können, hat Vorteile, z. B. können /products
und /products/5
an unterschiedliche Aktionen gesendet werden.
Kombinieren von Attributrouten
Um Attributrouting weniger repetitiv zu gestalten, werden Routenattribute auf dem Controller mit Routenattributen auf den einzelnen Aktionen kombiniert. Alle auf dem Controller definierten Routenvorlagen werden den Routenvorlagen auf den Aktionen vorangestellt. Wenn Routenattribute auf dem Controller platziert werden, verwenden alle Aktionen im Controller Attributrouting.
[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
[HttpGet]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Im vorherigen Beispiel:
- Der URL-Pfad
/products
kann mitProductsApi.ListProducts
übereinstimmen. - Der URL-Pfad
/products/5
kann mitProductsApi.GetProduct(int)
übereinstimmen.
Beide Aktionen gleichen nur „HTTP GET
“ ab, weil sie mit dem [HttpGet]
-Attribut markiert sind.
Routenvorlagen, die auf eine Aktion angewendet werden, die mit einem /
oder ~/
beginnen, können nicht mit Routenvorlagen kombiniert werden, die auf den Controller angewendet werden. Im folgenden Beispiel werden mehrere URL-Pfade zugeordnet, die der Standardroute ähneln.
[Route("Home")]
public class HomeController : Controller
{
[Route("")]
[Route("Index")]
[Route("/")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("About")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
In der folgenden Tabelle werden die [Route]
-Attribute im vorherigen Code erläutert:
attribute | Kann mit [Route("Home")] kombiniert werden |
Definiert die Routenvorlage |
---|---|---|
[Route("")] |
Ja | "Home" |
[Route("Index")] |
Ja | "Home/Index" |
[Route("/")] |
Nein | "" |
[Route("About")] |
Ja | "Home/About" |
Reihenfolge der Attributrouten
Beim Routing wird eine Struktur erstellt, die alle Endpunkte gleichzeitig abgleicht:
- Der Vorgang wird also ausgeführt, als ob die Routeneinträge in der idealen Reihenfolge platziert worden wären.
- Die spezifischsten Routen können also vor den allgemeineren ausgeführt werden.
Eine Attributroute wie blog/search/{topic}
ist beispielsweise spezifischer als eine Attributroute wie blog/{*article}
. Die blog/search/{topic}
-Route hat standardmäßig eine höhere Priorität, da sie spezifischer ist. Beim herkömmlichen Routing ist der*die Entwickler*in verantwortlich dafür, die Routen in die gewünschte Reihenfolge zu bringen.
Attributrouten können die Reihenfolge mit der Eigenschaft Order konfigurieren. Alle vom Framework bereitgestellten Routenattribute enthalten Order
. Routen werden entsprechend einer aufsteigenden Reihenfolge der Order
-Eigenschaft verarbeitet. Die Standardreihenfolge ist 0
. Das Festlegen einer Route mit Order = -1
wird vor Routen ausgeführt, die keine Reihenfolge vorgeben. Das Festlegen einer Route mit Order = 1
wird nach der Standardreihenfolge der Routen ausgeführt.
Vermeiden Sie eine Abhängigkeit von Order
. Wenn der URL-Raum einer App explizite Reihenfolgenwerte erfordert, um korrekt weiterzuleiten, ist es wahrscheinlich auch für Clients verwirrend. Beim Attributrouting wird im Allgemeinen mithilfe der URL-Zuordnung die richtige Route ausgewählt. Wenn die für die URL-Generierung verwendete Standardreihenfolge nicht funktioniert, ist es meist einfacher, Routennamen als Außerkraftsetzung zu verwenden, statt die Order
-Eigenschaft anzuwenden.
Betrachten Sie die folgenden beiden Controller, die beide den Routenabgleich /home
definieren:
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Beim Anfordern von /home
mit dem vorherigen Code wird eine Ausnahme ausgelöst, die der folgenden ähnelt:
AmbiguousMatchException: The request matched multiple endpoints. Matches:
WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex
Das Hinzufügen von Order
zu einem der Routenattribute löst die Mehrdeutigkeit auf:
[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
return ControllerContext.MyDisplayRouteInfo();
}
Mit dem vorherigen Code führt /home
den HomeController.Index
-Endpunkt aus. Um zu MyDemoController.MyIndex
zu gelangen, fordern Sie /home/MyIndex
. Hinweis:
- Der vorangehende Code ist ein Beispiel für ein schlechtes Routingdesign. Es wurde verwendet, um die Eigenschaft
Order
zu veranschaulichen. - Die
Order
-Eigenschaft löst nur die Mehrdeutigkeit auf. Diese Vorlage kann nicht zugeordnet werden. Es wäre besser, die[Route("Home")]
-Vorlage zu entfernen.
Unter Razor Pages: Routen- und App-Konventionen: Routenreihenfolge finden Sie weitere Informationen zur Routenreihenfolge mit Razor Pages.
In einigen Fällen wird ein HTTP 500-Fehler mit mehrdeutigen Routen zurückgegeben. Verwenden Sie die Protokollierung, um zu ermitteln, welche Endpunkte die AmbiguousMatchException
verursacht haben.
Ersetzen von Token in Routenvorlagen ([controller], [action] [area])
Der Einfachheit halber unterstützen Attributrouten die Tokenersetzung, indem ein Token in eckige Klammern eingeschlossen wird ([
, ]
). Die Token [action]
, [area]
und [controller]
werden durch die Werte der Aktionsnamen, den Namen des Bereichs und des Controllers der Aktion ersetzt, in dem die Route definiert ist:
[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Für den Code oben gilt:
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
- Stimmt mit
/Products0/List
überein
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
- Stimmt mit
/Products0/Edit/{id}
überein
Die Tokenersetzung tritt im letzten Schritt der Erstellung von Attributrouten auf. Der folgende Code verhält sich genauso wie der aus dem obigen Beispiel:
public class Products20Controller : Controller
{
[HttpGet("[controller]/[action]")] // Matches '/Products20/List'
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("[controller]/[action]/{id}")] // Matches '/Products20/Edit/{id}'
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Wenn Sie dies in einer anderen Sprache als Englisch lesen, informieren Sie uns in diesem GitHub-Issue, wenn Sie die Codekommentare in der eigenen Sprache sehen möchten.
Attributrouten können auch mit Vererbung kombiniert werden. Dies ist effektiv in Kombination mit der Tokenersetzung. Tokenersetzung gilt auch für Routennamen, die durch Attributrouten definiert werden.
[Route("[controller]/[action]", Name="[controller]_[action]")]
generiert für jede Aktion einen eindeutigen Routennamen.
[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}
public class Products11Controller : MyBase2Controller
{
[HttpGet] // /api/products11/list
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Damit das Trennzeichen [
oder ]
der Tokenersetzungs-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein ([[
oder ]]
), was einem Escapezeichen entspricht.
Verwenden eines Parametertransformators zum Anpassen der Tokenersetzung
Die Tokenersetzung kann mit einem Parametertransformator angepasst werden. Ein Parametertransformator implementiert IOutboundParameterTransformer und wandelt den Wert der Parameter um. Beispielsweise ändert ein benutzerdefinierter SlugifyParameterTransformer
-Parametertransformator den Routenwert SubscriptionManagement
in subscription-management
:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string TransformOutbound(object value)
{
if (value == null) { return null; }
return Regex.Replace(value.ToString(),
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
Die RouteTokenTransformerConvention ist eine Anwendungsmodellkonvention, die Folgendes ausführt:
- Wendet einen angegebenen Parametertransformator auf alle Attributrouten in der App an.
- Passt die Tokenwerte für Attributrouten bei ihrer Ersetzung an.
public class SubscriptionManagementController : Controller
{
[HttpGet("[controller]/[action]")]
public IActionResult ListAll()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die vorherige ListAll
-Method stimmt mit /subscription-management/list-all
überein.
Die RouteTokenTransformerConvention
wird als Option in ConfigureServices
registriert.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
});
}
Die Definition von Slug finden Sie in der MDN-Webdokumentation zu Slug.
Warnung
Übergeben Sie ein Timeout, wenn Sie System.Text.RegularExpressions zum Verarbeiten nicht vertrauenswürdiger Eingaben verwenden. Ein böswilliger Benutzer kann Eingaben für RegularExpressions
angeben, um einen Denial-of-Service-Angriff durchzuführen. ASP.NET Core-Framework-APIs, die RegularExpressions
verwenden, übergeben ein Timeout.
Mehrere Attributrouten
Attributrouting unterstützt das Definieren mehrerer Routen, die zu derselben Aktion führen. Dies wird am häufigsten beim Imitieren des Verhaltens der herkömmlichen Standardroute verwendet. Im folgenden Beispiel wird dies gezeigt:
[Route("[controller]")]
public class Products13Controller : Controller
{
[Route("")] // Matches 'Products13'
[Route("Index")] // Matches 'Products13/Index'
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
Wenn mehrere Routenattribute auf dem Controller platziert werden, bedeutet dies, dass jedes Attribut mit den Routenattributen der Aktionsmethoden kombiniert wird:
[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
[HttpPost("Buy")] // Matches 'Products6/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products6/Checkout' and 'Store/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Alle Routeneinschränkungen von HTTP-Verben implementieren IActionConstraint
.
Wenn mehrere Routenattribute, die IActionConstraint implementieren, einer Aktion zugeordnet werden, geschieht Folgendes:
- Jede Aktionseinschränkung wird mit der Routenvorlage kombiniert, die auf den Controller angewendet wird.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
[HttpPut("Buy")] // Matches PUT 'api/Products7/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products7/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die Verwendung mehrerer Routen für Aktionen mag zwar nützlich und leistungsfähig erscheinen, es ist jedoch besser, den URL-Bereich Ihrer App einfach und klar definiert zu halten. Verwenden Sie nur mehrere Routen für Aktionen, wenn dies notwendig ist, z. B. um vorhandene Clients zu unterstützen.
Angeben von optionalen Attributroutenparametern, Standardwerten und Einschränkungen
Attributrouten unterstützen dieselbe Inline-Syntax wie herkömmliche Routen, um optionale Parameter, Standardwerte und Einschränkungen anzugeben.
public class Products14Controller : Controller
{
[HttpPost("product14/{id:int}")]
public IActionResult ShowProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
[HttpPost("product14/{id:int}")]
wendet im vorherigen Code eine Routeneinschränkung an. Die Products14Controller.ShowProduct
-Aktion wird nur mit URL-Pfaden wie /product14/3
abgeglichen. Der Routenvorlagenteil {id:int}
schränkt dieses Segment auf ganze Zahlen ein.
Eine ausführliche Beschreibung der Syntax der Routenvorlage finden Sie unter Routenvorlagenreferenz.
Benutzerdefinierte Routenattribute, die IRouteTemplateProvider verwenden
Alle Routenattribute implementieren IRouteTemplateProvider. Für die ASP.NET Core-Runtime gilt:
- Sie sucht nach Attributen für Controllerklassen und Aktionsmethoden, wenn die App gestartet wird.
- Sie verwendet die Attribute, die
IRouteTemplateProvider
implementieren, um die erste Gruppe der Routen zu erstellen.
Implementieren Sie IRouteTemplateProvider
, um benutzerdefinierte Routenattribute zu definieren. Jeder IRouteTemplateProvider
lässt Sie eine einzelne Route mit einer benutzerdefinierten Routenvorlage, Reihenfolge und einem benutzerdefinierten Namen definieren:
public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
public string Template => "api/[controller]";
public int? Order => 2;
public string Name { get; set; }
}
[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die vorhergehende Get
-Methode gibt Order = 2, Template = api/MyTestApi
zurück.
Anpassen von Attributrouten mithilfe des Anwendungsmodells
Das Anwendungsmodell:
- Ist ein Objektmodell, das beim Start erstellt wurde
- Enthält alle Metadaten, die von ASP.NET Core zum Weiterleiten und Ausführen der Aktionen in einer App verwendet werden
Das Anwendungsmodell enthält alle Daten, die aus Routenattributen erfasst wurden. Die Daten aus Routenattributen werden von der IRouteTemplateProvider
-Implementierung bereitgestellt. Konventionen:
- Können geschrieben werden, um das Anwendungsmodell zu ändern und das Routingverhalten anzupassen
- Werden beim Starten der App gelesen
Dieser Abschnitt enthält ein einfaches Beispiel für das Anpassen des Routings mithilfe des Anwendungsmodells. Der folgende Code sorgt dafür, dass die Routen in etwa mit der Ordnerstruktur des Projekts übereinstimmen.
public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
private readonly string _baseNamespace;
public NamespaceRoutingConvention(string baseNamespace)
{
_baseNamespace = baseNamespace;
}
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
var namespc = controller.ControllerType.Namespace;
if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]/[action]/{id?}");
foreach (var selector in controller.Selectors)
{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}
Der folgende Code verhindert, dass die namespace
-Konvention auf Controller angewendet wird, die über Attribute zugeordnet werden:
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
Der folgende Controller verwendet beispielsweise NamespaceRoutingConvention
nicht:
[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
// /managers/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
return Content($"Index- template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
Die NamespaceRoutingConvention.Apply
-Methode:
- Wird nicht ausgeführt, wenn der Controller mit einem Attribut weitergeleitet wird
- Legt die Controllervorlage basierend auf dem
namespace
fest, wobei die Basisnamespace
entfernt wurde
Die NamespaceRoutingConvention
kann in Startup.ConfigureServices
angewendet werden:
namespace My.Application
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(Startup).Namespace));
});
}
// Remaining code ommitted for brevity.
Betrachten Sie beispielsweise den folgenden Controller:
using Microsoft.AspNetCore.Mvc;
namespace My.Application.Admin.Controllers
{
public class UsersController : Controller
{
// GET /admin/controllers/users/index
public IActionResult Index()
{
var fullname = typeof(UsersController).FullName;
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var path = Request.Path.Value;
return Content($"Path: {path} fullname: {fullname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"Path: {path} ID:{id}");
}
}
}
Für den Code oben gilt:
- Die Basis
namespace
istMy.Application
. - Der vollständige Name des vorherigen Controllers lautet
My.Application.Admin.Controllers.UsersController
. - Die
NamespaceRoutingConvention
legt die Controllervorlage aufAdmin/Controllers/Users/[action]/{id?
fest.
Die NamespaceRoutingConvention
kann auch als Attribut auf einen Controller angewendet werden:
[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
// /admin/controllers/test/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var actionname = ControllerContext.ActionDescriptor.ActionName;
return Content($"Action- {actionname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
Gemischtes Routing: Attributrouting vs. herkömmliches Routing
ASP.NET Core-Apps können herkömmliches Routing und Attributrouting kombinieren. In der Regel werden herkömmliche Routen für Controller verwendet, die für Browser-HTML-Seiten gedacht sind, und das Attributrouting für Controller für REST-APIs.
Aktionen werden entweder herkömmlich oder über Attribute zugeordnet, d.h., dass eine Route auf dem Controller oder der Aktion platziert wird. Aktionen, die Attributrouten definieren, können nicht über die herkömmliche Routen und umgekehrt erreicht werden. Ein beliebiges Routenattribut auf dem Controller bewirkt, dass alle Aktionen im Controller über Attribute zugeordnet werden.
Attributrouting und herkömmliches Routing verwenden dieselbe Routing-Engine.
URL-Generierung und Umgebungswerte
Apps können Routingfeatures zur URL-Generierung verwenden, um URL-Links zu Aktionen zu generieren. Durch das Generieren von URLs müssen URLs nicht mehr hartcodiert werden, sodass der Code robuster und leichter verwaltbar wird. Dieser Abschnitt konzentriert sich auf die von MVC bereitgestellte URL-Generierung und befasst sich nur kurz mit der Funktionsweise. Eine detaillierte Beschreibung der URL-Generierung finden Sie unter Routing in ASP.NET Core.
Die IUrlHelper-Schnittstelle ist das zugrunde liegende Element zwischen MVC und dem Routing zur URL-Generierung. Eine Instanz von IUrlHelper
ist über die Url
-Eigenschaft in Controllern, Ansichten und Ansichtskomponenten verfügbar.
Im folgenden Beispiel wird die IUrlHelper
-Schnittstelle von der Controller.Url
-Eigenschaft dazu verwendet, eine URL in einer anderen Aktion zu generieren.
public class UrlGenerationController : Controller
{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Wenn die App die herkömmliche Standardroute verwendet, ist der Wert der url
-Variable die URL-Pfadzeichenfolge /UrlGeneration/Destination
. Dieser URL-Pfad wird durch Routing erstellt, indem Folgendes kombiniert wird:
- Die Routenwerte aus der aktuellen Anforderung, die als Umgebungswerte bezeichnet werden
- Die an
Url.Action
übergebenen Werte und das Einfügen dieser Werte in die Routenvorlage:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}
result: /UrlGeneration/Destination
Der Wert eines jeden Routenparameters wird in der Routenvorlage durch die entsprechenden Namen mit den Werten und Umgebungswerten ersetzt. Ein Routenparameter, der keinen Wert aufweist, kann Folgendes tun:
- Einen Standardwert verwenden (falls vorhanden)
- Übersprungen werden (falls möglich). Als Beispiel kann die
id
aus der Routenvorlage{controller}/{action}/{id?}
genannt werden.
Die URL-Generierung schlägt fehl, wenn ein erforderlicher Routenparameter keinen entsprechenden Wert besitzt. Wenn die URL-Generierung für eine Route fehlschlägt, wird die nächste Route ausprobiert, bis alle Routen getestet wurden oder eine Übereinstimmung gefunden wurde.
Im vorherigen Beispiel Url.Action
wird von herkömmlichen Routing ausgegangen. Die URL-Generierung funktioniert ähnlich wie das Attributrouting, obwohl sich die Konzepte unterscheiden. Für das herkömmliche Routing gilt Folgendes:
- Die Routenwerte werden verwendet, um eine Vorlage zu erweitern.
- Die Routenwerte für
controller
undaction
werden in der Regel in dieser Vorlage angezeigt. Dies funktioniert, da die vom Routing abgeglichenen URLs einer Konvention entsprechen.
Im folgenden Beispiel wird Attributrouting verwendet:
public class UrlGenerationAttrController : Controller
{
[HttpGet("custom")]
public IActionResult Source()
{
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Die Source
-Aktion im vorherigen Code generiert custom/url/to/destination
.
LinkGenerator wurde in ASP.NET Core 3.0 als Alternative zu IUrlHelper
hinzugefügt. LinkGenerator
bietet ähnliche, aber flexiblere Funktionen. Jede Methode für IUrlHelper
verfügt ebenfalls über eine entsprechende Gruppe von Methoden für LinkGenerator
.
Generieren von URLs nach Aktionsnamen
Url.Action, LinkGenerator.GetPathByAction und alle zugehörigen Überladungen sind alle dafür konzipiert, den Zielendpunkt durch Angabe eines Controllernamens und eines Aktionsnamens zu erzeugen.
Bei Verwendung von Url.Action
sind die aktuellen Routenwerte für controller
und action
von der Runtime angegeben:
- Die Werte von
controller
undaction
sind sowohl Teil der Umgebungswerte als auch der Werte. Die MethodeUrl.Action
verwendet immer die aktuellen Werte vonaction
undcontroller
und generiert einen URL-Pfad, der zur aktuellen Aktion weiterleitet.
Beim Routing wird versucht, mit den Werten in den Umgebungswerten Informationen auszufüllen, die Sie beim Generieren einer URL nicht bereitgestellt haben. Betrachten Sie eine Route wie {a}/{b}/{c}/{d}
mit Umgebungswerten { a = Alice, b = Bob, c = Carol, d = David }
:
- Das Routing verfügt über genügend Informationen, um eine URL ohne zusätzliche Werte zu generieren.
- Das Routing verfügt über genügend Informationen, da alle Routenparameter über einen Wert verfügen.
Wenn der Wert { d = Donovan }
hinzugefügt wird, geschieht Folgendes:
- Der
{ d = David }
-Wert wird ignoriert. - Der generierte URL-Pfad ist
Alice/Bob/Carol/Donovan
.
Warnung: URL-Pfade sind hierarchisch. Wenn der Wert { c = Cheryl }
im vorherigen Beispiel hinzugefügt wird, geschieht Folgendes:
- Beide der Werte
{ c = Carol, d = David }
werden ignoriert. - Es gibt keinen Wert mehr für
d
, und die URL-Generierung schlägt fehl. - Die gewünschten Werte von
c
undd
müssen angegeben werden, um eine URL zu generieren.
Sie können davon ausgehen, dass dieses Problem mit der Standardroute {controller}/{action}/{id?}
auftritt. Dieses Problem tritt in der Praxis selten auf, da Url.Action
immer explizit einen controller
- und action
-Wert angibt.
Mehrere Überladungen von Url.Action nehmen ein Routenwerteobjekt, um Werte für andere Routenparameter als controller
und action
bereitzustellen. Das Routenwertobjekt wird häufig mit id
verwendet. Beispiel: Url.Action("Buy", "Products", new { id = 17 })
. Für das Routenwertobjekt gilt Folgendes:
- Es ist üblicherweise ein Objekt von anonymem Typ.
- Es kann ein
IDictionary<>
oder POCO sein.
Alle zusätzlichen Routenwerte, die keinen Routenparametern zugeordnet sind, werden in der Abfragezeichenfolge platziert.
public IActionResult Index()
{
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url);
}
Der vorangehende Code generiert /Products/Buy/17?color=red
.
Der folgende Code generiert eine absolute URL:
public IActionResult Index2()
{
var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
// Returns https://localhost:5001/Products/Buy/17
return Content(url);
}
Verwenden Sie eine der folgenden Optionen, um eine absolute URL zu erstellen:
- Eine Überladung, die ein
protocol
akzeptiert. Beispiel: der vorangehende Code. - LinkGenerator.GetUriByAction, der standardmäßig absolute URIs generiert
Generieren von URLs nach Route
Im vorangehenden Code wurde das Generieren einer URL durch das Übergeben des Controller- und Aktionsnamens veranschaulicht. IUrlHelper
stellt außerdem die Url.RouteUrl-Methodengruppe bereit. Diese Methoden ähneln Url.Action, kopieren jedoch nicht die aktuellen Werte action
und controller
in die Routenwerte. Die häufigste Verwendung von Url.RouteUrl
:
- Sie gibt einen Routennamen an, um die URL zu generieren.
- Sie gibt im Allgemeinen keinen Controller- oder Aktionsnamen an.
public class UrlGeneration2Controller : Controller
{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
Die folgende Razor-Datei generiert einen HTML-Link zu Destination_Route
:
<h1>Test Links</h1>
<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>
Generieren von URLs in HTML und Razor
IHtmlHelper stellt die HtmlHelper-Methoden Html.BeginForm und Html.ActionLink bereit, um jeweils <form>
- und <a>
-Elemente zu generieren. Diese Methoden verwenden die Url.Action-Methode, um eine URL zu generieren, und akzeptieren ähnliche Argumente. Die Url.RouteUrl
-Begleiter für HtmlHelper
sind Html.BeginRouteForm
und Html.RouteLink
, die ähnliche Funktionen aufweisen.
Taghilfsprogramme generieren URLs mit den Taghilfsprogrammen form
und <a>
. Beide implementieren mit IUrlHelper
. Weitere Informationen finden Sie unter Taghilfsprogramme in Formularen.
In Ansichten ist IUrlHelper
über die Url
-Eigenschaft für jede Ad-hoc-URL-Generierung verfügbar, die keine der oben genannten ist.
URL-Generierung in Aktionsergebnissen
In den obigen Beispielen wurde gezeigt, wie IUrlHelper
in einem Controller verwendet wird. Diese dient üblicherweise dazu, eine URL im Rahmen eines Aktionsergebnisses zu generieren.
Die Basisklassen ControllerBase und Controller stellen Hilfsmethoden für Aktionsergebnisse bereit, die auf eine andere Aktionen verweisen. Eine typische Verwendung besteht darin, nach dem Akzeptieren einer Benutzereingabe weiterzuleiten:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
if (ModelState.IsValid)
{
// Update DB with new details.
ViewData["Message"] = $"Successful edit of customer {id}";
return RedirectToAction("Index");
}
return View(customer);
}
Die Factorymethoden der Aktionsergebnisse (wie RedirectToAction und CreatedAtAction) folgen einem ähnlichen Muster wie die Methoden für IUrlHelper
.
Sonderfall für dedizierte herkömmliche Routen
Beim herkömmlichen Routing können Sie eine bestimmte Art von Routendefinition verwenden, die als dedizierte herkömmliche Route bezeichnet wird. Die Route namens blog
im folgenden Beispiel ist eine dedizierte herkömmliche Route.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Unter Verwendung der vorangegangenen Routendefinitionen erzeugt Url.Action("Index", "Home")
den URL-Pfad /
mit der Route default
. Aber warum ist das so? Man könnte meinen, dass die Routenwerte { controller = Home, action = Index }
ausreichen würden, um eine URL mithilfe von blog
zu generieren, und das Ergebnis wäre /blog?action=Index&controller=Home
.
Dedizierte herkömmliche Routen nutzen ein spezielles Verhalten von Standardwerten, die keinen entsprechenden Routenparameter besitzen, der verhindert, dass die Route bei der URL-Generierung „zu gierig“ wird. In diesem Fall sind die Standardwerte { controller = Blog, action = Article }
, und weder controller
noch action
werden als Routenparameter verwendet. Wenn das Routing die URL-Generierung ausführt, müssen die angegebenen Werte mit den Standardwerten übereinstimmen. Die URL-Generierung mithilfe von blog
schlägt fehl, da die Werte { controller = Home, action = Index }
nicht mit { controller = Blog, action = Article }
übereinstimmen. Routing greift dann wieder auf default
zurück, was erfolgreich ausgeführt wird.
Bereiche
Bereiche sind ein Feature von MVC, das für die Organisation von verwandten Funktionalitäten in eine Gruppe als Folgendes verwendet wird:
- Separater Routingnamespace für Controlleraktionen
- Separate Ordnerstruktur für Ansichten
Mithilfe von Bereichen kann eine App mehrere Controller mit demselben Namen haben – solange sie verschiedene Bereiche haben. Mithilfe von Bereichen wird außerdem eine Hierarchie erstellt, damit das Routing durch Hinzufügen eines anderen Routenparameters ausgeführt werden kann: area
zu controller
und action
. In diesem Abschnitt wird erläutert, wie Routing mit Bereichen interagiert. Weitere Informationen zur Verwendung von Bereichen mit Ansichten finden Sie unter Bereiche.
Im folgenden Beispiel wird MVC konfiguriert, sodass es die herkömmliche Standardroute und eine area
-Route für einen area
namens Blog
verwendet:
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});
Im vorherigen Code wird MapAreaControllerRoute aufgerufen, um die "blog_route"
zu erstellen. Der zweite Parameter, "Blog"
, ist der Bereichsname.
Beim Abgleich mit einem URL-Pfad wie /Manage/Users/AddUser
generiert die "blog_route"
-Route die Routenwerte { area = Blog, controller = Users, action = AddUser }
. Der area
-Routenwert wird von einem Standardwert für area
erzeugt. Die von MapAreaControllerRoute
erstellte Route entspricht dem Folgenden:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});
MapAreaControllerRoute
erstellt mit einem Standardwert und einer Einschränkung für area
sowie mit dem bereitgestellten Bereichsnamen (in diesem Fall Blog
) eine Route. Der Standardwert stellt sicher, dass die Route immer { area = Blog, ... }
erzeugt, die Einschränkung erfordert den Wert { area = Blog, ... }
für URL-Generierung.
Beim herkömmlichen Routing ist die Reihenfolge wichtig. Routen mit Bereichen werden im Allgemeinen früher aufgeführt als die spezifischeren Routen ohne Bereich.
Die Routenwerte { area = Blog, controller = Users, action = AddUser }
im vorherigen Beispiel werden der folgenden Aktion zugeordnet:
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
Das Attribut [Area] kennzeichnet einen Controller als Teil eines Bereichs. Dieser Controller befindet sich im Blog
-Bereich. Controller ohne [Area]
-Attribut gehören demnach zu keinem Bereich und stimmen nicht überein, wenn die area
-Route wird vom Routing bereitgestellt wird. Im folgenden Beispiel kann nur der erste aufgelistete Controller die Routenwerte { area = Blog, controller = Users, action = AddUser }
abgleichen.
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
// GET /zebra/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
// GET /users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
Der Namespace der einzelnen Controller wird hier zur Vollständigkeit angezeigt. Wenn die vorherigen Controller denselben Namespace verwenden, wird ein Compilerfehler generiert. Klassennamespaces haben keine Auswirkungen auf das MVC-Routing.
Die ersten beiden Controller gehören zu Bereichen und werden können nur abgleichen, wenn ihr jeweiliger Bereichsname vom area
-Routenwert bereitgestellt wird. Der dritte Controller gehört keinem Bereich an und kann nur abgleichen, wenn vom Routing kein Wert für area
bereitgestellt wird.
Im Hinblick auf das Erkennen keines Werts hat die Abwesenheit des area
-Werts dieselben Auswirkungen, wie wenn der Wert für area
0 (null) oder eine leere Zeichenfolge wäre.
Beim Ausführen einer Aktion innerhalb eines Bereichs ist der Routenwert für area
als Umgebungswert für das Routing verfügbar und kann für die URL-Generierung verwendet werden. Das bedeutet, dass Bereiche bei der URL-Generierung wie im folgenden Beispiel dargestellt standardmäßig beständig sind.
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute(name: "duck_route",
areaName: "Duck",
pattern: "Manage/{controller}/{action}/{id?}");
endpoints.MapControllerRoute(name: "default",
pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
// GET /Manage/users/GenerateURLInArea
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area.
var url = Url.Action("Index", "Home");
// Returns /Manage/Home/Index
return Content(url);
}
// GET /Manage/users/GenerateURLOutsideOfArea
public IActionResult GenerateURLOutsideOfArea()
{
// Uses the empty value for area.
var url = Url.Action("Index", "Home", new { area = "" });
// Returns /Manage
return Content(url);
}
}
}
Der folgende Code erzeugt eine URL zu /Zebra/Users/AddUser
:
public class HomeController : Controller
{
public IActionResult About()
{
var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
return Content($"URL: {url}");
}
Aktionsdefinition
Öffentliche Methoden in Controllern sind Aktionen, mit Ausnahme von Methoden mit NonAction-Attributen.
Beispielcode
- MyDisplayRouteInfo wird von dem NuGet-Paket Rick.Docs.Samples.RouteInfo bereitgestellt und zeigt Routeninformationen an.
- Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Debugdiagnose
Legen Sie für eine ausführliche Routingdiagnoseausgabe Logging:LogLevel:Microsoft
auf Debug
fest. Legen Sie in der Entwicklungsumgebung die Protokollebene in appsettings.Development.json
fest:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}