Freigeben über


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 .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-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 namens ProductsController und eine Details-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 von id = 5, um den id-Parameter auf 5 festzulegen. Weitere Informationen finden Sie unter Modellbindung.

  • {controller=Home} definiert Home als Standard-controller.

  • {action=Index} definiert Index als Standard-action.

  • Das ?-Zeichen in {id?} definiert id 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:

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 optionale idverwendet. 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 der ProductsController.List-Aktion zugeordnet.
  • /Blog/Article/17 wird BlogController.Article zugeordnet, und das Modell bindet den id-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 auf 0 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:

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 die default-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. Diese UseRouting-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 über GET 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:

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 wird id 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 die id-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änkt id 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. Der id-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 mit ProductsApi.ListProducts übereinstimmen.
  • Der URL-Pfad /products/5 kann mit ProductsApi.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 Basis namespace 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 ist My.Application.
  • Der vollständige Name des vorherigen Controllers lautet My.Application.Admin.Controllers.UsersController.
  • Die NamespaceRoutingConvention legt die Controllervorlage auf Admin/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 und action 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 und action sind sowohl Teil der Umgebungswerte als auch der Werte. Die Methode Url.Action verwendet immer die aktuellen Werte von action und controller 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 und d 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

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 namens ProductsController und eine Details-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 von id = 5, um den id-Parameter auf 5 festzulegen. Weitere Informationen finden Sie unter Modellbindung.

  • {controller=Home} definiert Home als Standard-controller.

  • {action=Index} definiert Index als Standard-action.

  • Das ?-Zeichen in {id?} definiert id 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:

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 optionale idverwendet. 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 der ProductsController.List-Aktion zugeordnet.
  • /Blog/Article/17 wird BlogController.Article zugeordnet, und das Modell bindet den id-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 auf 0 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:

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 die default-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. Diese UseRouting-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 über GET 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:

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 wird id 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 die id-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änkt id 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. Der id-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 mit ProductsApi.ListProducts übereinstimmen.
  • Der URL-Pfad /products/5 kann mit ProductsApi.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 Basis namespace 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 ist My.Application.
  • Der vollständige Name des vorherigen Controllers lautet My.Application.Admin.Controllers.UsersController.
  • Die NamespaceRoutingConvention legt die Controllervorlage auf Admin/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 und action 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 und action sind sowohl Teil der Umgebungswerte als auch der Werte. Die Methode Url.Action verwendet immer die aktuellen Werte von action und controller 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 und d 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

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