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 Aktionenzuzuordnen. Routenvorlagen:

  • Werden beim Start in Program.cs oder in Attributen definiert.
  • Beschreiben, wie URL-Pfade mit Aktionenabgegleichen werden.
  • Werden verwendet, um URLs für Links zu generieren. Die generierten Links werden in der Regel in Antworten zurückgegeben.

Aktionen sind entweder konventionell oder attributgeroutet. Wenn Sie eine Route auf dem Controller oder der Aktion platzieren, wird sie attributgeroutet. Weitere Informationen finden Sie im Abschnitt Gemischtes Routing.

Dieses Dokument:

  • Erläutert die Interaktionen zwischen MVC und Routing:
    • Wie typische MVC-Apps Routingfunktionen nutzen.
    • Umfasst beides:
    • Ausführliche Informationen zum Routing finden Sie unter Routing.
  • Bezieht sich auf das Standardroutingsystem namens Endpunktrouting. Es ist möglich, Controller mit der vorherigen Routingversion zu Kompatibilitätszwecken zu verwenden. Anweisungen finden Sie im Migrationshandbuch zu 2.2-3.0.

Einrichten einer herkömmlichen Route

Die ASP.NET Core MVC-Vorlage generiert herkömmlichen Routingcode ähnlich dem folgenden:

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 wird route default genannt. Die meisten Apps mit Controllern und Ansichten verwenden eine Routenvorlage ähnlich der default Route. REST-APIs sollten Attributroutingverwenden.

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

Die Routenvorlage "{controller=Home}/{action=Index}/{id?}" :

  • Entspricht einem URL-Pfad wie /Products/Details/5

  • Extrahiert die Routenwerte { controller = Products, action = Details, id = 5 } durch Tokenisierung 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 model bindet den Wert von id = 5 , um den Parameter auf id 5 festzulegen. Weitere Informationen finden Sie unter Modellbindung.

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

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

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

  • 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. / entspricht nur, wenn eine - und -Aktion vorhanden HomeController Index ist:

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

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

Mit der Hilfsmethode MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Ersetzt:

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

Wichtig

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

Herkömmliches Routing

Herkömmliches Routing wird mit Controllern und Ansichten verwendet. Die default-Route:

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

Das vorangehende ist ein Beispiel für eine herkömmliche Route. Sie wird als herkömmliches Routing bezeichnet, da sie 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 id verwendet. Das ? in macht es {id?} optional. id wird verwendet, um einer Modellentität zuzuordnen.

Mit dieser default Route wird der URL-Pfad verwendet:

  • /Products/List wird der ProductsController.List Aktion zugeordnet.
  • /Blog/Article/17 wird dem BlogController.Article Modell zugeordnet, und in der Regel bindet das Modell den id Parameter 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 CRUD-Stilaktionen mit Konsistenz für die URLs über Controller hinweg:

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

Warnung

Der id im vorangehenden Code wird von der Routenvorlage als optional definiert. Aktionen können ohne die optionale ID ausgeführt werden, die als Teil der URL bereitgestellt wird. Im Allgemeinen, wenn id von der URL ausgelassen wird:

  • id wird durch Modellbindung auf 0 festgelegt.
  • In der Datenbank, die entspricht, wurde keine Entität id == 0 gefunden.

Das Attributrouting bietet eine differenzierte Steuerung, um die ID für einige Aktionen und nicht für andere erforderlich zu machen. Gemäß konventionsgemäß enthält die Dokumentation optionale Parameter, z. id B. 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. Bei größeren Web-UI-Apps ist häufig nur eine andere Route mit Bereichen erforderlich.

MapControllerRoute und MapAreaRoute :

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

Endpunktrouting in ASP.NET Core:

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

Das Attributrouting wird weiter unten in diesem Dokument erläutert.

Mehrere herkömmliche Routen

Mehrere konventionelle Routen können innerhalb von hinzugefügt UseEndpoints werden, indem weitere Aufrufe von und hinzugefügt MapControllerRoute MapAreaControllerRoute werden. Auf diese Weise können mehrere Konventionen definiert oder herkömmliche Routen hinzugefügt werden, die einer bestimmten Aktionzugeordnet 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 vorangehenden Code ist eine dedizierte konventionelle Route. Dies wird aus folgenden Grund als dedizierte konventionelle Route bezeichnet:

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

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

/Blog``/Blog/Article, und sind die /Blog/{any-string} 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 üblich ist, einen Artikelnamen als Teil der URL zu verwenden.

Warnung

In ASP.NET Core hat das Routing nicht die folgenden Folgenden:

  • 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 die beste Endpunkt übereinstimmung basierend auf der Anforderung aus.
  • Stellen Sie Garantien für die Ausführungsreihenfolge der Erweiterbarkeit bereit, IRouteConstraint IActionConstraint z. B. oder .

Referenzmaterial zum Routing finden Sie unter Routing.

Herkömmliche 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 überlappen. Beim Hinzufügen von Routen mit , MapControllerRoute MapDefaultControllerRoute und wird ihren MapAreaControllerRoute Endpunkten basierend auf der reihenfolge, in der sie aufgerufen werden, automatisch ein Auftragswert zugewiesen. Übereinstimmungen von einer Route, die zuvor angezeigt wird, 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 als Routen ohne Bereich sind. Dedizierte konventionelle Routen mit Catch-All-Routenparametern wie {*article} können eine Route zu gierigmachen, d. h., sie entspricht URLs, die von anderen Routen abgeglichen werden sollen. Legen 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 mehrdeutiger Aktionen

Wenn zwei Endpunkte per Routing übereinstimmen, muss das Routing einen der folgenden Schritte ausführen:

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

Beispiel:

public class Products33Controller : Controller
{
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpPost]
    public IActionResult Edit(int id, Product product)
    {
        return ControllerContext.MyDisplayRouteInfo(id, product.name);
    }
}

Der vorangehende Controller definiert zwei Aktionen, die übereinstimmen:

  • Der URL-Pfad /Products33/Edit/17
  • Weiterleiten von Daten { 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 lösen Sie die richtige Route auf:

  • Edit(int, Product) wird ausgewählt, wenn es sich bei der Anforderung um eine HTTP-Anforderung POST handelt.
  • Edit(int) wird ausgewählt, wenn das HTTP-Verb etwas anderes ist. Edit(int) wird im Allgemeinen über GET aufgerufen.

Der HttpPostAttribute [HttpPost] wird für das Routing bereitgestellt, sodass er basierend auf der HTTP-Methode der Anforderung auswählen kann. HttpPostAttributeist Edit(int, Product) eine bessere Übereinstimmung als Edit(int) .

Es ist wichtig, die Rolle von Attributen wie zu HttpPostAttribute verstehen. Ähnliche Attribute werden für andere HTTP-Verbendefiniert. Beim herkömmlichen Routingist es üblich, dass Aktionen den gleichen Aktionsnamen verwenden, wenn sie Teil eines Formulars zum Senden eines Formularworkflows sind. Weitere Informationen finden Sie beispielsweise unter Untersuchen der beiden Edit-Aktionsmethoden.

Wenn das Routing keinen besten Kandidaten auswählen kann, wird eine AmbiguousMatchException ausgelöst, die die mehreren übereinstimmenden Endpunkte auflistet.

Herkömmliche Routennamen

Die Zeichenfolgen "blog" und in den folgenden "default" Beispielen sind herkömmliche Routennamen:

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

Die Routennamen geben der Route einen logischen Namen. Die benannte Route kann 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 kann. 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 Konzept des Routennamens wird beim Routing als IEndpointNameMetadatadargestellt. Die Begriffe Routenname und Endpunktname:

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

Attributrouting für REST-APIs

REST-APIs sollten das Attributrouting verwenden, um die Funktionalität der App als eine Gruppe von Ressourcen zu modellieren, bei denen Vorgänge durch HTTP-Verbendargestellt 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 vorangehenden Code MapControllers wird in aufgerufen, UseEndpoints um Attributroutencontroller zuzuordnen.

Siehe folgendes Beispiel:

  • HomeController entspricht einem Satz von URLs, der dem entspricht, dem die herkömmliche Standardroute {controller=Home}/{action=Index}/{id?} entspricht.
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 einen der URL-Pfade , , oder / /Home /Home/Index /Home/Index/3 ausgeführt.

In diesem Beispiel wird ein wichtiger Programmierunterschied zwischen Attributrouting und herkömmlichem Routinghervorgehoben. Das Attributrouting erfordert mehr Eingaben, um eine Route anzugeben. Die herkömmliche Standardroute behandelt Routen prägnanter. Das Attributrouting ermöglicht und erfordert jedoch eine genaue Steuerung, welche Routenvorlagen für jede Aktiongelten.

Beim Attributrouting spielen die Controller- und Aktionsnamen keine Rolle, in denen die Aktion abgegleichen wird, es sei denn, es wird ein Tokenersetzung verwendet. Das folgende Beispiel entspricht den gleichen URLs wie im vorherigen Beispiel:

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Der folgende Code verwendet die Tokenersetzung für action und controller :

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Der folgende Code 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 vorangehenden Code müssen die Index Methodenvorlagen oder den Routenvorlagen vorangestellt / ~/ werden. 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 von Routenvorlagen.

Reservierte Routingnamen

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

  • action
  • area
  • controller
  • handler
  • page

Die Verwendung page von als Routenparameter mit Attributrouting ist ein häufiger Fehler. Dies führt zu inkonsistentem und verwirrenden 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 ein URL-Generierungsvorgang auf eine Razor Seite oder einen Controller verweist.

  • 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 den Abgleich nur mit HTTP GET-Anforderungen einschränkt.
  • Die GetProduct Aktion enthält die Vorlage und wird daher an die Vorlage auf dem Controller "{id}" id "api/[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 Teil der Vorlage beschränkt die Routenwerte auf :int id Zeichenfolgen, die in eine ganze Zahl konvertiert werden können. Eine GET-Anforderung an /api/test2/int/abc :
    • Passt nicht zu dieser Aktion.
    • 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 Aktion enthält in der Vorlage, beschränkt sich jedoch nicht auf Werte, GetInt2Product die in eine ganze Zahl konvertiert werden {id} id können. Eine GET-Anforderung an /api/test2/int2/abc :
    • Entspricht dieser Route.
    • Die Modellbindung kann nicht in abc eine ganze Zahl konvertiert werden. Der id -Parameter der -Methode ist integer.
    • Gibt eine 400 Bad Request zurück, da die Modellbindung nicht in eine abc ganze Zahl konvertiert werden konnte.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Attributrouting kann HttpMethodAttribute Attribute wie , und HttpPostAttribute HttpPutAttribute HttpDeleteAttribute verwenden. Alle HTTP-Verbattribute akzeptieren eine Routenvorlage. Das folgende Beispiel zeigt zwei Aktionen, die mit der gleichen 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 /products3 URL-Pfads:

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

Beim Erstellen einer REST-API ist es selten, dass Sie für eine Aktionsmethode verwenden müssen, da die Aktion [Route(...)] alle HTTP-Methoden akzeptiert. Es ist besser, das spezifischere HTTP-Verbattribut zu verwenden, um genau zu sein, was Ihre API unterstützt. REST-API-Clients sollten wissen, welche Pfade und HTTP-Verben bestimmten logischen Operationen entsprechen.

REST-APIs sollten Attributrouting verwenden, um die Funktionen der App als eine 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 ist id als Teil des URL-Pfads erforderlich:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Die Products2ApiController.GetProduct(int) Aktion:

  • Wird mit einem URL-Pfad wie ausgeführt. /products2/3
  • Wird nicht mit dem URL-Pfad /products2 ausgeführt.

Das [Consumes]-Attribut ermöglicht es einer Aktion, die unterstützten Anforderungsinhaltstypen einzuschränken. Weitere Informationen finden Sie unter Definieren unterstützter Anforderungsinhaltstypen mit dem Consumes-Attribut.

Eine vollständige Beschreibung und Routenvorlagen und dazugehörige Optionen finden Sie unter Routing in ASP.NET Core.

Weitere Informationen zu finden Sie [ApiController] unter ApiController-Attribut.

Routenname

Im folgenden Code wird ein Routenname von Products_List definiert:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Routennamen können verwendet werden, um basierend auf einer bestimmten Route eine URL zu generieren. Routennamen:

  • 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 vorangehenden Code mit der herkömmlichen Standardroute, die den id Parameter als optional definiert ( {id?} ). Die Möglichkeit, APIs genau anzugeben, hat Vorteile, z. B. das Zulassen und Das Versenden an /products /products/5 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 ProductsApi.GetProduct(int) übereinstimmen.

Beide Aktionen passen nur zu GET HTTP, da sie mit dem -Attribut gekennzeichnet [HttpGet] 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 einem Satz 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 [Route] die Attribute im vorangehenden Code erläutert:

attribute Kombiniert mit [Route("Home")] Definiert routenvorlage
[Route("")] Ja "Home"
[Route("Index")] Ja "Home/Index"
[Route("/")] Nein ""
[Route("About")] Ja "Home/About"

Attributrouten reihenfolge

Das Routing erstellt eine Struktur und gleicht alle Endpunkte gleichzeitig ab:

  • Die Routeneinträge verhalten sich so, als ob sie in einer idealen Reihenfolge platziert würden.
  • Die spezifischsten Routen können vor den allgemeineren Routen ausgeführt werden.

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. Mit herkömmlichem Routingist der Entwickler dafür verantwortlich, Routen in der gewünschten Reihenfolge zu platzieren.

Attributrouten können eine Bestellung mithilfe der -Eigenschaft Order konfigurieren. Alle vom Framework bereitgestellten Routenattribute enthalten Order . Routen werden entsprechend einer aufsteigenden Reihenfolge der Order-Eigenschaft verarbeitet. Die Standardreihenfolge ist 0. Das Festlegen einer Route mit Order = -1 wird vor Routen ausgeführt, die keine Reihenfolge festlegen. Das Festlegen einer Route mit Order = 1 wird nach der Standardrouten reihenfolge ausgeführt.

Vermeiden Sie die Abhängigkeit von Order . Wenn der URL-Bereich einer App explizite Reihenfolgenwerte erfordert, um ordnungsgemäß weiter zu routen, ist dies wahrscheinlich auch für Clients verwirrend. Im Allgemeinen wählt das Attributrouting die richtige Route mit URL-Abgleich aus. Wenn die für die URL-Generierung verwendete Standard reihenfolge 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 /home definieren:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Das Anfordern /home mit dem vorangehenden 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 Order Hinzufügen zu einem der Routenattribute löst die Mehrdeutigkeit auf:

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

Mit dem vorangehenden Code führt /home den Endpunkt HomeController.Index aus. Um zum zu MyDemoController.MyIndex kommen, fordern Sie /home/MyIndex an. Hinweis:

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

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

In einigen Fällen wird ein HTTP 500-Fehler mit mehrdeutigen Routen zurückgegeben. Verwenden Sie die Protokollierung, um zu sehen, welche Endpunkte den verursacht AmbiguousMatchException 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 , und werden durch die Werte des Aktionsnamens, Bereichsnamens und Controllernamens aus der Aktion ersetzt, in der [action] die Route definiert [area] [controller] 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 nativen Sprache anzeigen möchten.

Attributrouten können auch mit Vererbung kombiniert werden. Dies ist in Kombination mit token replacement 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);
    }
}

Damit das Trennzeichen [ oder ] der Tokenersetzungs-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein ([[ oder ]]), was einem Escapezeichen entspricht.

Verwenden eines Parametertransformators zum Anpassen der Tokenersetzung

Die Tokenersetzung kann mit einem Parametertransformator angepasst werden. Ein Parametertransformator implementiert IOutboundParameterTransformer und wandelt den Wert der Parameter um. Beispielsweise ändert ein SlugifyParameterTransformer benutzerdefinierter 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 vorangehende ListAll Methode entspricht /subscription-management/list-all .

Wird 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-Webdokumenten zu Slug.

Warnung

Übergeben Sie ein Timeout, wenn Sie System.Text.RegularExpressions zum Verarbeiten nicht vertrauenswürdiger Eingaben verwenden. Ein böswilliger Benutzer kann Eingaben für RegularExpressions angeben, um einen Denial-of-Service-Angriff durchzuführen. ASP.NET Core-Framework-APIs, die RegularExpressions verwenden, übergeben ein Timeout.

Mehrere Attributrouten

Attributrouting unterstützt das Definieren mehrerer Routen, die zu derselben Aktion führen. Dies wird am häufigsten beim Imitieren des Verhaltens der herkömmlichen Standardroute verwendet. Im folgenden Beispiel wird dies gezeigt:

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Das Festlegen mehrerer Routenattribute auf dem Controller bedeutet, dass jedes attribut mit jedem der Routenattribute in den Aktionsmethoden kombiniert wird:

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Alle Routeneinschränkungen des HTTP-Verbs implementieren IActionConstraint .

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

  • Jede Aktionseinschränkung kombiniert mit der Routenvorlage, 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 leistungsstark erscheinen. Es ist besser, den URL-Bereich Ihrer App einfach und klar definiert zu halten. Verwenden Sie nur bei Bedarf mehrere Routen für Aktionen, um beispielsweise vorhandene Clients zu unterstützen.

Angeben von optionalen Attributroutenparametern, Standardwerten und Einschränkungen

Attributrouten unterstützen dieselbe Inline-Syntax wie herkömmliche Routen, um optionale Parameter, Standardwerte und Einschränkungen anzugeben.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Im vorangehenden Code wendet [HttpPost("product14/{id:int}")] eine Routeneinschränkung an. Die Products14Controller.ShowProduct Aktion wird nur mit URL-Pfaden wie /product14/3 übereinstimmen. 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 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 IRouteTemplateProvider implementieren, um den anfänglichen Satz von Routen zu erstellen.

Implementieren IRouteTemplateProvider Sie , 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 Methode Get gibt Order = 2, Template = api/MyTestApi zurück.

Verwenden des Anwendungsmodells zum Anpassen von Attributrouten

Das Anwendungsmodell:

  • Ein Objektmodell, das beim Start in Program.cs erstellt wurde.
  • Enthält alle Metadaten, die von ASP.NET Core zum Routen 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 Implementierung IRouteTemplateProvider 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 die Anpassung des Routings mithilfe des Anwendungsmodells. Der folgende Code sorgt dafür, dass Routen in etwa der Ordnerstruktur des Projekts entspricht.

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 Konvention auf Controller namespace angewendet wird, die attributiert werden:

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

Der folgende Controller verwendet beispielsweise nicht NamespaceRoutingConvention :

[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 attributiert ist.
  • Legt die Controllervorlage basierend auf namespace fest, ohne dass die Basis namespace entfernt wurde.

Kann NamespaceRoutingConvention in Program.cs angewendet werden:

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(
     new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

Betrachten Sie beispielsweise den folgenden Controller:

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

Für den Code oben gilt:

  • Die Basis namespace ist My.Application .
  • Der vollständige Name des vorangehenden Controllers ist My.Application.Admin.Controllers.UsersController .
  • Legt NamespaceRoutingConvention die Controllervorlage auf Admin/Controllers/Users/[action]/{id? fest.

Kann NamespaceRoutingConvention 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 herkömmlichem Routing und Attributrouting kombinieren. In der Regel werden herkömmliche Routen für Controller verwendet, die für Browser-HTML-Seiten gedacht sind, und das Attributrouting für Controller für REST-APIs.

Aktionen werden entweder herkömmlich oder über Attribute zugeordnet, d.h., dass eine Route auf dem Controller oder der Aktion platziert wird. Aktionen, die Attributrouten definieren, können nicht über die herkömmliche Routen und umgekehrt erreicht werden. *Jedes _route-Attribut auf dem Controller führt *_all**-Aktionen im Controllerattribut als Route aus.

Attributrouting und herkömmliches Routing verwenden die gleiche 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 hart codierende URLs beseitigt, wodurch Code robuster und verflässigbarer 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 -Eigenschaft in Url Controllern, Ansichten und Ansichtskomponenten verfügbar.

Im folgenden Beispiel wird die -Schnittstelle über die -Eigenschaft verwendet, IUrlHelper um eine URL zu einer anderen Aktion zu Controller.Url 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 Variablen url 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 Url.Action an übergeben und durch diese Werte in der Routenvorlage substituiert werden:
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:

  • Verwenden Sie einen Standardwert, wenn er einen hat.
  • Wird übersprungen, 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 hat. 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 wird Url.Action von konventionellem Routing ausgegangen. Die URL-Generierung funktioniert ähnlich wie das Attributrouting,obwohl die Konzepte unterschiedlich sind. Mit herkömmlichem Routing:

  • Die Routenwerte werden verwendet, um eine Vorlage zu erweitern.
  • Die Routenwerte für controller und werden in der Regel in dieser Vorlage action angezeigt. Dies funktioniert, da die vom Routing übereinstimmenden URLs eine Konvention einhalten.

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 vorangehenden Code generiert custom/url/to/destination .

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

Generieren von URLs nach Aktionsnamen

Url.Action, LinkGenerator.GetPathByActionund alle zugehörigen Überladungen sind so konzipiert, dass der Zielendpunkt durch Angabe eines Controllernamens und Aktionsnamens generiert wird.

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

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

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

  • 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 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. Im vorherigen Beispiel gilt Folgendes, wenn der { c = Cheryl } Wert hinzugefügt wird:

  • Beide Werte { c = Carol, d = David } werden ignoriert.
  • Es gibt keinen Wert mehr für , d und die URL-Generierung schlägt fehl.
  • Die gewünschten Werte von c und d müssen angegeben werden, um eine URL zu generieren.

Möglicherweise erwarten Sie, dass dieses Problem mit der Standardroute {controller}/{action}/{id?} besteht. Dieses Problem ist in der Praxis selten, da Url.Action immer explizit einen - und controller -Wert action angibt.

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

  • Standardmäßig ist in der Regel ein Objekt vom anonymen Typ.
  • Kann ein oder IDictionary<> 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 zum Absolute URL eines der folgenden Schritte:

  • Eine Überladung, die ein protocol akzeptiert. Beispiel: der vorangehende Code.
  • LinkGenerator.GetUriByAction, wodurch standardmäßig absolute URIs generiert werden.

Generieren von URLs nach Route

Im vorangehenden Code wurde das Generieren einer URL durch Übergeben des Controllers und des Aktionsnamens demonstriert. IUrlHelper stellt auch die Url.RouteUrl-Methodenfamilie zur Seite. Diese Methoden ähneln Url.Action,kopieren jedoch nicht die aktuellen Werte von action und in die controller Routenwerte. Die häufigste Verwendung von Url.RouteUrl :

  • Gibt einen Routennamen zum Generieren der URL an.
  • 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 zum Generieren von - bzw. <form> <a> -Elementen zur Verfügung. Diese Methoden verwenden die Url.Action-Methode, um eine URL zu generieren, und akzeptieren ähnliche Argumente. Die Url.RouteUrl-Begleiter für HtmlHelper sind Html.BeginRouteForm und Html.RouteLink, die ähnliche Funktionen aufweisen.

Taghilfsprogramme generieren URLs mit den Taghilfsprogrammen form und <a>. Beide implementieren mit IUrlHelper. Weitere Informationen finden Sie unter Tag helpers in forms (Taghilfen 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 von in einem Controller gezeigt. Die häufigste Verwendung in einem Controller ist das Generieren einer URL als Teil eines Aktionsergebnis.

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

Sonderfall für dedizierte herkömmliche Routen

Herkömmliches Routing kann eine spezielle Art von Routendefinition verwenden, die als dedizierte konventionelle Route bezeichnet wird. Im folgenden Beispiel ist die Route mit blog dem Namen 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 obigen Url.Action("Index", "Home") Routendefinitionen 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 herkömmliche 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 blog URL-Generierung mit schlägt fehl, da { controller = Home, action = Index } die Werte nicht mit { controller = Blog, action = Article } übereinstimmen. 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 als separate Gruppe zu organisieren:

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

Durch die Verwendung von Bereichen kann eine App über mehrere Controller mit demselben Namen verfügen, solange sie unterschiedliche Bereiche haben. Mithilfe von Bereichen wird außerdem eine Hierarchie erstellt, damit das Routing durch Hinzufügen eines anderen Routenparameters ausgeführt werden kann: area zu controller und action. In diesem Abschnitt wird erläutert, wie das Routing mit Bereichen interagiert. Weitere Informationen zur Verwendung von Bereichen mit Ansichten finden Sie unter Bereiche.

Im folgenden Beispiel wird MVC so konfiguriert, dass die herkömmliche Standardroute und eine area Route für eine mit dem Namen verwendet area Blog wird:

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 vorangehenden Code MapAreaControllerRoute wird aufgerufen, um zu "blog_route" erstellen. Der zweite Parameter, "Blog" , ist der Bereichsname.

Beim Abgleich eines URL-Pfads /Manage/Users/AddUser wie generiert die Route die "blog_route" Routenwerte { area = Blog, controller = Users, action = AddUser } . Der area Routenwert wird durch einen Standardwert für area erzeugt. Die von erstellte MapAreaControllerRoute 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 als Routen ohne Bereich sind.

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

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 kennzeichnet einen Controller als Teil eines Bereichs. Dieser Controller befindet sich im Blog Bereich . Controller ohne Attribut sind keine Elemente eines Bereichs und werden nicht übereinstimmen, wenn der [Area] area Routenwert durch 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 der Vollständigkeit halber angezeigt. Wenn die obigen Controller denselben Namespace verwendet haben, wird ein Compilerfehler generiert. Klassennamespaces haben keine Auswirkungen auf das MVC-Routing.

Die ersten beiden Controller gehören zu Bereichen und werden können nur abgleichen, wenn ihr jeweiliger Bereichsname vom area-Routenwert bereitgestellt wird. Der dritte Controller gehört keinem Bereich an und kann nur abgleichen, wenn vom Routing kein Wert für area bereitgestellt wird.

Im Hinblick auf das Erkennen keines Werts hat die Abwesenheit des area-Werts dieselben Auswirkungen, wie wenn der Wert für area 0 (null) oder eine leere Zeichenfolge wäre.

Beim Ausführen einer Aktion innerhalb eines Bereichs ist der Routenwert für als Umgebungswert für das Routing verfügbar, der für die area URL-Generierung verwendet werden kann. 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 Routing-Middleware, um die URLs eingehender Anforderungen zu finden und sie Aktionen zu zuordnen. Routenvorlagen:

  • Werden im Startcode oder in Attributen definiert.
  • Beschreiben Sie, wie URL-Pfade mit Aktionen übereinstimmen.
  • Werden verwendet, um URLs für Links zu generieren. Die generierten Links werden in der Regel in Antworten zurückgegeben.

Aktionen werden entweder konventionell oder attributgeroutet. Wenn Sie eine Route auf dem Controller oder der Aktion platzieren, wird sie attributiert. Weitere Informationen finden Sie im Abschnitt Gemischtes Routing.

Dieses Dokument:

  • Erläutert die Interaktionen zwischen MVC und Routing:
    • Wie typische MVC-Apps Routingfeatures nutzen.
    • Deckt beides ab:
    • Erweiterte Routingdetails finden Sie unter Routing.
  • Bezieht sich auf das in ASP.NET Core 3.0 hinzugefügte Standardroutingsystem, das als Endpunktrouting bezeichnet wird. Aus Kompatibilitätsgründen können Controller mit der vorherigen Version des Routings verwendet werden. Anweisungen finden Sie im Migrationshandbuch zu 2.2-3.0. Referenzmaterial zum Legacyroutingsystem finden Sie in der Version 2.2 dieses Dokuments.

Einrichten einer herkömmlichen Route

Startup.Configureverfü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 UseEndpoints 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 Route default ähnelt. REST-APIs sollten Attributrouting verwenden.

Die "{controller=Home}/{action=Index}/{id?}" Routenvorlage:

  • Entspricht einem URL-Pfad wie /Products/Details/5

  • Extrahiert die Routenwerte durch { controller = Products, action = Details, id = 5 } Tokenisieren des Pfads. Die Extraktion von Routenwerten führt zu einer Übereinstimmung, wenn die App über einen Controller namens ProductsController und eine Aktion Details 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 Parameter auf zu id 5 setzen. Weitere Informationen finden Sie unter Modellbindung.

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

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

  • Das ? Zeichen in definiert als {id?} id 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 enthalten ist. / findet nur dann eine Übereinstimmung, wenn eine - und HomeController Index -Aktion vorhanden ist:

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

Mithilfe der obigen Controllerdefinition und Routenvorlage wird HomeController.Index die Aktion für die folgenden URL-Pfade ausgeführt:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

Der URL-Pfad / verwendet die Standardcontroller und die Home Aktion der Index Routenvorlage. Der URL-Pfad /Home verwendet die Standardaktion der Index Routenvorlage.

Mit der Hilfsmethode MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Ersetzt:

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

Wichtig

Das Routing wird mithilfe der UseRouting MapControllerRoute Middleware , und 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?}");

Die vorangehende ist ein Beispiel für eine herkömmliche Route. Dies wird als herkömmliches Routing bezeichnet, da eine Konvention für URL-Pfade festgelegt wird:

  • Das erste Pfadsegment, {controller=Home} , wird dem Controllernamen angezeigt.
  • Das zweite Segment, {action=Index} , wird dem Aktionsnamen angezeigt.
  • Das dritte Segment wird {id?} für eine optionale id verwendet. Das ? in macht es {id?} optional. id wird verwendet, um einer Modellentität zu zuordnen.

Mit dieser default Route wird der URL-Pfad verwendet:

  • /Products/List wird der Aktion ProductsController.List zu.
  • /Blog/Article/17 ist dem BlogController.Article -Modell zu und bindet den -Parameter in der Regel id an 17.

Diese Zuordnung:

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

Die Verwendung des herkömmlichen Routings mit der Standardroute ermöglicht das Erstellen der App, ohne für jede Aktion ein neues URL-Muster erstellen zu müssen. Für eine App mit CRUD-Stilaktionen mit Controllerkonsistenz für die URLs:

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

Warnung

Der id im vorangehenden Code wird von der Routenvorlage als optional definiert. Aktionen können ohne die optionale ID ausgeführt werden, die als Teil der URL angegeben wird. Im Allgemeinen gilt id Folgendes, wenn in der URL ausgelassen wird:

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

Attributrouting bietet eine fein abgrenzende Steuerung, um die ID für einige Aktionen und nicht für andere erforderlich zu machen. Die Dokumentation enthält standardmäßig optionale Parameter, z. B. wenn sie id 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 Webbenutzeroberflächen-Apps erforderlich ist. Bei größeren Webbenutzeroberflächen-Apps ist häufig nur eine andere Route erforderlich, die Bereiche verwendet.

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 Reihenfolgengarantien für die Ausführung der Erweiterbarkeit, da alle Endpunkte gleichzeitig verarbeitet werden.

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

Das Attributrouting wird später in diesem Dokument erläutert.

Mehrere konventionelle Routen

Mehrere konventionelle Routen können innerhalb von hinzugefügt UseEndpoints werden, indem weitere Aufrufe von und MapControllerRoute hinzugefügt MapAreaControllerRoute werden. Dies ermöglicht das Definieren mehrerer Konventionen oder das Hinzufügen herkömmlicher Routen, die einer bestimmten Aktion speziell 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 vorangehenden Code ist eine dedizierte konventionelle Route. Dies wird aus den hier genannten dedizierten herkömmlichen Routen bezeichnet:

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

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

/Blog, /Blog/Article und sind die einzigen /Blog/{any-string} URL-Pfade, die mit der Blogroute übereinstimmen.

Für das vorherige Beispiel gilt Folgendes:

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

Warnung

In ASP.NET Core 3.0 und höher funktioniert das Routing nicht:

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

Unter Routing finden Sie Referenzmaterial zum Routing.

Herkömmliche Routing reihenfolge

Herkömmliches Routing entspricht nur einer Kombination aus Aktion und Controller, die von der App definiert werden. Dies soll Fälle vereinfachen, in denen herkömmliche Routen überlappen. Beim Hinzufügen von Routen mit , und wird ihren Endpunkten automatisch ein Bestellwert zugewiesen, der auf der Reihenfolge MapControllerRoute MapDefaultControllerRoute MapAreaControllerRoute basiert, in der sie aufgerufen werden. Übereinstimmungen aus einer Route, die zuvor angezeigt wird, 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 als Routen ohne Bereich sind. Dedizierte konventionelle Routen mit Catch-All-Routenparametern wie können dazu führen, dass eine Route zu gierig ist. Dies bedeutet, dass sie urLs entspricht, die von anderen Routen {*article} übereinstimmen sollen. Legen Sie die gierigen Routen später in der Routentabelle ab, 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 mehrdeutiger Aktionen

Wenn zwei Endpunkte durch Routing übereinstimmen, muss das Routing einen der folgenden Schritte unternehmen:

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

Beispiel:

    public class Products33Controller : Controller
    {
        public IActionResult Edit(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpPost]
        public IActionResult Edit(int id, Product product)
        {
            return ControllerContext.MyDisplayRouteInfo(id, product.name);
        }
    }
}

Der vorangehende 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 gepostete Formular.

So lösen Sie die richtige Route auf:

  • Edit(int, Product) wird ausgewählt, wenn die Anforderung ein HTTP-Element POST ist.
  • Edit(int) wird ausgewählt, wenn es sich bei dem HTTP-Verb um ein anderes Verb handelt. Edit(int) wird im Allgemeinen über GET aufgerufen.

Der HttpPostAttribute , , wird für das Routing bereitgestellt, sodass er basierend auf der [HttpPost] HTTP-Methode der Anforderung auswählen kann. der HttpPostAttribute ergibt eine bessere Übereinstimmung als Edit(int, Product) Edit(int) .

Es ist wichtig, die Rolle von Attributen wie zu HttpPostAttribute verstehen. Ähnliche Attribute werden für andere HTTP-Verben definiert. Beim herkömmlichenRouting verwenden Aktionen häufig denselben Aktionsnamen, wenn sie Teil eines Formulars zum Anzeigen und Übermitteln des Formularworkflows sind. Weitere Informationen finden Sie beispielsweise unter Untersuchen der beiden Edit-Aktionsmethoden.

Wenn das Routing keinen besten Kandidaten auswählen kann, wird eine ausgelöst, die AmbiguousMatchException mehrere übereinstimmende Endpunkte auflistet.

Konventionelle Routennamen

Die "blog" Zeichenfolgen "default" und in den folgenden Beispielen sind konventionelle 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 Konzept des Routennamens 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 Funktionen der App als eine Gruppe 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 MapControllers in aufgerufen, UseEndpoints um Attributroutencontroller zu zuordnen.

Siehe folgendes Beispiel:

  • HomeController entspricht einem Satz von URLs, ähnlich wie bei der herkömmlichen {controller=Home}/{action=Index}/{id?} Standardroute.
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 einen der URL-Pfade , , oder / /Home /Home/Index /Home/Index/3 ausgeführt.

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

Beim Attributrouting spielen controller- und aktionsnamen keine Rolle bei der Übereinstimmung der Aktion, es sei denn, es wird ein Tokenersetzung verwendet. Das folgende Beispiel entspricht den gleichen URLs wie im vorherigen Beispiel:

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

Im folgenden Code wird die Tokenersetzung für action und controller verwendet:

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 [Route("[controller]/[action]")] für 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 vorangehenden Code müssen die Index Methodenvorlagen den Routenvorlagen / oder ~/ vorangehenden vorangehenden Methodenvorlagen vorangehenden. 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 von Routenvorlagen.

Reservierte Routingnamen

Die folgenden Schlüsselwörter sind reservierte Routenparameternamen, wenn Controller oder Pages verwendet Razor werden:

  • action
  • area
  • controller
  • handler
  • page

Die page Verwendung als Routenparameter mit Attributrouting ist ein häufiger Fehler. Dies führt zu inkonsistenten und verwirrenden Verhaltensweisen 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 ein URL-Generierungsvorgang auf eine Razor Seite oder einen Controller verweist.

  • 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 den Abgleich nur mit HTTP GET-Anforderungen einschränkt.
  • Die GetProduct Aktion enthält die Vorlage und wird daher an die Vorlage auf dem Controller "{id}" id "api/[controller]" angefügt. Die Methodenvorlage ist "api/[controller]/"{id}"" . Daher stimmt diese Aktion nur mit GET-Anforderungen für das Format /api/test2/xyz , /api/test2/123 , /api/test2/{any string} usw. überein.
    [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 schränkt die id Routenwerte auf Zeichenfolgen ein, 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 Aktion enthält in der Vorlage, beschränkt GetInt2Product sich jedoch nicht auf {id} 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 ungültige Anforderung vom Typ 400 zurück, da die Modellbindung nicht in eine ganze Zahl konvertiert abc werden konnte.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Beim Attributrouting können Attribute wie , und verwendet HttpMethodAttribute HttpPostAttribute HttpPutAttribute HttpDeleteAttribute werden. Alle HTTP-Verbattribute akzeptieren eine Routenvorlage. Das folgende Beispiel zeigt zwei Aktionen, die derselben 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 /products3 URL-Pfads:

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

Beim Erstellen einer REST-API ist es selten, dass Sie für eine Aktionsmethode verwenden müssen, [Route(...)] da die Aktion alle HTTP-Methoden akzeptiert. Es ist besser, das spezifischere HTTP-Verbattribut zu verwenden, um genau zu erfahren, was Ihre API unterstützt. REST-API-Clients sollten wissen, welche Pfade und HTTP-Verben bestimmten logischen Operationen entsprechen.

REST-APIs sollten das Attributrouting verwenden, um die Funktionalität der App als eine Gruppe von Ressourcen zu modellieren, 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 /products2 ausgeführt.

Das [Consumes]-Attribut ermöglicht es einer Aktion, die unterstützten Anforderungsinhaltstypen einzuschränken. Weitere Informationen finden Sie unter Definieren unterstützter Anforderungsinhaltstypen mit dem Consumes-Attribut.

Eine vollständige Beschreibung und Routenvorlagen und dazugehörige Optionen finden Sie unter Routing in ASP.NET Core.

Weitere Informationen zu [ApiController] finden Sie unter ApiController-Attribut.

Routenname

Im folgenden Code wird ein Routenname von Products_List definiert:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Routennamen können verwendet werden, um basierend auf einer bestimmten Route eine URL zu generieren. Routennamen:

  • 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 vorangehenden Code mit der herkömmlichen Standardroute, die den id Parameter als optional definiert ( {id?} ). Die Möglichkeit, APIs genau anzugeben, hat Vorteile, z. B. das Zulassen /products von und das Senden an verschiedene /products/5 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 ProductsApi.GetProduct(int) übereinstimmen.

Beide Aktionen stimmen nur mit HTTP GET überein, da sie mit dem -Attribut markiert [HttpGet] sind.

Routenvorlagen, die auf eine Aktion angewendet werden, die mit einem / oder ~/ beginnen, können nicht mit Routenvorlagen kombiniert werden, die auf den Controller angewendet werden. Im folgenden Beispiel wird ein Satz von URL-Pfaden ähnlich der Standardroute verwendet.

[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 vorangehenden 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"

Reihenfolge der Attributrouten

Das Routing erstellt eine Struktur und gleicht alle Endpunkte gleichzeitig ab:

  • Die Routeneinträge verhalten sich so, als ob sie in einer idealen Reihenfolge platziert würden.
  • 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. Mit herkömmlichem Routingist 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. Das Festlegen einer Route mit Order = -1 wird vor Routen ausgeführt, die keine Reihenfolge festlegen. Das Festlegen einer Route mit Order = 1 wird nach der Standardroutenreihenfolge ausgeführt.

Vermeiden Sie je nach Order . Wenn der URL-Bereich einer App explizite Bestellwerte erfordert, um ordnungsgemäß weitergeleitet zu werden, ist dies wahrscheinlich auch für Clients verwirrend. Im Allgemeinen wählt das Attributrouting die richtige Route mit URL-Abgleich aus. Wenn die für die URL-Generierung verwendete Standardreihenfolge 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 /home definieren:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Die Anforderung /home mit dem vorangehenden 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:

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

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

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

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

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

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

Zur Vereinfachung unterstützen Attributrouten die Tokenersetzung, indem sie ein Token in eckige Klammern einschließen ( [ , ] ). Die Token [action] , und werden durch die Werte des [area] [controller] Aktionsnamens, Bereichsnamens und 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 nativen Sprache anzeigen möchten.

Attributrouten können auch mit Vererbung kombiniert werden. Dies ist leistungsfähig 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);
    }
}

Damit das Trennzeichen [ oder ] der Tokenersetzungs-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein ([[ oder ]]), was einem Escapezeichen entspricht.

Verwenden eines Parametertransformators zum Anpassen der Tokenersetzung

Die Tokenersetzung kann mit einem Parametertransformator angepasst werden. Ein Parametertransformator implementiert IOutboundParameterTransformer und wandelt den Wert der Parameter um. Beispielsweise ändert ein benutzerdefinierter SlugifyParameterTransformer Parametertransformator den 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 vorangehende ListAll Methode stimmt mit /subscription-management/list-all überein.

Die RouteTokenTransformerConvention wird als Option in ConfigureServices registriert.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

Die Definition von Slug finden Sie in der MDN-Webdokument zu Slug.

Warnung

Übergeben Sie ein Timeout, wenn Sie System.Text.RegularExpressions zum Verarbeiten nicht vertrauenswürdiger Eingaben verwenden. Ein böswilliger Benutzer kann Eingaben für RegularExpressions angeben, um einen Denial-of-Service-Angriff durchzuführen. ASP.NET Core-Framework-APIs, die RegularExpressions verwenden, übergeben ein Timeout.

Mehrere Attributrouten

Attributrouting unterstützt das Definieren mehrerer Routen, die zu derselben Aktion führen. Dies wird am häufigsten beim Imitieren des Verhaltens der herkömmlichen Standardroute verwendet. Im folgenden Beispiel wird dies gezeigt:

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Das Setzen mehrerer Routenattribute auf dem Controller bedeutet, dass jedes mit jedem der Routenattribute in den 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 implementieren, IActionConstraint für eine Aktion platziert werden:

  • Jede Aktionseinschränkung kombiniert mit der Routenvorlage, 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 leistungsstark erscheinen. Es ist besser, den URL-Bereich Ihrer App einfach und klar definiert zu halten. Verwenden Sie mehrere Routen nur bei Bedarf für Aktionen, 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 vorangehenden Code [HttpPost("product14/{id:int}")] wendet eine Routeneinschränkung an. Die Products14Controller.ShowProduct Aktion wird nur von URL-Pfaden wie abgegleichen. /product14/3 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 mit 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 IRouteTemplateProvider implementieren, um den anfänglichen Satz von Routen zu erstellen.

Implementieren IRouteTemplateProvider Sie , 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 vorangehende Get Methode gibt Order = 2, Template = api/MyTestApi zurück.

Verwenden des Anwendungsmodells zum Anpassen von Attributrouten

Das Anwendungsmodell:

  • Ein Objektmodell, das beim Start erstellt 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 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 die Anpassung des Routings mithilfe des Anwendungsmodells. Mit dem folgenden Code werden Routen ungefähr an die Ordnerstruktur des Projekts angerechnet.

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

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

Der folgende Controller verwendet beispielsweise nicht NamespaceRoutingConvention :

[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 dem Controller das Attribut "Route" zugewiesen ist.
  • Legt die Controllervorlage basierend auf dem namespace fest, wobei die Basis namespace entfernt wird.

NamespaceRoutingConventionKann in angewendet Startup.ConfigureServices 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 vorangehenden Controllers lautet My.Application.Admin.Controllers.UsersController .
  • NamespaceRoutingConventionLegt die Controllervorlage auf Admin/Controllers/Users/[action]/{id? fest.

NamespaceRoutingConventionKann 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 herkömmlichem Routing und Attributrouting kombinieren. In der Regel werden herkömmliche Routen für Controller verwendet, die für Browser-HTML-Seiten gedacht sind, und das Attributrouting für Controller für REST-APIs.

Aktionen werden entweder herkömmlich oder über Attribute zugeordnet, d.h., dass eine Route auf dem Controller oder der Aktion platziert wird. Aktionen, die Attributrouten definieren, können nicht über die herkömmliche Routen und umgekehrt erreicht werden. Bei jedem Routenattribut auf dem Controller werden alle Aktionen im Controllerattribut weitergeleitet.

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

URL-Generierung und Umgebungswerte

Apps können Routing-URL-Generierungsfunktionen verwenden, um URL-Links zu Aktionen zu generieren. Durch das Generieren von URLs entfällt die Hartcodierung von URLs, wodurch Code stabiler und verwendbarer wird. Dieser Abschnitt konzentriert sich auf die von MVC bereitgestellten Funktionen zur URL-Generierung und behandelt nur Grundlagen zur Funktionsweise 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 -Eigenschaft verwendet, Controller.Url um eine URL zu einer anderen Aktion zu generieren.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Wenn die App die herkömmliche Standardroute verwendet, ist der Wert der url 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 hat, kann:

  • Verwenden Sie einen Standardwert, sofern dieser über einen verfügt.
  • Wird übersprungen, 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 hat. 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 herkömmlichem Routingausgegangen. Die URL-Generierung funktioniert ähnlich wie das Attributrouting,obwohl die Konzepte unterschiedlich sind. Mit herkömmlichem Routing:

  • Die Routenwerte werden verwendet, um eine Vorlage zu erweitern.
  • Die Routenwerte für controller und werden in der Regel in dieser Vorlage action angezeigt. Dies funktioniert, da die durch 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 vorangehenden Code generiert custom/url/to/destination .

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

Generieren von URLs nach Aktionsnamen

Url.Action, LinkGenerator.GetPathByActionund alle zugehörigen Überladungen sind so konzipiert, dass der Zielendpunkt durch Angabe eines Controllernamens und Aktionsnamens generiert wird.

Bei Verwendung Url.Action von werden die aktuellen Routenwerte für controller und von der Laufzeit action bereitgestellt:

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

Beim Routing wird versucht, die Werte in Ambient-Werten zu verwenden, um Informationen auszufüllen, die beim Generieren einer URL nicht bereitgestellt 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 einen Wert aufweisen.

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. Im vorherigen Beispiel, wenn der Wert { c = Cheryl } hinzugefügt wird:

  • Beide Werte { c = Carol, d = David } werden ignoriert.
  • Es gibt keinen Wert mehr für d , und die URL-Generierung schlägt fehl.
  • Die gewünschten Werte von c und d müssen angegeben werden, um eine URL zu generieren.

Sie erwarten möglicherweise, dass dieses Problem mit der Standardroute {controller}/{action}/{id?} auftritt. Dieses Problem ist in der Praxis selten, da Url.Action immer explizit einen -Wert und einen controller -Wert angibt. action

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

  • Konventionsweise ist in der Regel ein Objekt vom anonymen Typ.
  • Kann ein IDictionary<> oder ein POCOsein.

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 .

Mit dem folgenden Code wird ein absolute URL generiert:

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

Um eine absolute URL zu erstellen, verwenden Sie eine der folgenden Möglichkeiten:

  • Eine Überladung, die eine protocol akzeptiert. Beispiel: der vorangehende Code.
  • LinkGenerator.GetUriByAction, das standardmäßig absolute URIs generiert.

Generieren von URLs nach Route

Der vorangehende Code veranschaulichte das Generieren einer URL durch Übergeben des Controllers und des Aktionsnamens. IUrlHelper stellt auch die Url.RouteUrl-Methodenfamilie bereit. Diese Methoden ähneln Url.Action, kopieren jedoch nicht die aktuellen Werte von action und in die controller Routenwerte. Die gängigste Verwendung von Url.RouteUrl :

  • Gibt einen Routennamen zum Generieren der URL an.
  • Im Allgemeinen wird kein Controller- oder Aktionsname angegeben.
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 zum Generieren von <form> - <a> bzw. -Elementen bereit. 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 Taghilfs-Hilfsmöglichkeiten 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 von IUrlHelper in einem Controller gezeigt. Die gängigste Verwendung in einem Controller ist das Generieren einer URL als Teil eines Aktionsergebnisses.

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

Sonderfall für dedizierte herkömmliche Routen

Herkömmliches Routing kann eine spezielle Art von Routendefinition verwenden, die als dedizierte konventionelle Routebezeichnet wird. Im folgenden Beispiel ist die Route namens 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 obigen 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. Die URL-Generierung mit blog schlägt fehl, da die Werte { controller = Home, action = Index } nicht mit { controller = Blog, action = Article } übereinstimmen. Routing greift dann wieder auf default zurück, was erfolgreich ausgeführt wird.

Bereiche

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

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

Die Verwendung von Bereichen ermöglicht es einer App, mehrere Controller mit dem gleichen Namen zu verwenden, solange sie unterschiedliche Bereiche aufweisen. 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 das Routing mit Bereichen interagiert. Ausführliche Informationen zur Verwendung von Bereichen mit Ansichten finden Sie unter Bereiche.

Im folgenden Beispiel wird MVC so konfiguriert, dass die herkömmliche Standardroute und eine Route für einen mit dem Namen verwendet area area Blog wird:

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

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

Beim Abgleichen eines URL-Pfads wie /Manage/Users/AddUser generiert die Route die "blog_route" Routenwerte { area = Blog, controller = Users, action = AddUser } . Der area Routenwert wird durch einen Standardwert für area erzeugt. Die von erstellte Route MapAreaControllerRoute 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 als Routen ohne Bereich sind.

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 kennzeichnet einen Controller als Teil eines Bereichs. Dieser Controller befindet sich im Blog Bereich . Controller ohne [Area] Attribut sind keine Elemente 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 zur Vollständigkeit angezeigt. Wenn die vorangehenden Controller denselben Namespace verwenden, wird ein Compilerfehler generiert. Klassennamespaces haben keine Auswirkungen auf das MVC-Routing.

Die ersten beiden Controller gehören zu Bereichen und werden können nur abgleichen, wenn ihr jeweiliger Bereichsname vom area-Routenwert bereitgestellt wird. Der dritte Controller gehört keinem Bereich an und kann nur abgleichen, wenn vom Routing kein Wert für area bereitgestellt wird.

Im Hinblick auf das Erkennen keines Werts hat die Abwesenheit des area-Werts dieselben Auswirkungen, wie wenn der Wert für area 0 (null) oder eine leere Zeichenfolge wäre.

Beim Ausführen einer Aktion innerhalb eines Bereichs ist der Routenwert für area als Umgebungswert für das Routing verfügbar, das für die URL-Generierung verwendet werden soll. 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 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 derjenigen 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"
    }
  }
}