Routing zu Controlleraktionen in ASP.NET Core

Von Ryan Nowak, Kirk Larkin und Rick Anderson

ASP.NET Core Controller verwenden die Routing-Middleware, um die URLs eingehender 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 konventionell oderAttributrouten ausgeführt. Wenn Sie eine Route auf dem Controller oder der Aktion platzieren, wird sie attributroutet. Weitere Informationen finden Sie im Abschnitt Gemischtes Routing.

Dieses Dokument hat folgende Eigenschaften:

  • Erläutert die Interaktionen zwischen MVC und Routing:
    • Wie typische MVC-Apps Routingfeatures nutzen.
    • Deckt beides ab:
      • 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 .
    • Details zu erweitertem Routing finden Sie unter Routing .
  • Bezieht sich auf das Standardroutingsystem namens Endpunktrouting. Es ist möglich, Controller mit der vorherigen Version des Routings aus Kompatibilitätsgründen zu verwenden. Anweisungen finden Sie im Migrationsleitfaden 2.2-3.0 .

Einrichten einer herkömmlichen Route

Die ASP.NET Core MVC-Vorlage generiert herkömmlichen Routingcode wie folgt:

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 } , indem der Pfad tokenisiert wird. 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.

  • /Products/Details/5 model bindet den Wert von, id = 5 um den id Parameter auf 5festzulegen. 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.
  • Entspricht dem URL-Pfad /.

  • Erzeugt die Routenwerte { controller = Home, action = Index }.

Die Werte für controller und action verwenden die Standardwerte. id erzeugt keinen Wert, da im URL-Pfad kein entsprechendes Segment vorhanden ist. / stimmt nur überein, wenn eine HomeController - und Index -Aktion vorhanden ist:

public class HomeController : Controller
{
    public IActionResult Index() { ... }
}

Mithilfe der vorherigen Controllerdefinition und 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 Standardcontroller Home und Index -aktion der Routenvorlage. Der URL-Pfad /Home verwendet die Standardaktion Index der Routenvorlage.

Mit der Hilfsmethode MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Ersetzt:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Wichtig

Routing wird mithilfe der UseRouting Middleware und UseEndpoints konfiguriert. So verwenden Sie Controller:

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 obige ist ein Beispiel für eine konventionelle Route. Es wird als konventionelles Routing bezeichnet, da es eine Konvention für URL-Pfade festlegt:

  • Das erste Pfadsegment wird {controller=Home}dem Controllernamen zugeordnet.
  • Das zweite Segment, {action=Index}, wird dem Aktionsnamen zugeordnet.
  • Das dritte Segment {id?} wird für ein optionales idverwendet. Der ? in {id?} macht es optional. id wird verwendet, um einer Modellentität zuzuordnen.

Bei Verwendung dieser default Route wird der URL-Pfad verwendet:

  • /Products/List wird der ProductsController.List Aktion zugeordnet.
  • /Blog/Article/17 ordnet zu BlogController.Article , und modell bindet den Parameter in der id Regel an 17.

Diese Zuordnung:

  • Basiert nur auf den Controller- und Aktionsnamen.
  • Basiert nicht auf Namespaces, Quelldateispeicherorten oder Methodenparametern.

Die Verwendung des herkömmlichen Routings mit der Standardroute ermöglicht das Erstellen der App, ohne dass für jede Aktion ein neues URL-Muster erstellt werden muss. Für eine App mit Aktionen im CRUD-Stil mit Konsistenz für die URLs zwischen den Controllern:

  • Vereinfacht den Code.
  • Macht die Benutzeroberfläche vorhersagbarer.

Warnung

Der 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. Wenn in der URL nicht angegeben wird, id gilt im Allgemeinen Folgendes:

  • id wird durch Modellbindung auf 0 festgelegt.
  • Es wird keine Entität in der Datenbank gefunden, die abgleicht id == 0.

Attributrouting bietet eine differenzierte Steuerung, um die ID für einige Aktionen und nicht für andere Aktionen erforderlich zu machen. Gemäß der Konvention enthält die Dokumentation optionale Parameter, z. B 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 Web-UI-Apps benötigt wird. Für größere Web-UI-Apps genügt häufig eine andere Route, die Bereiche verwendet.

MapControllerRoute und MapAreaRoute :

  • Weisen Sie ihren Endpunkten basierend auf der aufgerufenen Reihenfolge automatisch einen Auftragswert zu.

Endpunktrouting in ASP.NET Core:

  • Weist kein Konzept für Routen auf.
  • Bietet keine Bestellgarantien 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.

Attributrouting wird weiter unten in diesem Dokument erläutert.

Mehrere konventionelle Routen

Mehrere herkömmliche Routen können konfiguriert werden, indem weitere Aufrufe von MapControllerRoute und MapAreaControllerRoutehinzugefügt werden. Dies ermöglicht das Definieren mehrerer Konventionen oder das Hinzufügen konventioneller Routen, die einer bestimmten Aktion zugewiesen 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 konventionelle Route. Sie wird als dedizierte konventionelle Route bezeichnet, weil:

Da controller und action nicht als Parameter in der Routenvorlage "blog/{*article}" angezeigt werden:

  • Sie können nur die Standardwerte { controller = "Blog", action = "Article" }haben.
  • Diese Route wird immer der Aktion BlogController.Articlezugeordnet.

/Blog, /Blog/Articleund /Blog/{any-string} sind die einzigen URL-Pfade, die der Blogroute entsprechen.

Für das vorherige Beispiel gilt Folgendes:

  • blog route hat eine höhere Priorität für Übereinstimmungen als die default Route, da sie zuerst hinzugefügt wird.
  • Ein Beispiel für das Routing im Slug-Stil , bei dem es typisch ist, einen Artikelnamen als Teil der URL zu verwenden.

Warnung

In ASP.NET Core funktioniert routing nicht:

  • Definieren Sie ein Konzept, das als Route bezeichnet wird. UseRouting fügt der Middlewarepipeline einen Routenabgleich hinzu. Die UseRouting Middleware untersucht die in der App definierten Endpunkte und wählt basierend auf der Anforderung die beste Endpunkt-Übereinstimmung aus.
  • Stellen Sie Garantien für die Ausführungsreihenfolge der Erweiterbarkeit bereit, z. IRouteConstraint B. oder IActionConstraint.

Referenzmaterial zum Routing finden Sie unter Routing .

Konventionelle Routingreihenfolge

Herkömmliches Routing entspricht nur einer Kombination aus Aktion und Controller, die von der App definiert werden. Dies soll Fälle vereinfachen, in denen sich herkömmliche Routen überschneiden. Hinzufügen von Routen mit MapControllerRoute, MapDefaultControllerRouteund MapAreaControllerRoute automatisches Zuweisen eines Auftragswerts zu ihren Endpunkten 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. Im Allgemeinen sollten Routen mit Bereichen früher platziert werden, da sie spezifischer sind als Routen ohne Gebiet. Dedizierte konventionelle Routen mit Catch-All-Routenparametern wie {*article} können eine Route zu gierig machen, was bedeutet, dass sie mit URLs übereinstimmt, die von anderen Routen abgeglichen werden sollen. Fügen Sie die gierigen Routen später in die Routingtabelle ein, um gierige Ü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 einen der folgenden Aktionen ausführen:

  • Wählen Sie den besten Kandidaten aus.
  • Löst eine Ausnahme aus.

Zum 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 übereinstimmen:

  • Der URL-Pfad /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.

So beheben Sie die richtige Route:

  • Edit(int, Product) wird ausgewählt, wenn die Anforderung ein HTTP POSTist.
  • Edit(int) wird ausgewählt, wenn das HTTP-Verb ein anderes ist. Edit(int) wird im Allgemeinen über GETaufgerufen.

HttpPostAttribute, [HttpPost]wird für das Routing bereitgestellt, sodass sie basierend auf der HTTP-Methode der Anforderung auswählen kann. Die HttpPostAttribute entspricht Edit(int, Product) besser als Edit(int).

Es ist wichtig, die Rolle von Attributen wie HttpPostAttributezu verstehen. Ähnliche Attribute werden für andere HTTP-Verben definiert. Beim herkömmlichen Routing ist es üblich, dass Aktionen denselben Aktionsnamen verwenden, wenn sie Teil eines Formulars zum Anzeigen und Senden 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, die die mehreren übereinstimmend zugeordneten Endpunkte auflistet.

Herkömmliche Routennamen

Die Zeichenfolgen und "default" in den folgenden Beispielen "blog" 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 für die URL-Generierung verwendet werden. Die Verwendung einer benannten Route vereinfacht die URL-Erstellung, wenn die Reihenfolge der Routen die URL-Generierung kompliziert machen könnte. Routennamen müssen anwendungsweit eindeutig sein.

Routennamen:

  • Hat 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. Die Begriffe Routenname und Endpunktname:

  • Sind austauschbar.
  • Welche in der Dokumentation und im Code verwendet wird, hängt von der beschriebenen API ab.

Attributrouting für REST APIs

REST APIs sollten Attributrouting verwenden, um die Funktionalität der App als Eine Reihe von Ressourcen zu modellieren, in 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 aufgerufen, MapControllers um Routingcontroller für Attributs zuzuordnen.

Siehe folgendes Beispiel:

  • HomeController entspricht einer Reihe von URLs, die der Übereinstimmung der herkömmlichen Standardroute {controller=Home}/{action=Index}/{id?} ähneln.
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 HomeController.Index Aktion wird für jeden der URL-Pfade /, /Home, /Home/Indexoder /Home/Index/3ausgeführt.

In diesem Beispiel wird ein wichtiger Programmierunterschied zwischen Attributrouting und herkömmlichem Routing hervorgehoben. Attributrouting erfordert mehr Eingaben, um eine Route anzugeben. Die herkömmliche Standardroute verarbeitet Routen prägnanter. Attributrouting ermöglicht und erfordert jedoch eine präzise Kontrolle darüber, welche Routenvorlagen für jede Aktion gelten.

Beim Attributrouting spielen der Controller und der Aktionsname keine Rolle, bei der die Aktion abgeglichen wird, es sei denn, es wird ein 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 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 gilt für [Route("[controller]/[action]")] den Controller:

[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 vorherigen Code müssen die Index Methodenvorlagen den Routenvorlagen vorangestellt / werden oder ~/ . 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 Routenvorlagenauswahl finden Sie unter Rangfolge der Routenvorlage.

Reservierte Routingnamen

Die folgenden Schlüsselwörter sind reservierte Routenparameternamen bei Verwendung von Controllern oder Razor Pages:

  • action
  • area
  • controller
  • handler
  • page

Die Verwendung 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 eine Razor Seite 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 Linkgenerationen, 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-Verbattributen

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 und wird daher id an die "api/[controller]" Vorlage auf dem Controller angefügt. Die Methodenvorlage ist "api/[controller]/"{id}"". Daher entspricht diese Aktion nur GET-Anforderungen für das Formular /api/test2/xyz,/api/test2/123 usw/api/test2/{any string}.
    [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. Eine GET-Anforderung an /api/test2/int/abc:
    • Entspricht dieser Aktion nicht.
    • 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 sich aber nicht auf id Werte, die in eine ganze Zahl konvertiert werden können. Eine GET-Anforderung an /api/test2/int2/abc:
    • Entspricht dieser Route.
    • Die Modellbindung kann nicht in eine ganze Zahl konvertiert abc werden. Der id Parameter der Methode ist ganzzahliger Wert.
    • Gibt eine ungültige 400-Anforderung zurück, da die Modellbindung nicht in eine ganze Zahl konvertiertabc werden konnte.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Attributrouting kann Attribute wie HttpPostAttribute, HttpPutAttributeund HttpDeleteAttributeverwendenHttpMethodAttribute. Alle HTTP-Verbattribute akzeptieren eine Routenvorlage. Das folgende Beispiel zeigt zwei Aktionen, die der gleichen Routenvorlage entsprechen:

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

Verwenden des URL-Pfads /products3:

  • Die MyProductsController.ListProducts Aktion wird ausgeführt, wenn das HTTP-Verb ist GET.
  • Die MyProductsController.CreateProduct Aktion wird ausgeführt, wenn das HTTP-Verb ist POST.

Beim Erstellen einer REST API ist es selten erforderlich, dass Sie eine Aktionsmethode verwenden [Route(...)] müssen, da die Aktion alle HTTP-Methoden akzeptiert. Es ist besser, das spezifischere HTTP-Verb-Attribut zu verwenden, um genau zu sein, was Ihre API unterstützt. REST Von APIs-Clients wird erwartet, dass sie wissen, welche Pfade und HTTP-Verben bestimmten logischen Vorgängen zugeordnet werden.

REST APIs sollten Attributrouting verwenden, um die Funktionalität der App als Gruppe von Ressourcen zu modellieren, in 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 id ist 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);
    }
}

Die Products2ApiController.GetProduct(int) Aktion:

  • Wird mit URL-Pfad wie ausgeführt /products2/3
  • Wird nicht mit dem URL-Pfad /products2ausgefü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

Der folgende Code definiert einen Routennamen von Products_List:

[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:

  • Hat keine Auswirkungen auf das URL-Abgleichsverhalten des Routings.
  • 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 ({id?}) definiert. Die Möglichkeit, APIs präzise anzugeben, hat Vorteile, z. B. das Zulassen /products und /products/5 Die Verteilung an verschiedene Aktionen.

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 übereinstimmen ProductsApi.ListProducts
  • Der URL-Pfad /products/5 kann mit übereinstimmen ProductsApi.GetProduct(int).

Beide Aktionen stimmen nur mit HTTP GET überein, da sie mit dem [HttpGet] -Attribut gekennzeichnet 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. Das folgende Beispiel entspricht einer Reihe von URL-Pfaden, 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 Kombiniert mit [Route("Home")] Definiert die Routenvorlage
[Route("")] Ja "Home"
[Route("Index")] Ja "Home/Index"
[Route("/")] Nein ""
[Route("About")] Ja "Home/About"

Attributroutenreihenfolge

Das Routing erstellt eine Struktur und entspricht allen Endpunkten gleichzeitig:

  • Die Routeneinträge verhalten sich wie in einer idealen Reihenfolge.
  • Die spezifischsten Routen haben die Möglichkeit, vor den allgemeineren Routen auszuführen.

Beispielsweise ist eine Attributroute wie blog/search/{topic} spezifischer als eine Attributroute wie blog/{*article}. Die blog/search/{topic} Route hat standardmäßig eine höhere Priorität, da sie spezifischer ist. Bei Verwendung des herkömmlichen Routings ist der Entwickler dafür verantwortlich, Routen in der gewünschten Reihenfolge zu platzieren.

Attributrouten können eine Bestellung mithilfe der Order -Eigenschaft konfigurieren. Alle vom Framework bereitgestellten Routenattribute enthalten Order . Routen werden entsprechend einer aufsteigenden Reihenfolge der Order-Eigenschaft verarbeitet. Die Standardreihenfolge ist 0. Festlegen einer Route mithilfe von Order = -1 Ausführungen vor Routen, die keine Reihenfolge festlegen. Festlegen einer Route mithilfe von Order = 1 Ausführungen nach der Standardroutenreihenfolge.

Vermeiden Sie abhängig von Order. Wenn der URL-Bereich einer App explizite Bestellwerte für die korrekte Weiterleitung erfordert, ist dies wahrscheinlich auch für Clients verwirrend. Im Allgemeinen wählt das Attributrouting die richtige Route mit URL-Abgleich aus. Wenn die Standardreihenfolge, die für die URL-Generierung verwendet wird, nicht funktioniert, ist die Verwendung eines Routennamens als Überschreibung in der Regel einfacher als das Anwenden der Order Eigenschaft.

Betrachten Sie die folgenden beiden Controller, die beide den Routenabgleich /homedefinieren:

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

Die Anforderung /home mit dem vorherigen Code löst eine Ausnahme ähnlich der folgenden aus:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

Das Hinzufügen Order zu einem der Routenattribute löst die Mehrdeutigkeit auf:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

Führt mit dem obigen /home Code den HomeController.Index Endpunkt aus. Um zum zu MyDemoController.MyIndexgelangen, fordern Sie an /home/MyIndex. Hinweis:

  • Der vorangehende Code ist ein Beispiel oder ein schlechter Routingentwurf. Es wurde verwendet, um die -Eigenschaft zu veranschaulichen Order .
  • Die Order -Eigenschaft löst nur die Mehrdeutigkeit auf. Diese Vorlage kann nicht abgeglichen werden. Es wäre besser, die [Route("Home")] Vorlage zu entfernen.

Informationen zur Routenreihenfolge mit Razor Pages finden Sie Razor unter Seitenrouten- und App-Konventionen: Routenreihenfolge.

In einigen Fällen wird ein HTTP 500-Fehler mit mehrdeutigen Routen zurückgegeben. Verwenden Sie die Protokollierung , um zu sehen, welche Endpunkte die verursacht haben AmbiguousMatchException.

Ersetzen von Token in Routenvorlagen [Controller], [Aktion], [Bereich]

Der Einfachheit halber unterstützen Attributrouten die Tokenersetzung , indem sie ein Token in eckige Klammern ([, ]) einschließen. Die Token [action], [area]und [controller] werden durch die Werte des Aktionsnamens, des Bereichsnamens und des Controllernamens der Aktion ersetzt, in der 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();
}
  • Entspricht /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Entspricht /Products0/Edit/{id}

Die Tokenersetzung tritt im letzten Schritt der Erstellung von Attributrouten auf. Das vorherige Beispiel verhält sich genauso wie der folgende Code:

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, teilen Sie uns dies in diesem GitHub-Diskussionsproblem mit, wenn Sie die Codekommentare in Ihrer Muttersprache sehen möchten.

Attributrouten können auch mit Vererbung kombiniert werden. Dies ist in Kombination mit tokenersetzung leistungsstark. Tokenersetzung gilt auch für Routennamen, die durch Attributrouten definiert werden. [Route("[controller]/[action]", Name="[controller]_[action]")]generiert einen eindeutigen Routennamen für jede Aktion:

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

Um dem Literaltokenersetzungstrennzeichen [ oder ]zu entsprechen, escapen Sie es, indem Sie das Zeichen ([[ oder ]]) wiederholen.

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 SubscriptionManagement Routenwert 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 Methode stimmt mit überein /subscription-management/list-all.

Ist RouteTokenTransformerConvention 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 unter 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 Sie mehrere Routenattribute auf dem Controller platzieren, wird jedes mit jedem Routenattribute für die Aktionsmethoden kombiniert:

[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 HTTP-Verbrouteneinschränkungen implementieren .IActionConstraint

Wenn mehrere Routenattribute, die implementieren IActionConstraint , für eine Aktion platziert werden:

  • Jede Aktionseinschränkung wird mit der auf den Controller angewendeten Routenvorlage kombiniert.
[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 nützlich und leistungsstark erscheinen. Es ist besser, den URL-Bereich Ihrer App einfach und gut definiert zu halten. Verwenden Sie mehrere Routen für Aktionen nur bei Bedarf, z. B. zur Unterstützung vorhandener Clients.

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

Im vorherigen Code [HttpPost("product14/{id:int}")] wendet eine Routeneinschränkung an. Die Products14Controller.ShowProduct Aktion wird nur durch URL-Pfade wie abgeglichen /product14/3. Der Routenvorlagenteil {id:int} beschränkt dieses Segment auf nur ganze Zahlen.

Eine ausführliche Beschreibung der Syntax der Routenvorlage finden Sie unter Routenvorlagenreferenz.

Benutzerdefinierte Routenattribute mit IRouteTemplateProvider

Alle Routenattribute implementieren IRouteTemplateProvider. Die ASP.NET Core Runtime:

  • Sucht nach Attributen für Controllerklassen und Aktionsmethoden, wenn die App gestartet wird.
  • Verwendet die Attribute, die implementieren IRouteTemplateProvider , um den anfänglichen Satz von 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 vorherige Get Methode gibt zurück Order = 2, Template = api/MyTestApi.

Verwenden des Anwendungsmodells zum Anpassen von Attributrouten

Das Anwendungsmodell:

  • Ist ein Objektmodell, das beim Start in Program.cserstellt wird.
  • 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 gesammelt werden. Die Daten aus Routenattributen werden von der IRouteTemplateProvider -Implementierung bereitgestellt. Konventionen:

  • Kann geschrieben werden, um das Anwendungsmodell zu ändern, um das Routingverhalten anzupassen.
  • Werden beim Starten der App gelesen.

Dieser Abschnitt zeigt ein einfaches Beispiel für die Anpassung des Routings mithilfe des Anwendungsmodells. Mit dem folgenden Code werden Routen ungefähr an die Ordnerstruktur des Projekts aneinander aneinander gerandet.

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 attributgeleitet werden:

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Der folgende Controller verwendet NamespaceRoutingConventionbeispielsweise 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:

  • Führt nichts aus, wenn der Controller mit einem Attribut weitergeleitet wird.
  • Legt die Controllervorlage basierend auf fest namespace, wobei die Basis namespace entfernt wurde.

Kann NamespaceRoutingConvention in Program.csangewendet 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 vorangehenden Controllers lautet My.Application.Admin.Controllers.UsersController.
  • Legt NamespaceRoutingConvention die Controllervorlage auf fest Admin/Controllers/Users/[action]/{id?.

Kann NamespaceRoutingConvention auch als Attribut auf einem 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 die Verwendung von konventionellem Routing und Attributrouting kombinieren. Es ist typisch, herkömmliche Routen für Controller zu verwenden, die HTML-Seiten für Browser bereitstellen, und Attributrouting für Controller, die APIs bereitstellen REST .

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. Jedes Routenattribute auf dem Controller führt alle Aktionen im Controller-Attribut durch.

Attributrouting und herkömmliches Routing verwenden dieselbe Routing-Engine.

Routing mit Sonderzeichen

Das Routing mit Sonderzeichen kann zu unerwarteten Ergebnissen führen. Betrachten Sie beispielsweise einen Controller mit der folgenden Aktionsmethode:

[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 enthalten sind, können unerwartete Ergebnisse auftreten:

ASCII Codiert
/ %2F
+

Routenparameter sind nicht immer URL-decodiert. Dieses Problem kann in Zukunft behoben werden. Weitere Informationen finden Sie in diesem GitHub-Problem.

URL-Generierung und Umgebungswerte

Apps können Routing-URL-Generierungsfeatures verwenden, um URL-Links zu Aktionen zu generieren. Durch das Generieren von URLs werden hartcodierende URLs eliminiert, sodass Code robuster und verwaltbarer wird. Dieser Abschnitt konzentriert sich auf die von MVC bereitgestellten URL-Generierungsfeatures und behandelt nur die Grundlagen der URL-Generierung. Eine detaillierte Beschreibung der URL-Generierung finden Sie unter Routing in ASP.NET Core.

Die IUrlHelper Schnittstelle ist das zugrunde liegende Element der Infrastruktur zwischen MVC und Routing für die 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 über die Controller.Url -Eigenschaft verwendet, um eine URL für eine andere 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 Variablen die URL-Pfadzeichenfolge /UrlGeneration/Destination. Dieser URL-Pfad wird durch Routing erstellt, indem Folgendes kombiniert wird:

  • Die Routenwerte aus der aktuellen Anforderung, die als Ambient-Werte bezeichnet werden.
  • Die an Url.Action übergebenen Werte und ersetzen diese Werte in der 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:

  • Verwenden Sie einen Standardwert, wenn er über einen verfügt.
  • Übersprungen werden, wenn dies optional ist. Beispiel: id aus der Routenvorlage {controller}/{action}/{id?}.

Die URL-Generierung schlägt fehl, wenn ein erforderlicher Routenparameter keinen entsprechenden Wert aufweist. 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 von Url.Action wird von konventionellem Routing ausgegangen. Die URL-Generierung funktioniert ähnlich beim Attributrouting, obwohl sich die Konzepte unterscheiden. Mit konventionellem Routing:

  • 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.

LinkGeneratorwurde in ASP.NET Core 3.0 als Alternative zu IUrlHelperhinzugefügt. LinkGenerator bietet ähnliche, aber flexiblere Funktionen. Jede Methode für verfügt auch IUrlHelper über eine entsprechende Familie von Methoden für LinkGenerator .

Generieren von URLs nach Aktionsnamen

Url.Action, LinkGenerator.GetPathByAction und alle zugehörigen Überladungen sind zum Generieren des Zielendpunkts konzipiert, indem sie einen Controllernamen und aktionsnamen angeben.

Bei Verwendung von Url.Actionwerden die aktuellen Routenwerte für controller und action von der Runtime bereitgestellt:

  • Der Wert von controller und action ist Teil sowohl von Ambient-Werten als auch von Werten. Die -Methode Url.Action verwendet immer die aktuellen Werte von action und controller und generiert einen URL-Pfad, der an die aktuelle Aktion weitergeleitet wird.

Beim Routing wird versucht, die Werte in Umgebungswerten zum Ausfüllen von Informationen zu verwenden, die beim Generieren einer URL nicht angegeben wurden. 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:

  • Der Wert { d = David } 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:

  • Beide Werte { c = Carol, d = David } werden ignoriert.
  • Es ist kein Wert mehr für d vorhanden, 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 erwarten, dass dieses Problem mit der Standardroute {controller}/{action}/{id?}auftritt. Dieses Problem ist in der Praxis selten, da Url.Action immer explizit ein controller - und action -Wert angegeben wird.

Mehrere Überladungen von Url.Action verwenden ein Routenwertobjekt, um Werte für andere Routenparameter als controller und actionbereitzustellen. Das Routenwertobjekt wird häufig mit idverwendet. Beispiel: Url.Action("Buy", "Products", new { id = 17 }). Das Routenwertobjekt:

  • Gemäß Konvention ist in der Regel ein Objekt des anonymen Typs.
  • Kann ein IDictionary<>oder ein 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 protocolakzeptiert. Beispiel: der vorangehende Code.
  • LinkGenerator.GetUriByAction, der standardmäßig absolute URIs generiert.

Generieren von URLs nach Route

Im obigen Code wurde das Generieren einer URL durch Übergeben des Controllers und des Aktionsnamens veranschaulicht. IUrlHelper stellt außerdem die Url.RouteUrl-Familie von Methoden bereit. Diese Methoden ähneln Url.Action, kopieren aber nicht die aktuellen Werte von action und controller in die Routenwerte. Die häufigste Verwendung von Url.RouteUrl:

  • Gibt einen Routennamen an, um die URL zu generieren.
  • 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 Elemente bzw. zu generieren <form><a> . Diese Methoden verwenden die Url.Action-Methode , um eine URL zu generieren, und sie 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 vorherigen Beispielen wurde die Verwendung IUrlHelper in einem Controller gezeigt. Die häufigste Verwendung in einem Controller besteht darin, eine URL als Teil eines Aktionsergebnisses zu generieren.

Die Basisklassen ControllerBase und Controller stellen Hilfsmethoden für Aktionsergebnisse bereit, die auf eine andere Aktionen verweisen. Eine typische Verwendung ist die Umleitung nach dem Akzeptieren von Benutzereingaben:

[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 Aktion ergibt Factorymethoden wie RedirectToAction und CreatedAtAction folgen einem ähnlichen Muster wie die Methoden auf IUrlHelper.

Sonderfall für dedizierte herkömmliche Routen

Konventionelles Routing kann eine spezielle Art von Routendefinition verwenden, die als dedizierte konventionelle Route bezeichnet wird. Im folgenden Beispiel ist die Route mit dem Namen blog eine dedizierte konventionelle Route:

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Generiert mithilfe der vorherigen Routendefinitionen Url.Action("Index", "Home") den URL-Pfad / mithilfe der default Route, aber warum? 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 konventionelle Routen basieren auf einem speziellen Verhalten von Standardwerten, die keinen entsprechenden Routenparameter haben, der verhindert, dass die Route bei der URL-Generierung zu gierig ist. 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 übereinstimmen { controller = Blog, action = Article }. Routing greift dann wieder auf default zurück, was erfolgreich ausgeführt wird.

Bereiche

Bereiche sind ein MVC-Feature, das verwendet wird, um verwandte Funktionen in einer Gruppe als separates zu organisieren:

  • Routingnamespace für Controlleraktionen.
  • Ordnerstruktur für Ansichten.

Mithilfe von Bereichen kann eine App über mehrere Controller mit demselben Namen verfügen, sofern sie über unterschiedliche Bereiche verfügen. 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. Ausführliche Informationen zur Verwendung von Bereichen mit Ansichten finden Sie unter Bereiche .

Im folgenden Beispiel wird MVC für die Verwendung der herkömmlichen Standardroute und einer area Route mit dem area Namen konfiguriert Blog:

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 aufgerufen, MapAreaControllerRoute um zu erstellen "blog_route". Der zweite Parameter, "Blog", ist der Name des Bereichs.

Beim Abgleich eines URL-Pfads wie /Manage/Users/AddUsergeneriert die "blog_route" Route die Routenwerte { area = Blog, controller = Users, action = AddUser }. Der area Routenwert wird durch einen Standardwert für areageneriert. Die von MapAreaControllerRoute erstellte Route entspricht folgendem:

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. Im Allgemeinen sollten Routen mit Bereichen früher platziert werden, da sie spezifischer sind als Routen ohne Gebiet.

Im vorherigen Beispiel stimmen die Routenwerte { area = Blog, controller = Users, action = AddUser } mit der folgenden Aktion überein:

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 [Area]- Attribut bezeichnet einen Controller als Teil eines Bereichs. Dieser Controller befindet sich Blog im Bereich. Controller ohne Attribut [Area] sind keine Mitglieder eines Bereichs und stimmen nicht überein, wenn der area Routenwert 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 jedes Controllers wird hier aus Gründen der 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 steht der Routenwert für area als Ambient-Wert für für das Routing zur Verwendung für die URL-Generierung zur Verfügung. 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 generiert 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 auf einem Controller, mit Ausnahme der Methoden mit dem NonAction-Attribut , sind Aktionen.

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 die Routingmiddleware, um die URLs eingehender Anforderungen abzugleichen und aktionen zuzuordnen. Routenvorlagen:

  • Werden in Startcode oder -attributen definiert.
  • Beschreiben, wie URL-Pfade mit Aktionen abgeglichen werden.
  • Dient zum Generieren von URLs für Links. Die generierten Links werden in der Regel in Antworten zurückgegeben.

Aktionen werden entweder konventionell oderattributgeroutet. Durch das Platzieren einer Route auf dem Controller oder der Aktion wird sie attributgeroutet. Weitere Informationen finden Sie im Abschnitt Gemischtes Routing.

Dieses Dokument hat folgende Eigenschaften:

  • Erläutert die Interaktionen zwischen MVC und Routing:
    • Wie typische MVC-Apps Routingfeatures nutzen.
    • Deckt beides ab:
      • 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 zu erweiterten Routingdetails finden Sie unter Routing .
  • Bezieht sich auf das Standardroutingsystem, das in ASP.NET Core 3.0 hinzugefügt wurde, das als Endpunktrouting bezeichnet wird. Es ist möglich, Controller mit der vorherigen Version des Routings aus Kompatibilitätsgründen 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 konventionellen Route

Startup.Configure verfügt in der Regel über Code, der dem folgenden ähnelt, wenn herkömmliches Routing verwendet wird:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Innerhalb des Aufrufs von UseEndpointswird verwendet, MapControllerRoute 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.

  • /Products/Details/5 das Modell bindet den Wert von, id = 5 um den id Parameter auf festzulegen 5. Weitere Informationen finden Sie unter Modellbindung .

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

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

  • Das ? Zeichen in {id?} wird als optional definiert id .

  • 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.

  • Entspricht dem URL-Pfad /.

  • Erzeugt die Routenwerte { controller = Home, action = Index }.

Die Werte für controller und action verwenden die Standardwerte. id erzeugt keinen Wert, da im URL-Pfad kein entsprechendes Segment vorhanden ist. / stimmt nur überein, wenn eine HomeController - und Index -Aktion vorhanden ist:

public class HomeController : Controller
{
  public IActionResult Index() { ... }
}

Mithilfe der vorherigen Controllerdefinition und 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 Standardcontroller Home und Index die Aktion der Routenvorlage. Der URL-Pfad /Home verwendet die Standardaktion Index der Routenvorlage.

Mit der Hilfsmethode MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Ersetzt:

endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

Wichtig

Das Routing wird mithilfe der UseRoutingMiddleware , MapControllerRouteund MapAreaControllerRoute konfiguriert. So verwenden Sie Controller:

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. Es wird als herkömmliches Routing bezeichnet, da es eine Konvention für URL-Pfade festlegt:

  • Das erste Pfadsegment , {controller=Home}, wird dem Controllernamen zugeordnet.
  • Das zweite Segment, {action=Index}, wird dem Aktionsnamen zugeordnet.
  • Das dritte Segment {id?} wird für ein optionales idverwendet. Die ? in {id?} macht es optional. id wird verwendet, um einer Modellentität zuzuordnen.

Bei Verwendung dieser default Route gilt der URL-Pfad:

  • /Products/List wird der ProductsController.List Aktion zugeordnet.
  • /Blog/Article/17 ordnet zu und BlogController.Article modell bindet den Parameter in der id Regel an 17.

Diese Zuordnung:

  • Basiert nur auf dem Controller- und Aktionsnamen.
  • Basiert nicht auf Namespaces, Quelldateispeicherorten oder Methodenparametern.

Die Verwendung des herkömmlichen Routings mit der Standardroute ermöglicht das Erstellen der App, ohne dass für jede Aktion ein neues URL-Muster erstellt werden muss. Für eine App mit Aktionen im CRUD-Stil mit Konsistenz für die URLs zwischen Controllern:

  • Vereinfacht den Code.
  • Macht die Benutzeroberfläche besser vorhersagbar.

Warnung

Der 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, wenn id in der URL nicht angegeben wird:

  • id wird durch Modellbindung auf 0 festgelegt.
  • Es wurde keine Entität in der Datenbank gefunden, die mit übereinstimmt id == 0.

Das Attributrouting bietet eine differenzierte Steuerung, um die id für einige Aktionen und nicht für andere aktionen zu definieren. Gemäß konventionsgemäß enthält die Dokumentation optionale Parameter, z. B 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 Web-UI-Apps benötigt wird. Für größere Web-UI-Apps ist häufig eine andere Route mit Areas erforderlich.

MapControllerRoute und MapAreaRoute :

  • Weisen Sie ihren Endpunkten automatisch einen Bestellwert basierend auf der Reihenfolge zu, in der sie aufgerufen werden.

Endpunktrouting in ASP.NET Core 3.0 und höher:

  • Weist kein Konzept für Routen auf.
  • Bietet keine Bestellgarantien für die Ausführung der Erweiterbarkeit, alle Endpunkte werden gleichzeitig verarbeitet.

Wenn Sie die Protokollierung aktivieren, erfahren Sie, wie die integrierten Routingimplementierungen (z.B. Route) Zuordnungen für Anforderungen ermitteln.

Attributrouting wird weiter unten in diesem Dokument erläutert.

Mehrere konventionelle Routen

Mehrere herkömmliche Routen können innerhalb UseEndpoints von hinzugefügt werden, indem weitere Aufrufe von MapControllerRoute und MapAreaControllerRoutehinzugefügt werden. Dies ermöglicht das Definieren mehrerer Konventionen oder das Hinzufügen konventioneller Routen, die einer bestimmten Aktion zugewiesen 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 konventionelle Route. Sie wird als dedizierte konventionelle Route bezeichnet, weil:

Da controller und action nicht als Parameter in der Routenvorlage "blog/{*article}" angezeigt werden:

  • Sie können nur die Standardwerte { controller = "Blog", action = "Article" }haben.
  • Diese Route wird immer der Aktion BlogController.Articlezugeordnet.

/Blog, /Blog/Articleund /Blog/{any-string} sind die einzigen URL-Pfade, die der Blogroute entsprechen.

Für das vorherige Beispiel gilt Folgendes:

  • blog route hat eine höhere Priorität für Übereinstimmungen als die default Route, da sie zuerst hinzugefügt wird.
  • Ein Beispiel für das Routing im Slug-Stil , bei dem es typisch ist, einen Artikelnamen als Teil der URL zu verwenden.

Warnung

In ASP.NET Core Version 3.0 und höher gilt folgendes Nicht:

  • Definieren Sie ein Konzept, das als Route bezeichnet wird. UseRouting fügt der Middlewarepipeline einen Routenabgleich hinzu. Die UseRouting Middleware untersucht die in der App definierten Endpunkte und wählt basierend auf der Anforderung die beste Endpunkt-Übereinstimmung aus.
  • Stellen Sie Garantien für die Ausführungsreihenfolge der Erweiterbarkeit bereit, z. IRouteConstraint B. oder IActionConstraint.

Referenzmaterial zum Routing finden Sie unter Routing .

Konventionelle Routingreihenfolge

Herkömmliches Routing entspricht nur einer Kombination aus Aktion und Controller, die von der App definiert werden. Dies soll Fälle vereinfachen, in denen sich herkömmliche Routen überschneiden. Hinzufügen von Routen mit MapControllerRoute, MapDefaultControllerRouteund MapAreaControllerRoute automatisches Zuweisen eines Auftragswerts zu ihren Endpunkten 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. Im Allgemeinen sollten Routen mit Bereichen früher platziert werden, da sie spezifischer sind als Routen ohne Gebiet. Dedizierte konventionelle Routen mit Catch-All-Routenparametern wie {*article} können eine Route zu gierig machen, was bedeutet, dass sie mit URLs übereinstimmt, die von anderen Routen abgeglichen werden sollen. Fügen Sie die gierigen Routen später in die Routingtabelle ein, um gierige Ü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 einen der folgenden Aktionen ausführen:

  • Wählen Sie den besten Kandidaten aus.
  • Löst eine Ausnahme aus.

Zum 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 übereinstimmen:

  • Der URL-Pfad /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.

So beheben Sie die richtige Route:

  • Edit(int, Product) wird ausgewählt, wenn die Anforderung ein HTTP POSTist.
  • Edit(int) wird ausgewählt, wenn das HTTP-Verb ein anderes ist. Edit(int) wird im Allgemeinen über GETaufgerufen.

HttpPostAttribute, [HttpPost]wird für das Routing bereitgestellt, sodass sie basierend auf der HTTP-Methode der Anforderung auswählen kann. Die HttpPostAttribute entspricht Edit(int, Product) besser als Edit(int).

Es ist wichtig, die Rolle von Attributen wie HttpPostAttributezu verstehen. Ähnliche Attribute werden für andere HTTP-Verben definiert. Beim herkömmlichen Routing ist es üblich, dass Aktionen denselben Aktionsnamen verwenden, wenn sie Teil eines Formulars zum Anzeigen und Senden 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, die die mehreren übereinstimmend zugeordneten Endpunkte auflistet.

Herkömmliche Routennamen

Die Zeichenfolgen und "default" in den folgenden Beispielen "blog" 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 für die URL-Generierung verwendet werden. Die Verwendung einer benannten Route vereinfacht die URL-Erstellung, wenn die Reihenfolge der Routen die URL-Generierung kompliziert machen könnte. Routennamen müssen anwendungsweit eindeutig sein.

Routennamen:

  • Hat 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. Die Begriffe Routenname und Endpunktname:

  • Sind austauschbar.
  • Welche in der Dokumentation und im Code verwendet wird, hängt von der beschriebenen API ab.

Attributrouting für REST APIs

REST APIs sollten Attributrouting verwenden, um die Funktionalität der App als Eine Reihe von Ressourcen zu modellieren, in 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 vorangehenden Code wird innerhalb UseEndpoints aufgerufen, MapControllers um Attributroutencontroller zuzuordnen.

Siehe folgendes Beispiel:

  • HomeController entspricht einer Reihe von URLs, die der Übereinstimmung der herkömmlichen Standardroute {controller=Home}/{action=Index}/{id?} ähneln.
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 HomeController.Index Aktion wird für jeden der URL-Pfade /, /Home, /Home/Indexoder /Home/Index/3ausgeführt.

In diesem Beispiel wird ein wichtiger Programmierunterschied zwischen Attributrouting und herkömmlichem Routing hervorgehoben. Attributrouting erfordert mehr Eingaben, um eine Route anzugeben. Die herkömmliche Standardroute verarbeitet Routen prägnanter. Attributrouting ermöglicht und erfordert jedoch eine präzise Kontrolle darüber, welche Routenvorlagen für jede Aktion gelten.

Beim Attributrouting spielen der Controller und der Aktionsname keine Rolle, bei der die Aktion abgeglichen wird, es sei denn, es wird ein 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 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 gilt für [Route("[controller]/[action]")] den Controller:

[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 vorherigen Code müssen die Index Methodenvorlagen den Routenvorlagen vorangestellt / werden oder ~/ . 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 Routenvorlagenauswahl finden Sie unter Rangfolge der Routenvorlage.

Reservierte Routingnamen

Die folgenden Schlüsselwörter sind reservierte Routenparameternamen bei Verwendung von Controllern oder Razor Pages:

  • action
  • area
  • controller
  • handler
  • page

Die Verwendung 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 eine Razor Seite 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 Linkgenerationen, 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-Verbattributen

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 und wird daher id an die "api/[controller]" Vorlage auf dem Controller angefügt. Die Methodenvorlage ist "api/[controller]/"{id}"". Daher entspricht diese Aktion nur 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. Eine GET-Anforderung an /api/test2/int/abc:
    • Entspricht dieser Aktion nicht.
    • 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 sich jedoch nicht auf id Werte, die in eine ganze Zahl konvertiert werden können. Eine GET-Anforderung an /api/test2/int2/abc:
    • Entspricht dieser Route.
    • Die Modellbindung kann nicht in eine ganze Zahl konvertiert abc werden. Der id Parameter der -Methode ist integer.
    • Gibt eine 400 ungültige Anforderung zurück, da die Modellbindung nicht in eine ganze Zahl konvertiertabc werden konnte.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Attributrouting kann Attribute wie HttpPostAttribute, HttpPutAttributeund HttpDeleteAttributeverwendenHttpMethodAttribute. Alle HTTP-Verbattribute akzeptieren eine Routenvorlage. Das folgende Beispiel zeigt zwei Aktionen, die mit derselben Routenvorlage übereinstimmen:

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

Verwenden des URL-Pfads /products3:

  • Die MyProductsController.ListProducts Aktion wird ausgeführt, wenn das HTTP-Verb ist GET.
  • Die MyProductsController.CreateProduct Aktion wird ausgeführt, wenn das HTTP-Verb ist POST.

Beim Erstellen einer REST API ist es selten, dass Sie für eine Aktionsmethode verwenden [Route(...)] müssen, da die Aktion alle HTTP-Methoden akzeptiert. Es ist besser, das spezifischere HTTP-Verb-Attribut zu verwenden, um genau zu sein, was Ihre API unterstützt. REST Von APIs-Clients wird erwartet, dass sie wissen, welche Pfade und HTTP-Verben bestimmten logischen Vorgängen zugeordnet sind.

REST APIs sollten Attributrouting verwenden, um die Funktionalität der App als Eine Reihe von Ressourcen zu modellieren, in 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 id ist 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);
    }
}

Die Products2ApiController.GetProduct(int) Aktion:

  • Wird mit URL-Pfad wie ausgeführt. /products2/3
  • Wird nicht mit dem URL-Pfad /products2ausgefü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

Der folgende Code definiert den Routennamen :Products_List

[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.
  • Werden nur für die URL-Generierung verwendet.

Routennamen müssen anwendungsweit eindeutig sein.

Vergleichen Sie den vorangehenden Code mit der herkömmlichen Standardroute, die den id Parameter als optional ({id?}) definiert. Die Möglichkeit, APIs genau anzugeben, hat Vorteile, z. B. das Zulassen /products und /products/5 Die Verteilung an verschiedene Aktionen.

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 übereinstimmen. ProductsApi.ListProducts
  • Der URL-Pfad /products/5 kann mit übereinstimmen ProductsApi.GetProduct(int).

Beide Aktionen stimmen nur mit HTTP GET überein, da sie mit dem [HttpGet] -Attribut gekennzeichnet 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. Das folgende Beispiel entspricht einer Reihe von URL-Pfaden, 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 Kombiniert mit [Route("Home")] Definiert die Routenvorlage
[Route("")] Ja "Home"
[Route("Index")] Ja "Home/Index"
[Route("/")] Nein ""
[Route("About")] Ja "Home/About"

Attributroutenreihenfolge

Das Routing erstellt eine Struktur und entspricht allen Endpunkten gleichzeitig:

  • Die Routeneinträge verhalten sich wie in einer idealen Reihenfolge.
  • Die spezifischsten Routen haben die Möglichkeit, vor den allgemeineren Routen auszuführen.

Beispielsweise ist eine Attributroute wie blog/search/{topic} spezifischer als eine Attributroute wie blog/{*article}. Die blog/search/{topic} Route hat standardmäßig eine höhere Priorität, da sie spezifischer ist. Bei Verwendung des herkömmlichen Routings ist der Entwickler dafür verantwortlich, Routen in der gewünschten Reihenfolge zu platzieren.

Attributrouten können eine Bestellung mithilfe der Order -Eigenschaft konfigurieren. Alle vom Framework bereitgestellten Routenattribute enthalten Order . Routen werden entsprechend einer aufsteigenden Reihenfolge der Order-Eigenschaft verarbeitet. Die Standardreihenfolge ist 0. Festlegen einer Route mithilfe von Order = -1 Ausführungen vor Routen, die keine Reihenfolge festlegen. Festlegen einer Route mithilfe von Order = 1 Ausführungen nach der Standardroutenreihenfolge.

Vermeiden Sie abhängig von Order. Wenn der URL-Bereich einer App explizite Bestellwerte für die korrekte Weiterleitung erfordert, ist dies wahrscheinlich auch für Clients verwirrend. Im Allgemeinen wählt das Attributrouting die richtige Route mit URL-Abgleich aus. Wenn die Standardreihenfolge, die für die URL-Generierung verwendet wird, nicht funktioniert, ist die Verwendung eines Routennamens als Überschreibung in der Regel einfacher als das Anwenden der Order Eigenschaft.

Betrachten Sie die folgenden beiden Controller, die beide den Routenabgleich /homedefinieren:

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

Die Anforderung /home mit dem vorherigen Code löst eine Ausnahme ähnlich der folgenden aus:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

Das Hinzufügen Order zu einem der Routenattribute löst die Mehrdeutigkeit auf:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

Führt mit dem vorherigen Code /home den HomeController.Index Endpunkt aus. Um zum zu MyDemoController.MyIndexgelangen, fordern Sie an /home/MyIndex. Hinweis:

  • Der vorherige Code ist ein Beispiel oder ein schlechter Routingentwurf. Es wurde verwendet, um die Eigenschaft zu veranschaulichen Order .
  • Die Order -Eigenschaft löst nur die Mehrdeutigkeit auf. Diese Vorlage kann nicht übereinstimmen. Es wäre besser, die [Route("Home")] Vorlage zu entfernen.

Informationen zur Routenbestellung mit Razor Pages finden Sie unter Razor Seitenrouten- und App-Konventionen: Routenreihenfolge.

In einigen Fällen wird ein HTTP 500-Fehler mit mehrdeutigen Routen zurückgegeben. Verwenden Sie die Protokollierung , um zu ermitteln, welche Endpunkte das AmbiguousMatchExceptionverursacht haben.

Tokenersetzung in Routenvorlagen [Controller], [Aktion], [Bereich]

Der Einfachheit halber unterstützen Attributrouten die Tokenersetzung , indem sie ein Token in eckige Klammern ([, ]) einschließen. Die Token [action], [area], und [controller] werden durch die Werte des Aktionsnamens, des Bereichsnamens und des Controllernamens aus der Aktion ersetzt, in der 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();
}
  • Entspricht /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Entspricht /Products0/Edit/{id}

Die Tokenersetzung tritt im letzten Schritt der Erstellung von Attributrouten auf. Das vorherige Beispiel verhält sich wie der folgende Code:

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, teilen Sie uns dies in diesem GitHub-Diskussionsproblem mit, wenn Sie die Codekommentare in Ihrer Muttersprache sehen möchten.

Attributrouten können auch mit Vererbung kombiniert werden. Dies ist leistungsstark in Kombination mit tokenersetzung. Tokenersetzung gilt auch für Routennamen, die durch Attributrouten definiert werden. [Route("[controller]/[action]", Name="[controller]_[action]")]generiert einen eindeutigen Routennamen für jede Aktion:

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

Um dem Literaltokenersetzungstrennzeichen [ oder ]zu entsprechen, geben Sie es durch Wiederholen des Zeichens ([[ oder ]]) aus.

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 SubscriptionManagement Routenwert 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 Methode entspricht /subscription-management/list-all.

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 auf 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();
    }

Das Festlegen mehrerer Routenattribute auf dem Controller bedeutet, dass jedes mit jedem der Routenattribute für die 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 HTTP-Verbrouteneinschränkungen implementieren .IActionConstraint

Wenn mehrere Routenattribute, die implementiert IActionConstraint werden, für eine Aktion platziert werden:

  • 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 nützlich und effektiv erscheinen, es ist besser, den URL-Speicherplatz Ihrer App einfach und klar definiert zu halten. Verwenden Sie mehrere Routen für Aktionen nur bei Bedarf, z. B. zur Unterstützung vorhandener Clients.

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

Wendet im vorherigen Code [HttpPost("product14/{id:int}")] eine Routeneinschränkung an. Die Products14Controller.ShowProduct Aktion wird nur durch URL-Pfade wie /product14/3abgeglichen. 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 mithilfe von IRouteTemplateProvider

Alle Routenattribute implementieren IRouteTemplateProvider. Die ASP.NET Core Runtime:

  • Sucht beim Starten der App nach Attributen für Controllerklassen und Aktionsmethoden.
  • Verwendet die Attribute, die implementiert IRouteTemplateProvider werden, um den anfänglichen Satz von 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 vorherige Get Methode gibt zurück Order = 2, Template = api/MyTestApi.

Verwenden des Anwendungsmodells zum Anpassen von Attributrouten

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 gesammelt wurden. Die Daten aus Routenattributen werden von der IRouteTemplateProvider Implementierung bereitgestellt. Konventionen:

  • Kann geschrieben werden, um das Anwendungsmodell zu ändern, um das Verhalten des Routings anzupassen.
  • Werden beim App-Start gelesen.

Dieser Abschnitt zeigt ein einfaches Beispiel für das Anpassen des Routings mithilfe des Anwendungsmodells. Mit dem folgenden Code werden Routen grob mit der Ordnerstruktur des Projekts in Einklang stehen.

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 attributgeleitet werden:

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Der folgende Controller verwendet NamespaceRoutingConventionbeispielsweise 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:

  • Führt nichts aus, wenn der Controller ein Routing-Attribut ist.
  • Legt die Controllervorlage basierend auf dem namespacefest, wobei die Basis namespace entfernt wurde.

Kann NamespaceRoutingConvention in Startup.ConfigureServicesangewendet 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 ist My.Application.Admin.Controllers.UsersController.
  • Legt NamespaceRoutingConvention die Controllervorlage auf fest Admin/Controllers/Users/[action]/{id?.

Das 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 die Verwendung von konventionellem Routing und Attributrouting kombinieren. Es ist üblich, herkömmliche Routen für Controller zu verwenden, die HTML-Seiten für Browser bereitstellen, und Attributrouting für Controller, die APIs bereitstellen REST .

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. Jedes Route-Attribut auf dem Controller führt alle Aktionen im Controller-Attribut weiter.

Attributrouting und konventionelles Routing verwenden dieselbe Routing-Engine.

URL-Generierung und Umgebungswerte

Apps können Routing-URL-Generierungsfeatures verwenden, um URL-Links zu Aktionen zu generieren. Durch das Generieren von URLs werden Hardcodierungs-URLs eliminiert, wodurch Code robuster und verwaltbarer wird. Dieser Abschnitt konzentriert sich auf die von MVC bereitgestellten Funktionen zur URL-Generierung und behandelt nur die Grundlagen der URL-Generierung. Eine detaillierte Beschreibung der URL-Generierung finden Sie unter Routing in ASP.NET Core.

Die IUrlHelper Schnittstelle ist das zugrunde liegende Element der Infrastruktur zwischen MVC und Routing für die 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 über die Controller.Url -Eigenschaft verwendet, um eine URL für eine andere 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 konventionelle Standardroute verwendet, ist der Wert der url Variablen 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 Werte, die an Url.Action die Routenvorlage übergeben und diese Werte ersetzen:
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 ohne Wert kann Folgendes ausführen:

  • Verwenden Sie einen Standardwert, wenn er einen hat.
  • Übersprungen werden, wenn dies optional ist. Beispiel: aus id der Routenvorlage {controller}/{action}/{id?}.

Die URL-Generierung schlägt fehl, wenn ein erforderlicher Routenparameter keinen entsprechenden Wert aufweist. 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 wird von Url.Actionkonventionellem Routing ausgegangen. Die URL-Generierung funktioniert ähnlich beim Attributrouting, obwohl sich die Konzepte unterscheiden. Mit konventionellem Routing:

  • Die Routenwerte werden verwendet, um eine Vorlage zu erweitern.
  • Die Routenwerte für controller und action werden normalerweise in dieser Vorlage angezeigt. Dies funktioniert, da die URLs, die durch Routing übereinstimmen, 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.

LinkGeneratorwurde in ASP.NET Core 3.0 als Alternative zu IUrlHelperhinzugefügt. LinkGenerator bietet ähnliche, aber flexiblere Funktionen. Jede Methode für IUrlHelper verfügt auch über eine entsprechende Familie von Methoden für LinkGenerator .

Generieren von URLs nach Aktionsnamen

Url.Action, LinkGenerator.GetPathByAction und alle zugehörigen Überladungen sind so konzipiert, dass sie den Zielendpunkt generieren, indem sie einen Controllernamen und einen Aktionsnamen angeben.

Bei Verwendung Url.Actionwerden die aktuellen Routenwerte für controller und action von der Runtime bereitgestellt:

  • Der Wert von controller und action ist Teil sowohl von Umgebungswerten als auch von Werten. Die -Methode Url.Action verwendet immer die aktuellen Werte von action und controller und und generiert einen URL-Pfad, der an die aktuelle Aktion weitergeleitet wird.

Das Routing versucht, die Werte in Umgebungswerten zu verwenden, um Informationen auszufüllen, die beim Generieren einer URL nicht angegeben wurden. Betrachten Sie eine Route wie {a}/{b}/{c}/{d} mit Umgebungswerten { a = Alice, b = Bob, c = Carol, d = David }:

  • Routing enthält genügend Informationen, um eine URL ohne zusätzliche Werte zu generieren.
  • Routing enthält genügend Informationen, da alle Routenparameter einen Wert haben.

Wenn der Wert { d = Donovan } hinzugefügt wird:

  • Der Wert { d = David } 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:

  • Beide Werte { c = Carol, d = David } werden ignoriert.
  • Es gibt keinen Wert mehr für, und die d 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 erwarten, dass dieses Problem mit der Standardroute {controller}/{action}/{id?}auftritt. Dieses Problem ist in der Praxis selten, da Url.Action immer explizit ein controller - und action -Wert angegeben wird.

Mehrere Überladungen von Url.Action verwenden ein Routenwertobjekt, um Werte für andere Routenparameter als controller und actionbereitzustellen. Das Routenwertobjekt wird häufig mit idverwendet. Beispiel: Url.Action("Buy", "Products", new { id = 17 }). Das Route-Werte-Objekt:

  • Gemäß konvention ist in der Regel ein Objekt des anonymen Typs.
  • Kann ein IDictionary<> oder ein 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 vorherige 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 protocolakzeptiert. Beispiel: der vorherige Code.
  • LinkGenerator.GetUriByAction, das standardmäßig absolute URIs generiert.

Generieren von URLs nach Route

Im vorherigen Code wurde das Generieren einer URL durch Übergeben des Controller- und Aktionsnamens veranschaulicht. IUrlHelper stellt außerdem die Url.RouteUrl-Methodenfamilie bereit. Diese Methoden ähneln Url.Action, kopieren aber nicht die aktuellen Werte von action und controller in die Routenwerte. Die häufigste Verwendung von Url.RouteUrl:

  • Gibt einen Routennamen an, um die URL zu generieren.
  • 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 zum 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 Elemente bzw. zu generieren <form><a> . Diese Methoden verwenden die Url.Action-Methode , um eine URL zu generieren, und sie 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 vorherigen Beispielen wurde die Verwendung IUrlHelper in einem Controller gezeigt. Die häufigste Verwendung in einem Controller besteht darin, eine URL als Teil 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 von Benutzereingaben umzuleiten:

[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 Aktion führt zu Factorymethoden wie und CreatedAtAction folgt einem ähnlichen Muster wie RedirectToAction die Methoden für IUrlHelper.

Sonderfall für dedizierte herkömmliche Routen

Konventionelles Routing kann eine spezielle Art von Routendefinition verwenden, die als dedizierte konventionelle Route bezeichnet wird. Im folgenden Beispiel ist die Route mit dem Namen blog eine dedizierte konventionelle 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?}");
});

Generiert mithilfe der vorherigen Routendefinitionen Url.Action("Index", "Home") den URL-Pfad / mithilfe der default Route, aber warum? 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 konventionelle Routen basieren auf einem speziellen Verhalten von Standardwerten, die keinen entsprechenden Routenparameter aufweisen, der verhindert, dass die Route bei der URL-Generierung zu gierig ist. 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. Fehler bei der URL-Generierung mithilfe blog von, da die Werte { controller = Home, action = Index } nicht übereinstimmen { controller = Blog, action = Article }. Routing greift dann wieder auf default zurück, was erfolgreich ausgeführt wird.

Bereiche

Bereiche sind ein MVC-Feature, das verwendet wird, um verwandte Funktionen in einer Gruppe als separates zu organisieren:

  • Routingnamespace für Controlleraktionen.
  • Ordnerstruktur für Ansichten.

Mithilfe von Bereichen kann eine App mehrere Controller mit demselben Namen verwenden, sofern sie über unterschiedliche Bereiche verfügen. 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 die Interaktion des Routings mit Bereichen erläutert. Weitere Informationen zur Verwendung von Bereichen mit Ansichten finden Sie unter Bereiche .

Im folgenden Beispiel wird MVC so konfiguriert, dass die konventionelle Standardroute und eine area Route für einen area benannten verwendet wird Blog:

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

Im vorherigen Code wird aufgerufen, MapAreaControllerRoute um das "blog_route"zu erstellen. Der zweite Parameter, "Blog", ist der Bereichsname.

Beim Abgleich mit einem URL-Pfad wie /Manage/Users/AddUsergeneriert die "blog_route" Route die Routenwerte { area = Blog, controller = Users, action = AddUser }. Der area Routenwert wird von einem Standardwert für areaerzeugt. Die von MapAreaControllerRoute erstellte Route entspricht folgendem:

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. Im Allgemeinen sollten Routen mit Bereichen früher platziert werden, da sie spezifischer sind als Routen ohne Bereich.

Im vorherigen Beispiel stimmen die Routenwerte { area = Blog, controller = Users, action = AddUser } mit der folgenden Aktion überein:

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 [Area] -Attribut bezeichnet einen Controller als Teil eines Bereichs. Dieser Controller befindet sich Blog im Bereich. Controller ohne [Area] Attribut sind keine Mitglieder eines Bereichs und stimmen nicht überein, wenn der Routenwert durch das area 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 in einem Bereich steht der Routenwert für area als Umgebungswert für für das Routing zur Verwendung für die URL-Generierung zur Verfügung. 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 generiert eine URL für /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 auf einem Controller, mit Ausnahme der Methoden mit dem NonAction-Attribut , sind Aktionen.

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