Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenDieser Browser wird nicht mehr unterstützt.
Führen Sie ein Upgrade auf Microsoft Edge durch, um die neuesten Features, Sicherheitsupdates und den technischen Support zu nutzen.
Von Ryan Nowak, Kirk Larkin und Rick Anderson
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Das Routing wird für das Abgleichen von HTTP-Anforderungen und das Verteilen an ausführbare Endpunkte der App eingesetzt. Endpunkte sind die Einheiten des ausführbaren Codes für die Anforderungsverarbeitung in der App. Endpunkte werden in der App definiert und beim Start der App konfiguriert. Beim Endpunktabgleich können Werte aus der Anforderungs-URL extrahiert und für die Verarbeitung der Anforderung bereitgestellt werden. Mithilfe von Endpunktinformationen aus der App lassen sich durch das Routing URLs generieren, die Endpunkten zugeordnet werden.
Apps können das Routing mit folgenden Funktionen konfigurieren:
In diesem Artikel werden die grundlegenden Details zum ASP.NET Core-Routing beschrieben. Informationen zur Routingkonfiguration finden Sie wie folgt:
Der folgende Code veranschaulicht ein einfaches Beispiel für das Routing:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Das vorherige Beispiel enthält einen einzelnen Endpunkt unter Verwendung der MapGet-Methode:
GET
-HTTP-Anforderung an die Stamm-URL /
gesendet wird: Hello World!
wird in die HTTP-Antwort geschrieben.GET
bzw. die Stamm-URL nicht /
ist, gibt es keinen Routenabgleich und es wird ein HTTP-404-Fehler zurückgegeben.Beim Routing wird ein Middlewarepaar verwendet, das durch UseRouting und UseEndpoints registriert wird:
UseRouting
fügt der Middlewarepipeline einen Routenabgleich hinzu. Diese Middleware prüft die in der App definierten Endpunkte und wählt anhand der Anforderung die beste Übereinstimmung aus.UseEndpoints
fügt der Middlewarepipeline die Endpunktausführung hinzu. Dabei wird der mit dem ausgewählten Endpunkt verknüpfte Delegat ausgeführt.Apps müssen UseRouting
oder UseEndpoints
normalerweise nicht aufrufen. WebApplicationBuilder konfiguriert eine Middlewarepipeline, die die in Program.cs
hinzugefügte Middleware mit UseRouting
und UseEndpoints
umschließt. Apps können jedoch die Reihenfolge ändern, in der UseRouting
und UseEndpoints
ausgeführt werden, indem sie diese Methoden explizit aufrufen. Der folgende Code ruft beispielsweise UseRouting
explizit auf:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
Für den Code oben gilt:
app.Use
wird eine benutzerdefinierte Middleware registriert, die am Anfang der Pipeline ausgeführt wird.UseRouting
konfiguriert die Middleware für den Routenabgleich zur Ausführung nach der benutzerdefinierten Middleware.MapGet
registrierte Endpunkt wird am Ende der Pipeline ausgeführt.Wenn das vorhergehende Beispiel keinen Aufruf an UseRouting
enthält, wird die benutzerdefinierte Middleware nach der Middleware zum Routenabgleich ausgeführt.
Hinweis: Routen, die WebApplication direkt hinzugefügt werden, werden am Ende der Pipeline ausgeführt.
Die MapGet
-Methode wird verwendet, um einen Endpunkt zu definieren. Ein Endpunkt kann Folgendes sein:
Endpunkte, die von der App zugeordnet und ausgeführt werden können, sind in UseEndpoints
konfiguriert. Mit MapGet, MapPost und ähnlichen Methoden werden beispielsweise Anforderungsdelegate mit dem Routingsystem verbunden. Zudem können weitere Methoden zur Verbindung von ASP.NET Core-Frameworkfunktionen mit dem Routingsystem verwendet werden:
Das folgende Beispiel zeigt das Routing mit einer anspruchsvolleren Routenvorlage:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
Die Zeichenfolge /hello/{name:alpha}
ist eine Routenvorlage. Eine Routenvorlage, um zu konfigurieren, wie der Endpunkt abgeglichen wird. In diesem Fall gleicht die Vorlage Folgendes ab:
/hello/Docs
/hello/
beginnen, gefolgt von einer Sequenz alphabetischer Zeichen. :alpha
wendet eine Routeneinschränkung an, die nur alphabetische Zeichen abgleicht. Routeneinschränkungen werden weiter unten in diesem Artikel erläutert.Das zweite Segment des URL-Pfads, {name:alpha}
:
name
gebunden.Das folgende Beispiel zeigt das Routing mit Integritätsprüfungen und Autorisierung:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
Im vorherigen Beispiel wird veranschaulicht, wie Sie:
Der MapHealthChecks-Aufruf fügt einen Endpunkt für eine Integritätsprüfung hinzu. Durch die Verkettung von RequireAuthorization mit diesem Aufruf wird eine Autorisierungsrichtlinie an den Endpunkt angefügt.
Der Aufruf von UseAuthentication und UseAuthorization wird die Authentifizierungs- und Autorisierungsmiddleware hinzugefügt. Diese Middleware wird zum Ausführen folgender Aktionen zwischen UseRouting und UseEndpoints
platziert:
UseRouting
ausgewählten Endpunkts.Im vorangehenden Beispiel gibt es zwei Endpunkte, aber nur dem für die Integritätsprüfung ist eine Autorisierungsrichtlinie angefügt. Wenn die Anforderung mit dem Endpunkt der Integritätsprüfung, /healthz
, übereinstimmt, wird eine Autorisierungsprüfung durchgeführt. Dadurch wird veranschaulicht, dass Endpunkten zusätzliche Daten zugeordnet werden können. Diese zusätzlichen Daten werden als Metadaten des Endpunkts bezeichnet:
Durch Hinzufügen des effizienten Endpunkt-Konzepts stellt das Routingsystem eine Ergänzung der Middlewarepipeline dar. Endpunkte stehen für Funktionseinheiten der App, die sich in Bezug auf Routing, Autorisierung und die Anzahl der ASP.NET Core-Systeme voneinander unterscheiden.
Ein ASP.NET Core-Endpunkt ist:
Der folgende Code zeigt, wie der Endpunkt, der mit der aktuellen Anforderung übereinstimmt, abgerufen und geprüft werden kann:
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}
await next(context);
});
app.MapGet("/", () => "Inspect Endpoint.");
Der Endpunkt, falls ausgewählt, kann aus dem HttpContext
-Element abgerufen werden. Seine Eigenschaften können geprüft werden. Endpunktobjekte sind unveränderlich und können nach der Erstellung nicht mehr geändert werden. Der häufigste Typ des Endpunkts ist eine RouteEndpoint-Klasse. RouteEndpoint
enthält Informationen, die eine Auswahl durch das Routingsystem ermöglichen.
Im vorangehenden Code wird mit app.Use eine Middleware inline konfiguriert.
Der folgende Code zeigt, dass es, je nachdem, wo app.Use
in der Pipeline aufgerufen wird, möglicherweise keinen Endpunkt gibt:
// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");
app.UseEndpoints(_ => { });
// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
Im vorangehenden Beispiel werden Console.WriteLine
-Anweisungen hinzugefügt, die anzeigen, ob ein Endpunkt ausgewählt wurde oder nicht. Aus Gründen der Übersichtlichkeit wird in dem Beispiel dem bereitgestellten /
-Endpunkt ein Anzeigename zugewiesen.
Das vorherige Beispiel enthält auch Aufrufe an UseRouting
und UseEndpoints
, um genau zu steuern, wann diese Middleware in der Pipeline ausgeführt wird.
Wenn Sie diesen Code mit einer URL /
ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Wenn Sie diesen Code mit einer anderen URL ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Diese Ausgabe zeigt Folgendes:
UseRouting
aufgerufen wird.UseRouting
und UseEndpoints ungleich NULL.UseEndpoints
-Middleware ist eine Terminalmiddleware, wenn eine Übereinstimmung gefunden wird. Terminalmiddleware wird weiter unten in diesem Artikel definiert.UseEndpoints
wird nur ausgeführt, wenn keine Übereinstimmung gefunden wird.Die UseRouting
-Middleware verwendet die SetEndpoint-Methode, um den Endpunkt an den aktuellen Kontext anzufügen. Es ist möglich, die UseRouting
-Middleware durch benutzerdefinierte Logik zu ersetzen und dennoch die Vorteile durch die Verwendung von Endpunkten zu nutzen. Endpunkte befinden sich auf niedriger Ebene, wie Middleware, und sind nicht an die Routingimplementierung gekoppelt. Die meisten Apps müssen UseRouting
nicht durch eigene Logik ersetzen.
Die UseEndpoints
-Middleware ist so konzipiert, dass Sie zusammen mit der UseRouting
-Middleware verwendet werden kann. Die Hauptlogik zum Ausführen eines Endpunkts ist nicht kompliziert. Mit GetEndpoint können Sie einen Endpunkt abrufen und dann dessen RequestDelegate-Eigenschaft aufrufen.
Der folgende Code veranschaulicht, wie Middleware das Routing beeinflussen oder darauf reagieren kann:
app.UseHttpMethodOverride();
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
await next(context);
});
app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }
Im vorherigen Beispiel werden zwei wichtige Konzepte dargestellt:
UseRouting
ausgeführt werden, um die Daten zu ändern, auf denen das Routing basiert.
UseRouting
und UseEndpoints ausgeführt werden, um die Ergebnisse des Routings vor der Ausführung des Endpunkts zu verarbeiten.
UseRouting
und UseEndpoints
ausgeführt wird: UseAuthorization
und UseCors
.Der vorangehende Code zeigt ein Beispiel für eine benutzerdefinierte Middleware, die entpunktbezogene Richtlinien unterstützt. Die Middleware schreibt ein Überwachungsprotokoll für den Zugriff auf vertrauliche Daten in der Konsole. Die Middleware kann so konfiguriert werden, dass ein Endpunkt mit den -Metadaten RequiresAuditAttribute
wird. In diesem Beispiel wird ein Opt-In-Muster veranschaulicht, bei dem nur Endpunkte überwacht werden, die als vertraulich markiert sind. Es ist möglich, diese Logik umgekehrt zu definieren, indem beispielsweise alles geprüft wird, was nicht als sicher markiert ist. Das Endpunktmetadaten-System ist flexibel. Diese Logik lässt sich für jeden Anwendungsfall passend schreiben.
Der vorherige Beispielcode soll die grundlegenden Konzepte von Endpunkten veranschaulichen. Das Beispiel ist nicht für Produktionsumgebungen vorgesehen. Eine vollständigere Version einer Middleware für Überwachungsprotokolle würde Folgendes bieten:
Die Metadaten der Überwachungsrichtlinie RequiresAuditAttribute
sind als Attribute
definiert, um die Verwendung mit klassenbasierten Frameworks wie Controllern und SignalR zu erleichtern. Bei Verwendung von Route-zu-Code:
Die bewährten Methoden für Metadatentypen sind, sie entweder als Schnittstellen oder als Attribute zu definieren. Schnittstellen und Attribute ermöglichen die Wiederverwendung von Code. Das Metadatensystem ist flexibel und weist keine Einschränkungen auf.
Im folgenden Beispiel werden sowohl Terminalmiddleware als auch Routing veranschaulicht:
// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
app.UseRouting();
// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");
Beim in Approach 1:
gezeigten Stil von Middleware handelt es sich um Terminalmiddleware. Sie wird als Terminalmiddleware bezeichnet, da sie einen Abgleich durchgeführt.
Path == "/"
für die Middleware und Path == "/Routing"
für das Routing.next
-Middleware aufzurufen.Sie wird „Terminal Middleware“ genannt, da sie die Suche beendet, einige Funktionen ausführt und dann zurückkehrt.
In der folgenden Liste werden Terminalmiddleware und Routing verglichen:
next
aufzurufen.UseAuthorization
und UseCors
.
UseAuthorization
oder UseCors
erfordert eine manuelle Verknüpfung mit dem Autorisierungssystem.Ein Endpunkt definiert beides:
Terminalmiddleware kann sehr nützlich sein, erfordert aber möglicherweise auch:
Ziehen Sie daher zunächst die Integration von Routingfunktionen in Betracht, bevor Sie damit beginnen, Terminalmiddleware zu schreiben.
Vorhandene Terminalmiddleware, die in Map oder MapWhen integriert ist, kann in der Regel in einen routingfähigen Endpunkt umgewandelt werden. MapHealthChecks zeigt das Muster für eine Routinglösung:
Map
auf, und stellen Sie die neue Middlewarepipeline bereit.Map
aus der Erweiterungsmethode bereitgestellt wurde.Im folgenden Code ist die Verwendung von MapHealthChecks gezeigt:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
Das vorangehende Beispiel zeigt, warum das Zurückgeben des Generatorobjekts wichtig ist. Wenn das Generatorobjekt zurückgegeben wird, kann der App-Entwickler Richtlinien konfigurieren, z. B. die Autorisierung für den Endpunkt. In diesem Beispiel ist die Middleware für Integritätsprüfungen nicht direkt in das Autorisierungssystem integriert.
Das Metadatensystem wurde als Antwort auf die Probleme erstellt, die von Erweiterbarkeitsautoren mithilfe von Terminalmiddleware aufgetreten sind. Für jede Middleware ist es problematisch, deren eigene Integration in das Autorisierungssystem umzusetzen.
Wenn eine Routingmiddleware ausgeführt wird, legt sie ein Endpoint
-Element fest und leitet Werte an eine Anforderungsfunktion in der HttpContext-Klasse der aktuellen Anforderung weiter:
HttpRequest.RouteValues
ruft die Sammlung der Routenwerte ab.Middleware, die nach der Routingmiddleware ausgeführt wird, kann den Endpunkt untersuchen und Maßnahmen ergreifen. So kann beispielsweise eine Autorisierungsmiddleware die Erfassung der Metadaten des Endpunkts für eine Autorisierungsrichtlinie abfragen. Nachdem die gesamte Middleware in der Anforderungsverarbeitungspipeline ausgeführt wurde, wird der Delegat des ausgewählten Endpunkts aufgerufen.
Das Routingsystem ist beim Endpunktrouting für alle Weiterleitungsentscheidungen zuständig. Da die Middleware Richtlinien auf der Grundlage des ausgewählten Endpunkts anwendet, ist Folgendes wichtig:
Warnung
Wenn für Abwärtskompatibilität ein Controller- oder Razor Pages-Endpunktdelegat ausgeführt wird, werden die Eigenschaften von RouteContext.RouteData auf Grundlage der bisher verarbeiteten Anforderungen auf entsprechende Werte festgelegt.
Der Typ RouteContext
wird in einer zukünftigen Version als veraltet markiert:
RouteData.Values
zu HttpRequest.RouteValues
.RouteData.DataTokens
, um IDataTokensMetadata aus den Endpunktmetadaten abzurufen.Der URL-Abgleich erfolgt in mehreren Phasen und kann konfiguriert werden. In jeder Phase werden mehrere Übereinstimmungen ausgegeben. Diese Übereinstimmungen lassen sich in der nächsten Phase weiter eingrenzen. Die Routingimplementierung garantiert keine Verarbeitungsreihenfolge für übereinstimmende Endpunkte. Alle möglichen Übereinstimmungen werden gleichzeitig verarbeitet. Für die URL-Abgleichsphasen gilt folgende Reihenfolge. ASP.NET Core:
Die Liste der Endpunkte wird entsprechend den folgenden Punkten priorisiert:
Alle übereinstimmenden Endpunkte werden in jeder Phase verarbeitet, bis EndpointSelector erreicht ist. EndpointSelector
stellt die abschließende Phase dar. Darin wird der Endpunkt mit der höchsten Priorität aus den Übereinstimmungen als beste Übereinstimmung ausgewählt. Wenn es andere Übereinstimmungen mit derselben Priorität wie die beste Übereinstimmung gibt, wird ein Ausnahmefehler wegen einer nicht eindeutigen Übereinstimmung ausgelöst.
Die Routenpriorität wird anhand einer spezifischeren Routenvorlage berechnet, der eine höhere Priorität eingeräumt wird. Dies wird z. B. anhand der Vorlagen /hello
und /{message}
deutlich:
/hello
überein./hello
ist spezifischer und hat daher höhere Priorität.Im Allgemeinen eignet sich die Routenpriorität gut, um die beste Übereinstimmung für die in der Praxis verwendeten URL-Schemata zu finden. Verwenden Sie Order nur bei Bedarf, um eine Mehrdeutigkeit zu vermeiden.
Aufgrund der Erweiterungsmöglichkeiten, die das Routing bietet, kann das Routingsystem die mehrdeutigen Routen nicht im Voraus berechnen. Betrachten Sie ein Beispiel wie die Routenvorlagen /{message:alpha}
und /{message:int}
:
alpha
-Einschränkung gleicht nur alphabetische Zeichen ab.int
-Einschränkung gleicht nur Zahlen ab.Warnung
Die Reihenfolge der Vorgänge in UseEndpoints wirkt sich nicht auf das Routingverhalten aus, mit einer Ausnahme. MapControllerRoute und MapAreaRoute weisen ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Dadurch wird das Langzeitverhalten von Controllern simuliert, ohne dass das Routingsystem die gleichen Garantien bietet wie ältere Routingimplementierungen.
Endpunktrouting in ASP.NET Core:
Die Routenvorlagenpriorität ist ein System, bei dem jeder Routenvorlage ein Wert zugewiesen wird, je nachdem, wie spezifisch diese ist. Routenvorlagenpriorität:
Dies wird z. B. anhand der Vorlagen /Products/List
und /Products/{id}
deutlich. Es wäre begründet, anzunehmen, dass /Products/List
eine bessere Übereinstimmung als /Products/{id}
für den URL-Pfad /Products/List
ist. Dies funktioniert, weil das Literalsegment /List
eine höhere Priorität als das Parametersegment /{id}
hat.
Wie die Priorisierung im Einzelnen funktioniert, ist an die Definition der Routenvorlagen gekoppelt:
URL-Generierung:
Das Endpunktrouting umfasst die API zur Linkgenerierung (LinkGenerator). LinkGenerator
ist ein Singleton-Dienst, der in DI verfügbar ist. Die LinkGenerator
-API kann außerhalb des Kontexts einer ausgeführten Anforderung verwendet werden. Mvc.IUrlHelper und Szenarios, die von IUrlHelper abhängig sind (z. B. Taghilfsprogramme, HTML-Hilfsprogramme und Aktionsergebnisse), verwenden die LinkGenerator
-API, um entsprechende Funktionen bereitzustellen.
Die API zur Linkgenerierung wird von Konzepten wie Adressen und Adressschemas unterstützt. Sie können mithilfe eines Adressschemas die Endpunkte bestimmen, die bei der Linkgenerierung berücksichtigt werden sollen. Beispielsweise werden Routennamen und Routenwerte als Adressschemas implementiert. Diese Szenarios kennen viele Benutzer von Controllern und Razor Pages.
Die API zur Linkgenerierung kann Controller und Razor Pages über die folgenden Erweiterungsmethoden miteinander verknüpfen:
Beim Überladen dieser Methoden werden Argumente akzeptiert, die den HttpContext
umfassen. Diese Methoden sind zwar in funktionaler Hinsicht äquivalent zu Url.Action und Url.Page, bieten aber zusätzliche Flexibilität und Optionen.
Die GetPath*
-Methoden sind Url.Action
und Url.Page
in der Hinsicht ähnlich, dass sie einen URI mit einem absoluten Pfad generieren. Die GetUri*
-Methoden generieren immer einen absoluten URI mit einem Schema und einem Host. Die Methoden, die einen HttpContext
akzeptieren, generieren im Kontext der ausgeführten Anforderung einen URI. Die Umgebungsroutenwerte, der URL-basierte Pfad, das Schema und der Host von der ausführenden Anforderung werden so lange verwendet, bis sie außer Kraft gesetzt werden.
LinkGenerator wird mit einer Adresse aufgerufen. Ein URI wird in zwei Schritten generiert:
Die von LinkGenerator bereitgestellten Methoden unterstützen die Standardfunktionen zur Generierung von Links für jeden beliebigen Adresstypen. Am praktischsten ist es, die API zur Linkgenerierung mit Erweiterungsmethoden zu verwenden, die Vorgänge für einen bestimmten Adresstypen ausführen:
Erweiterungsmethode | Beschreibung |
---|---|
GetPathByAddress | Generiert einen URI mit einem absoluten Pfad, der auf den angegebenen Werten basiert. |
GetUriByAddress | Generiert einen absoluten URI, der auf den angegebenen Werten basiert. |
Warnung
Beachten Sie die folgenden Implikationen zum Aufrufen von LinkGenerator-Methoden:
Verwenden Sie GetUri*
-Erweiterungsmethoden in App-Konfigurationen, die den Host
-Header von eingehenden Anforderungen nicht überprüfen, mit Bedacht. Wenn der Host
-Header von eingehenden Anforderungen nicht überprüft wird, können nicht vertrauenswürdige Anforderungseingaben zurück an den Client in URIs einer Ansicht bzw. Seite zurückgesendet werden. Es wird empfohlen, dass alle Produktions-Apps ihren Server so konfigurieren, dass der Host
-Header auf bekannte gültige Werte überprüft wird.
Verwenden Sie LinkGenerator in Kombination mit Map
oder MapWhen
in Middleware mit Bedacht. Map*
ändert den Basispfad der ausgeführten Anforderung. Dies beeinflusst die Ausgabe der Linkgenerierung. Für alle LinkGenerator-APIs ist die Angabe eines Basispfads zulässig. Geben Sie einen leeren Basispfad an, um die Auswirkungen von Map*
auf die Linkgenerierung rückgängig zu machen.
Im folgenden Beispiel verwendet eine Middleware die LinkGenerator-API, um eine Verknüpfung zu einer Aktionsmethode herzustellen, die Speicherprodukte aufführt. Sie können für jede beliebige Klasse in einer App die API zur Linkgenerierung verwenden, indem Sie diese in eine Klasse einfügen und GenerateLink
aufrufen:
public class ProductsMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public async Task InvokeAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
var productsPath = _linkGenerator.GetPathByAction("Products", "Store");
await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}
Token in {}
definieren Routenparameter, die beim Abgleich der Route gebunden werden. In einem Routensegment können mehrere Routenparameter definiert werden, müssen aber durch einen Literalwert getrennt werden. Beispiel:
{controller=Home}{action=Index}
ist keine gültige Route, da sich zwischen {controller}
und {action}
kein Literalwert befindet. Routenparameter müssen einen Namen besitzen und können zusätzliche Attribute aufweisen.
Eine Literalzeichenfolge, die nicht den Routenparametern entspricht (z.B. {id}
), muss zusammen mit dem Pfadtrennzeichen /
mit dem URL-Text übereinstimmen. Beim Abgleich von Text wird nicht zwischen Groß-/Kleinbuchstaben unterschieden, und die Übereinstimmung basiert auf der decodierten Repräsentation des URL-Pfads. Damit das Trennzeichen ({
oder }
) der Routenparameter-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein, was einem Escapezeichen entspricht. Beispielsweise {{
oder }}
.
Sternchen *
oder Doppelsternchen **
:
blog/{**slug}
: blog/
beginnt und einem beliebigen Wert folgt.blog/
wird dem Slug-Routenwert zugewiesen.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:
{**slug}"
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.
Durch Catch-All-Parameter können auch leere Zeichenfolgen gefunden werden.
Der Catch-All-Parameter schützt die entsprechenden Zeichen (Escaping), wenn die Route verwendet wird, um eine URL, einschließlich Pfadtrennzeichen (/
) zu generieren. Z.B. generiert die Route foo/{*path}
mit den Routenwerten { path = "my/path" }
foo/my%2Fpath
. Beachten Sie den umgekehrten Schrägstrich mit Escapezeichen. Um Trennzeichen für Roundtrips einsetzen zu können, verwenden Sie das Routenparameterpräfix **
. Die Route foo/{**path}
mit { path = "my/path" }
generiert foo/my/path
.
Bei einem URL-Muster, durch das ein Dateiname mit einer optionalen Erweiterung erfasst werden soll, sind noch weitere Aspekte zu berücksichtigen. Dies wird z.B. anhand der Vorlage files/{filename}.{ext?}
deutlich. Wenn sowohl für filename
als auch für ext
Werte vorhanden sind, werden beide Werte angegeben. Wenn nur für filename
ein Wert in der URL vorhanden ist, wird für die Route eine Übereinstimmung ermittelt, da der nachstehende Punkt (.
) optional ist. Für die folgenden URLs wird eine Übereinstimmung für die Route ermittelt:
/files/myFile.txt
/files/myFile
Routenparameter können über mehrere Standardwerte verfügen, die nach dem Parameternamen angegeben werden und durch ein Gleichheitszeichen (=
) voneinander getrennt werden. Mit {controller=Home}
wird beispielsweise Home
als Standardwert für controller
definiert. Der Standardwert wird verwendet, wenn kein Wert in der Parameter-URL vorhanden ist. Routenparameter sind optional, wenn am Ende des Parameternamens ein Fragezeichen (?
) angefügt wird. Beispielsweise id?
. Zwischen optionalen Werten und Standardroutenparametern besteht folgender Unterschied:
Routenparameter können Einschränkungen aufweisen, die mit dem gebundenen Routenwert der URL übereinstimmen müssen. Eine Inline-Einschränkung für einen Routenparameter geben Sie an, indem Sie hinter dem Namen des Routenparameters einen Doppelpunkt (:
) und einen Einschränkungsnamen hinzufügen. Wenn für die Einschränkung Argumente erforderlich sind, werden diese nach dem Einschränkungsnamen in Klammern ((...)
) eingeschlossen. Mehrere Inline-Einschränkungen können festgelegt werden, indem ein weiterer Doppelpunkt (:
) und Einschränkungsname hinzugefügt werden.
Der Einschränkungsname und die Argumente werden dem IInlineConstraintResolver-Dienst übergeben, wodurch eine Instanz von IRouteConstraint für die URL-Verarbeitung erstellt werden kann. In der Routenvorlage blog/{article:minlength(10)}
wird beispielsweise die Einschränkung minlength
mit dem Argument 10
festgelegt. Weitere Informationen zu Routeneinschränkungen und eine Liste der vom Framework bereitgestellten Einschränkungen finden Sie im Abschnitt Routeneinschränkungen.
Routenparameter können darüber hinaus über Parametertransformatoren verfügen. Diese wandeln den Wert eines Parameters beim Generieren von Links um und passen Aktionen und Seiten an URLs an. Wie Einschränkungen können auch Parametertransformatoren einem Routenparameter inline hinzugefügt werden, indem ein Doppelpunkt (:
) und der Name des Transformators hinter dem Namen des Routenparameters hinzugefügt werden. In der Routenvorlage blog/{article:slugify}
wird beispielsweise der Transformator slugify
festgelegt. Weitere Informationen zu Parametertransformatoren finden Sie im Abschnitt Parametertransformatoren.
Die folgende Tabelle enthält Beispielvorlagen für Routen und deren Verhalten:
Routenvorlage | Beispiel-URI für Übereinstimmung | Der Anforderungs-URI |
---|---|---|
hello |
/hello |
Nur für den Pfad /hello wird eine Übereinstimmung ermittelt. |
{Page=Home} |
/ |
Eine Übereinstimmung wird ermittelt, und Page wird auf Home festgelegt. |
{Page=Home} |
/Contact |
Eine Übereinstimmung wird ermittelt, und Page wird auf Contact festgelegt. |
{controller}/{action}/{id?} |
/Products/List |
Stimmt mit dem Products -Controller und der List -Aktion überein. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Wird dem Controller Products und der Aktion Details zugeordnet, bei der id auf 123 festgelegt ist. |
{controller=Home}/{action=Index}/{id?} |
/ |
Stimmt mit dem Home -Controller und der Index -Methode überein. id wird ignoriert. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Stimmt mit dem Products -Controller und der Index -Methode überein. id wird ignoriert. |
Mit Vorlagen lässt sich Routing besonders leicht durchführen. Einschränkungen und Standardwerte können auch außerhalb der Routenvorlage angegeben werden.
Komplexe Segmente werden von rechts nach links auf eine nicht gierige Weise durch entsprechende Literaltrennzeichen verarbeitet. Beispielsweise ist [Route("/a{b}c{d}")]
ein komplexes Segment.
Komplexe Segmente funktionieren auf eine bestimmte Weise, die für eine erfolgreiche Verwendung verstanden werden muss. Das Beispiel in diesem Abschnitt zeigt, warum komplexe Segmente nur dann wirklich gut funktionieren, wenn der Trennzeichentext nicht innerhalb der Parameterwerte erscheint. Für komplexere Fälle ist die Verwendung eines RegEx und das anschließende manuelle Extrahieren der Werte erforderlich.
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.
Dies ist eine Zusammenfassung der Schritte, die beim Routing mit der Vorlage /a{b}c{d}
und dem URL-Pfad /abcd
ausgeführt werden. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen:
c
. Daher wird /abcd
von rechts durchsucht und /ab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /ab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.Nachfolgend ist ein Beispiel für einen negativen Fall mit derselben Vorlage /a{b}c{d}
und dem URL-Pfad /aabcd
. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen: Bei diesem Fall handelt es sich nicht um eine Übereinstimmung, was durch denselben Algorithmus belegt wird:
c
. Daher wird /aabcd
von rechts durchsucht und /aab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /aab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /a|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.a
, aber es gibt keine Routenvorlage mehr, die der Algorithmus analysieren kann, weshalb dies keine Übereinstimmung ist.Da der übereinstimmende Algorithmus nicht gierig ist:
Reguläre Ausdrücke bieten eine viel bessere Kontrolle über das Abgleichsverhalten.
Beim Greedy-Abgleich, auch als maximaler Abgleich bezeichnet, wird nach der längsten möglichen Übereinstimmung im Eingabetext gesucht, die dem RegEx-Muster entspricht. Beim Non-Greedy-Abgleich, auch bekannt als Lazy Matching, wird nach der kürzesten möglichen Übereinstimmung im Eingabetext gesucht, die dem RegEx-Muster entspricht.
Ein Routing mit Sonderzeichen kann zu unerwarteten Ergebnissen führen. Stellen Sie sich z. B. einen Controller mit der folgenden Aktionsmethode vor:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Wenn string id
die folgenden codierten Werte enthält, können unerwartete Ergebnisse auftreten:
ASCII | Codiert |
---|---|
/ |
%2F |
|
+ |
Routenparameter sind nicht immer URL-decodiert. Dieses Problem wird möglicherweise in Zukunft behoben. Weitere Informationen finden Sie in diesem GitHub-Issue.
Routeneinschränkungen werden angewendet, wenn eine Übereinstimmung mit der eingehenden URL gefunden wurde und der URL-Pfad in Routenwerten mit Token versehen wird. In der Regel wird mit Routeneinschränkungen der Routenwert der zugehörigen Vorlage geprüft. Dabei wird anhand einer True/False-Entscheidung bestimmt, ob der Wert gültig ist. Für einige Routeneinschränkungen werden anstelle des Routenwerts andere Daten verwendet, um zu ermitteln, ob das Routing einer Anforderung möglich ist. HttpMethodRouteConstraint kann beispielsweise auf der Grundlage des HTTP-Verbs eine Anforderung entweder annehmen oder ablehnen. Einschränkungen werden in Routinganforderungen und bei der Linkgenerierung verwendet.
Warnung
Verwenden Sie keine Einschränkungen für die Eingabeüberprüfung. Wenn Einschränkungen für die Eingabevalidierung verwendet werden, führt eine ungültige Eingabe zu einem 404
-Fehler (Nicht gefunden). Eine ungültige Eingabe sollte zu einer ungültigen Anforderung (400
) mit einer entsprechenden Fehlermeldung führen. Verwenden Sie Routeneinschränkungen nicht, um Eingaben für eine bestimmte Route zu überprüfen, sondern um ähnliche Routen zu unterscheiden.
In der folgenden Tabelle werden Beispiele für Routeneinschränkungen und deren zu erwartendes Verhalten beschrieben:
Einschränkung | Beispiel | Beispiele für Übereinstimmungen | Hinweise |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Für jeden Integer wird eine Übereinstimmung ermittelt. |
bool |
{active:bool} |
true , FALSE |
Entspricht true oder false . Groß-/Kleinschreibung nicht beachten |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Entspricht einem gültigen DateTime -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Entspricht einem gültigen decimal -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen double -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen float -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Für einen gültigen Guid -Wert wird eine Übereinstimmung ermittelt. |
long |
{ticks:long} |
123456789 , -123456789 |
Für einen gültigen long -Wert wird eine Übereinstimmung ermittelt. |
minlength(value) |
{username:minlength(4)} |
Rick |
Die Zeichenfolge muss mindestens eine Länge von 4 Zeichen aufweisen. |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Die Zeichenfolge darf maximal eine Länge von 8 Zeichen aufweisen. |
length(length) |
{filename:length(12)} |
somefile.txt |
Die Zeichenfolge muss genau 12 Zeichen aufweisen. |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Die Zeichenfolge muss mindestens eine Länge von 8 und darf maximal eine Länge von 16 Zeichen aufweisen. |
min(value) |
{age:min(18)} |
19 |
Der Integerwert muss mindestens 18 sein. |
max(value) |
{age:max(120)} |
91 |
Der Integerwert darf nicht größer als 120 sein. |
range(min,max) |
{age:range(18,120)} |
91 |
Der Integerwert muss zwischen 18 und 120 liegen. |
alpha |
{name:alpha} |
Rick |
Die Zeichenfolge muss aus mindestens einem alphabetische Zeichen bestehen, a -z und ohne Unterscheidung zwischen Groß-/Kleinbuchstaben. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Die Zeichenfolge muss mit dem regulären Ausdruck übereinstimmen. Weitere Informationen finden Sie unter Tipps zum Definieren eines regulären Ausdrucks. |
required |
{name:required} |
Rick |
Hierdurch wird erzwungen, dass ein Wert, der kein Parameter ist, für die URL-Generierung vorhanden sein muss. |
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.
Auf einen einzelnen Parameter können mehrere durch Doppelpunkte getrennte Einschränkungen angewendet werden. Durch die folgende Einschränkung wird ein Parameter beispielsweise auf einen Integerwert größer oder gleich 1 beschränkt:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warnung
Für Routeneinschränkungen, mit denen die URL überprüft wird und die in den CLR-Typ umgewandelt werden, wird immer die invariante Kultur verwendet. Dies gilt z. B. für die Konvertierung in den CLR-Typ int
oder DateTime
. Diese Einschränkungen setzen voraus, dass die URL nicht lokalisierbar ist. Die vom Framework bereitgestellten Routeneinschränkungen ändern nicht die Werte, die in Routenwerten gespeichert sind. Alle Routenwerte, die aus der URL analysiert werden, werden als Zeichenfolgen gespeichert. Durch die float
-Einschränkung wird beispielsweise versucht, den Routenwert in einen Gleitkommawert zu konvertieren. Mit dem konvertierten Wert wird allerdings nur überprüft, ob eine Umwandlung überhaupt möglich ist.
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.
Reguläre Ausdrücke können mithilfe der regex(...)
-Routeneinschränkung als Inline-Einschränkungen angegeben werden. Methoden der MapControllerRoute-Familie akzeptieren auch ein Objektliteral von Einschränkungen. Wenn dieses Formular verwendet wird, werden Zeichenfolgenwerte als reguläre Ausdrücke interpretiert.
Der folgende Code verwendet eine Inline-RegEx-Einschränkung:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
Der folgende Code verwendet ein Objektliteral, um eine RegEx-Einschränkung anzugeben:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
Im ASP.NET Core-Framework wird dem Konstruktor für reguläre Ausdrücke RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
hinzugefügt. Eine Beschreibung dieser Member finden Sie unter RegexOptions.
In regulären Ausdrücken werden Trennzeichen und Token verwendet, die auch beim Routing und in der Programmiersprache C# in ähnlicher Weise verwendet werden. Token, die reguläre Ausdrücke enthalten, müssen mit einem Escapezeichen versehen werden. Wenn Sie den regulären Ausdruck ^\d{3}-\d{2}-\d{4}$
in einer Inline-Einschränkung verwenden möchten, nutzen Sie eine der folgenden Optionen:
\
-Zeichen in der Zeichenfolge durch \\
-Zeichen in der C#-Quelldatei, um das Escapezeichen für die Zeichenfolge \
zu setzen.Wenn Sie Trennzeichen für Routenparameter mit Escapezeichen versehen möchten ({
, }
, [
, ]
), geben Sie jedes Zeichen im Ausdruck doppelt ein (z. B. {{
, }}
, [[
, ]]
). In der folgenden Tabelle werden reguläre Ausdrücke und Ausdrücke aufgeführt, die mit Escapezeichen versehen sind:
Regulärer Ausdruck | Mit Escapezeichen versehener regulärer Ausdruck |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Beim Routing verwendete reguläre Ausdrücke beginnen oft mit dem ^
-Zeichen und stellen die Startposition der Zeichenfolge dar. Die Ausdrücke enden häufig mit einem Dollarzeichen ($
) und stellen das Ende der Zeichenfolge dar. Mit den Zeichen ^
und $
wird sichergestellt, dass der reguläre Ausdruck mit dem vollständigen Routenparameterwert übereinstimmt. Ohne die Zeichen ^
und $
werden mit dem regulären Ausdruck alle Teilzeichenfolgen ermittelt, was häufig nicht gewünscht ist. In der folgenden Tabelle finden Sie Beispiele für reguläre Ausdrücke. Außerdem wird erklärt, warum ein Abgleich erfolgreich ist oder fehlschlägt:
expression | Zeichenfolge | Match | Kommentar |
---|---|---|---|
[a-z]{2} |
hello | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
123abc456 | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
mz | Ja | Ausdruck stimmt überein |
[a-z]{2} |
MZ | Ja | keine Unterscheidung zwischen Groß-/Kleinbuchstaben |
^[a-z]{2}$ |
hello | Nein | siehe Erläuterungen zu ^ und $ oben |
^[a-z]{2}$ |
123abc456 | Nein | siehe Erläuterungen zu ^ und $ oben |
Weitere Informationen zur Syntax von regulären Ausdrücken finden Sie unter Sprachelemente für reguläre Ausdrücke – Kurzübersicht.
Einen regulären Ausdruck können Sie verwenden, um einen Parameter auf zulässige Werte einzuschränken. Mit {action:regex(^(list|get|create)$)}
werden beispielsweise für den action
-Routenwert nur die Werte list
, get
oder create
abgeglichen. Wenn die Zeichenfolge ^(list|get|create)$
dem Einschränkungswörterbuch übergeben wird, führt dies zum gleichen Ergebnis. Auch Einschränkungen, die dem zugehörigen Wörterbuch hinzugefügt werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden als reguläre Ausdrücke behandelt. Einschränkungen, die innerhalb einer Vorlage übergeben werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden nicht als reguläre Ausdrücke behandelt.
Benutzerdefinierte Routeneinschränkungen können durch Implementierung der IRouteConstraint-Schnittstelle erstellt werden. Die IRouteConstraint
-Schnittstelle umfasst die Match-Methode, die true
zurückgibt, wenn die Einschränkung erfüllt wird, und andernfalls false
.
Benutzerdefinierte Routeneinschränkungen werden nur selten benötigt. Bevor Sie eine benutzerdefinierte Routeneinschränkung implementieren, sollten Sie Alternativen in Betracht ziehen, wie z. B. Modellbindung.
Der ASP.NET Core-Ordner Constraints bietet nützliche Beispiele für die Erstellung von Einschränkungen. Beispiel: GuidRouteConstraint.
Zum Verwenden eines benutzerdefinierten IRouteConstraint
-Elements muss der Routeneinschränkungstyp bei der ConstraintMap-Eigenschaft der App im Dienstcontainer registriert werden. Eine ConstraintMap
ist ein Wörterbuch, das Routeneinschränkungsschlüssel IRouteConstraint
-Implementierungen zuordnet, die diese Einschränkungen überprüfen. Die ConstraintMap
einer App kann in Program.cs
entweder als Teil eines AddRouting-Aufrufs oder durch direktes Konfigurieren von RouteOptions mit builder.Services.Configure<RouteOptions>
aktualisiert werden. Beispiel:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
Die vorangehende Einschränkung wird im folgenden Code angewendet:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
Die Implementierung von NoZeroesRouteConstraint
verhindert die Verwendung von 0
in einem Routenparameter:
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
public bool Match(
HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
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.
Der vorangehende Code:
0
im {id}
-Segment der Route vorhanden ist.Der folgende Code bietet einen besseren Ansatz, um zu verhindern, dass eine id
mit einer 0
verarbeitet wird:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
Der vorangehende Code bietet im Vergleich zum NoZeroesRouteConstraint
-Ansatz folgende Vorteile:
0
enthält.Parametertransformatoren:
Beispielsweise generiert ein benutzerdefinierter Parametertransformator slugify
im Routenmuster blog\{article:slugify}
mit Url.Action(new { article = "MyTestArticle" })
blog\my-test-article
.
Betrachten Sie die folgende Implementierung von IOutboundParameterTransformer
:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Um einen Parametertransformator in einem Routenmuster zu verwenden, konfigurieren Sie ihn mit ConstraintMap in Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
Das ASP.NET Core-Framework verwendet Parametertransformatoren, um den URI zu transformieren, zu dem ein Endpunkt aufgelöst wird. Beispielsweise wandeln Parametertransformatoren die Routenwerte um, die zum Zuordnen folgender Elemente verwendet werden: area
, controller
, action
und page
.
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Mit der vorstehenden Routenvorlage wird die Aktion SubscriptionManagementController.GetAll
dem URI /subscription-management/get-all
zugeordnet. Ein Parametertransformator ändert nicht die zum Generieren eines Links verwendeten Routenwerte. Beispielsweise gibt Url.Action("GetAll", "SubscriptionManagement")
/subscription-management/get-all
aus.
ASP.NET Core bietet API-Konventionen für die Verwendung von Parametertransformatoren mit generierten Routen:
Dieser Abschnitt enthält eine Referenz für den Algorithmus, der durch die URL-Generierung implementiert wird. In der Praxis werden bei den meisten komplexen Beispielen für die URL-Generierung Controller oder Razor Pages verwendet. Weitere Informationen finden Sie unter Routing in Controllern.
Die URL-Generierung beginnt mit einem Aufruf von LinkGenerator.GetPathByAddress oder einer ähnlichen Methode. Die Methode wird mit einer Adresse, mehreren Routenwerten und optional mit Informationen zur aktuellen Anforderung von HttpContext
versehen.
Im ersten Schritt wird die Adresse verwendet, um bestimmte Endpunktkandidaten mithilfe einer IEndpointAddressScheme<TAddress>-Schnittstelle aufzulösen, die dem Adresstyp entspricht.
Sobald eine Kandidatengruppe anhand des Adressschemas gefunden wurde, werden die Endpunkte geordnet und iterativ verarbeitet, bis die URL-Generierung erfolgreich abgeschlossen ist. Bei der URL-Generierung wird nicht auf Mehrdeutigkeiten geprüft, daher ist das erste zurückgegebene Ergebnis das Endergebnis.
Der erste Schritt bei der Behebung von Problemen bei der URL-Generierung ist die Einstellung des Protokolliergrads von Microsoft.AspNetCore.Routing
auf TRACE
. LinkGenerator
protokolliert viele Details über die Verarbeitung, die bei der Problembehebung nützlich sein können.
Ausführliche Informationen zur URL-Generierung finden Sie unter Referenz für URL-Generierung.
Mithilfe von Adressen wird bei der URL-Generierung ein Aufruf in der API zur Linkgenerierung an mehrere Endpunktkandidaten gebunden.
Adressen sind ein erweiterbares Konzept, das standardmäßig mit zwei Implementierungen bereitgestellt wird:
string
) als Adresse: IUrlHelper
, Taghilfsprogrammen, HTML-Hilfsprogrammen, Aktionsergebnissen usw. verwendet wird.Aufgabe des Adressschemas ist es, die Verbindung zwischen der Adresse und den übereinstimmenden Endpunkten anhand von beliebigen Kriterien herzustellen:
Aus der aktuellen Anforderung greift das Routing auf die Routenwerte der aktuellen Anforderung HttpContext.Request.RouteValues
zu. Die mit der aktuellen Anforderung verbundenen Werte werden als Umgebungswerte bezeichnet. Aus Gründen der Übersichtlichkeit werden in der Dokumentation die an die Methoden übergebenen Routenwerte als explizite Werte bezeichnet.
Das folgende Beispiel zeigt Umgebungswerte und explizite Werte. Er liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte:
public class WidgetController : ControllerBase
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public IActionResult Index()
{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;
return Content(indexPath);
}
// ...
Der vorangehende Code:
/Widget/Index/17
zurück.Der folgende Code liefert nur explizite Werte und keine Umgebungswerte:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
Die vorhergehende Methode gibt /Home/Subscribe/17
zurück.
Der folgende Code in WidgetController
gibt /Widget/Subscribe/17
zurück:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
Der folgende Code stellt den Controller aus den Umgebungswerten in der aktuellen Anforderung und explizite Werte dar:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
Für den Code oben gilt:
/Gadget/Edit/17
wird zurückgegeben.action
-Namen und route
-Werte.Sie liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
Im vorangehenden Code wird url
auf /Edit/17
festgelegt, wenn die Option zum Bearbeiten der Razor Page die folgende Seitenanweisung enthält:
@page "{id:int}"
Wenn die Routenvorlage "{id:int}"
nicht in der Seite „Bearbeiten“ enthalten ist, ist url
gleich /Edit?id=17
.
Das Verhalten der IUrlHelper-Schnittstelle von MVC fügt zusätzlich zu den hier beschriebenen Regeln eine weitere Komplexitätsebene hinzu:
IUrlHelper
liefert immer die Routenwerte aus der aktuellen Anforderung als Umgebungswerte.action
und controller
als explizite Werte, sofern sie nicht vom Entwickler außer Kraft gesetzt werden.page
als expliziten Wert, sofern er nicht außer Kraft gesetzt wird. IUrlHelper.Page
setzt immer den aktuellen Routenwert handler
mit null
als expliziten Wert außer Kraft, sofern er nicht außer Kraft gesetzt wird.Benutzer sind oft von den Verhaltensdetails der Umgebungswerte überrascht, da MVC anscheinend nicht den eigenen Regeln folgt. Aus Verlaufs- und Kompatibilitätsgründen weisen bestimmte Routenwerte wie action
, controller
, page
und handler
ein spezielles Verhalten auf.
Die äquivalente Funktionalität, die durch LinkGenerator.GetPathByAction
und LinkGenerator.GetPathByPage
bereitgestellt wird, verdoppelt diese Anomalien von IUrlHelper
aus Kompatibilitätsgründen.
Sobald die Gruppe der Endpunktkandidaten ermittelt ist, wird der URL-Generierungsalgorithmus angewendet:
Der erste Schritt in diesem Prozess wird als Routenwertinvalidierung bezeichnet. Die Routenwertinvalidierung ist der Prozess, bei dem das Routing entscheidet, welche Routenwerte aus den Umgebungswerten verwendet und welche ignoriert werden sollen. Jeder Umgebungswert wird berücksichtigt und entweder mit den expliziten Werten kombiniert oder aber ignoriert.
Denken Sie daran, dass Umgebungswerte Anwendungsentwicklern in allgemeinen Fällen das Schreiben von Code sparen können. In der Regel sind die Szenarios, in denen Umgebungswerte hilfreich sind, mit MVC verknüpft:
Aufrufe an LinkGenerator
oder IUrlHelper
, die null
zurückgeben, sind meist dadurch bedingt, dass die Routenwertinvalidierung nicht verstanden wurde. Beheben Sie die Routenwertinvalidierung, indem Sie explizit mehr Routenwerte angeben, um zu prüfen, ob das Problem dadurch gelöst wird.
Bei der Routenwertinvalidierung wird davon ausgegangen, dass das URL-Schema der Anwendung hierarchisch ist, mit einer von links nach rechts gebildeten Hierarchie. Sehen Sie sich die einfache Controllerroutenvorlage {controller}/{action}/{id?}
an, um ein Gespür dafür zu bekommen, wie dies in der Praxis funktioniert. Durch eine Änderung auf einen Wert werden alle rechts angezeigten Routenwerte ungültig. Dies spricht für die These von der Hierarchie. Wenn die App einen Umgebungswert für id
hat und der Vorgang einen anderen Wert für controller
angibt:
id
wird nicht wiederverwendet, weil {controller}
links von {id?}
steht.Einige Beispiele veranschaulichen dieses Prinzip:
id
enthalten, wird der Umgebungswert für id
ignoriert. Die Umgebungswerte für controller
und action
können verwendet werden.action
enthalten, wird jeder Umgebungswert für action
ignoriert. Die Umgebungswerte für controller
können verwendet werden. Wenn sich der explizite Wert für action
von dem Umgebungswert für action
unterscheidet, wird der Wert id
nicht verwendet. Wenn der explizite Wert für action
mit dem Umgebungswert für action
übereinstimmt, kann der Wert id
verwendet werden.controller
enthalten, wird jeder Umgebungswert für controller
ignoriert. Wenn sich der explizite Wert für controller
von dem Umgebungswert für controller
unterscheidet, werden die Werte action
und id
nicht verwendet. Wenn der explizite Wert für controller
mit dem Umgebungswert für controller
übereinstimmt, können die Werte action
und id
verwendet werden.Dieser Prozess wird zusätzlich durch die vorhandenen Attributrouten und dedizierten konventionellen Routen erschwert. Konventionelle Routen des Controllers wie {controller}/{action}/{id?}
legen eine Hierarchie mithilfe von Routenparametern fest. Bei bestimmten konventionellen Routen und Attributrouten zu Controllern und Razor Pages:
Für diese Fälle definiert die URL-Generierung das Konzept der erforderlichen Werte. Bei Endpunkten, die von Controllern und Razor Pages erstellt wurden, sind erforderliche Werte angegeben, die eine Routenwertinvalidierung ermöglichen.
Der Algorithmus der Routenwertinvalidierung im Detail:
An diesem Punkt ist der Vorgang zur URL-Generierung bereit, Routeneinschränkungen auszuwerten. Die akzeptierten Werte werden mit den Standardwerten der Parameter kombiniert, die für Einschränkungen bereitgestellt werden. Wenn alle Einschränkungen erfüllt sind, wird der Vorgang fortgesetzt.
Als Nächstes können die akzeptierten Werte verwendet werden, um die Routenvorlage zu erweitern. Die Routenvorlage wird verarbeitet:
Explizit bereitgestellte Werte, für die keine Übereinstimmungen mit einem Routensegment ermittelt werden, werden der Abfragezeichenfolge hinzugefügt. In der folgenden Tabelle werden die Ergebnisse dargestellt, die aus der Verwendung der Routenvorlage {controller}/{action}/{id?}
hervorgehen:
Umgebungswerte | Explizite Werte | Ergebnis |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Optionale Routenparameter kommen nach allen erforderlichen Routenparametern und Literalen. Im folgenden Code müssen die Parameter id
und name
nach dem Parameter color
kommen:
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers;
[Route("api/[controller]")]
public class MyController : ControllerBase
{
// GET /api/my/red/2/joe
// GET /api/my/red/2
// GET /api/my
[HttpGet("{color}/{id:int?}/{name?}")]
public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
{
return Ok($"{color} {id} {name ?? ""}");
}
}
Der folgende Code zeigt ein Beispiel für ein Schema zur URL-Generierung, das vom Routing nicht unterstützt wird:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
Im vorangehenden Code wird der culture
-Routenparameter für die Lokalisierung verwendet. Ziel ist es, dass der culture
-Parameter immer als Umgebungswert akzeptiert wird. Der culture
-Parameter wird jedoch aufgrund der Art und Weise, wie die erforderlichen Werte funktionieren, nicht als Umgebungswert akzeptiert:
"default"
-Routenvorlage befindet sich der culture
-Routenparameter links von controller
, sodass controller
durch Änderungen an culture
nicht ungültig wird."blog"
-Routenvorlage wird der culture
-Routenparameter rechts von controller
betrachtet, der in den erforderlichen Werten aufgeführt ist.Die LinkParser Klasse fügt die Möglichkeit hinzu, einen URL-Pfads in einen Satz von Routenwerten zu zerlegen. Die ParsePathByEndpointName Methode verwendet einen Endpunktnamen und einen URL-Pfad und gibt einen Satz von aus dem URL-Pfad extrahierten Routenwerten zurück.
Im folgenden Beispielcontroller verwendet die GetProduct
Aktion eine Routenvorlage von api/Products/{id}
und hat eine Name von GetProduct
:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
In derselben Controllerklasse erwartet die AddRelatedProduct
Aktion einen URL-Pfad, pathToRelatedProduct
, der als Abfragezeichenfolgen-Parameter bereitgestellt werden kann:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
Im vorherigen Beispiel extrahiert die AddRelatedProduct
Aktion den id
Routenwert aus dem URL-Pfad. Beispielsweise wird der /api/Products/1
Wert bei einem relatedProductId
URL-Pfad auf 1
festgelegt. Dieser Ansatz ermöglicht es den Clients der API, URL-Pfade beim Verweisen auf Ressourcen zu verwenden, ohne wissen zu müssen, wie eine solche URL strukturiert ist.
Die folgenden Links enthalten Informationen zum Konfigurieren von Endpunktmetadaten:
[MinimumAgeAuthorize]
-AttributRequireHost wendet eine Einschränkung auf die Route an, für die der angegebene Host erforderlich ist. Der Parameter RequireHost
oder [Host] kann wie folgt lauten:
www.domain.com
, entspricht www.domain.com
mit einem beliebigen Port.*.domain.com
, entspricht www.domain.com
, subdomain.domain.com
oder www.subdomain.domain.com
an einem beliebigen Port.*:5000
, entspricht Port 5000 mit einem beliebigen Host.www.domain.com:5000
oder *.domain.com:5000
, entspricht dem Host und Port.Es können mehrere Parameter mit RequireHost
oder [Host]
angegeben werden. Die Einschränkung gleicht die Hosts ab, die für einen der Parameter gültig sind. Beispielsweise entspricht [Host("domain.com", "*.domain.com")]
domain.com
, www.domain.com
und subdomain.domain.com
.
Im folgenden Code wird RequireHost
verwendet, um den angegebenen Host auf der Route anzufordern:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
Im folgenden Code wird das [Host]
-Attribut für den Controller verwendet, um die einzelnen angegebenen Hosts anzufordern:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Wenn das [Host]
-Attribut sowohl auf die Controller- als auch auf die Aktionsmethode angewendet wird, trifft Folgendes zu:
Warnung
Die APIs, die auf dem Hostheader basieren, z. B. HttpRequest.Host und RequireHost, sind potenziellem Spoofing durch Clients ausgesetzt.
Verwenden Sie einen der folgenden Ansätze, um Host- und Portspoofing zu verhindern:
Die MapGroup-Erweiterungsmethode hilft, Gruppen von Endpunkten mit einem gemeinsamen Präfix zu organisieren. Sie reduziert sich wiederholenden Code und ermöglicht die benutzerdefinierte Anpassung ganzer Gruppen von Endpunkten mit einem einzigen Aufruf von Methoden wie RequireAuthorization und WithMetadata,die Endpunktmetadaten hinzufügen.
Der folgende Code erstellt beispielsweise zwei ähnliche Endpunktgruppen:
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
In diesem Szenario können Sie eine relative Adresse für den Location
-Header im 201 Created
-Ergebnis verwenden:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Die erste Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /public/todos
und ist ohne Authentifizierung zugänglich. Die zweite Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /private/todos
und erfordert Authentifizierung.
Die QueryPrivateTodos
-Endpunktfilterfactory ist eine lokale Funktion, die die TodoDb
-Parameter des Routenhandlers so ändert, dass Zugriff auf private Aufgabendaten zulässig ist und diese gespeichert werden können.
Routengruppen unterstützen auch geschachtelte Gruppen und komplexe Präfixmuster mit Routenparametern und -einschränkungen. Im folgenden Beispiel kann der der user
-Gruppe zugeordnete Routenhandler die Routenparameter {org}
und {group}
erfassen, die in den Präfixen der äußeren Gruppe definiert sind.
Das Präfix kann auch leer sein. Dies kann hilfreich sein, um Endpunktmetadaten oder Filter einer Gruppe von Endpunkten hinzuzufügen, ohne das Routenmuster zu ändern.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Das Hinzufügen von Filtern oder Metadaten zu einer Gruppe verhält sich genauso wie das individuelle Hinzufügen zu jedem Endpunkt, bevor zusätzliche Filter oder Metadaten hinzugefügt werden, die einer inneren Gruppe oder einem bestimmten Endpunkt hinzugefügt wurden.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
Im Beispiel oben protokolliert der äußere Filter die eingehende Anforderung vor dem inneren Filter, obwohl er als zweiter hinzugefügt wurde. Da die Filter auf verschiedene Gruppen angewendet wurden, spielt die Reihenfolge, in der sie relativ zueinander hinzugefügt wurden, keine Rolle. Die Reihenfolge, in der Filter hinzugefügt werden, spielt eine Rolle, wenn sie auf dieselbe Gruppe oder einen bestimmten Endpunkt angewendet werden.
Eine Anforderung an /outer/inner/
protokolliert Folgendes:
/outer group filter
/inner group filter
MapGet filter
Wenn eine App Leistungsprobleme hat, wird die Ursache häufig beim Routing vermutet. Das Routing wird deshalb in Betracht gezogen, weil Frameworks wie Controller und Razor Pages in ihren Protokollierungsmeldungen die innerhalb des Frameworks verbrachte Zeit angeben. Wenn es einen signifikanten Unterschied zwischen der von den Controllern gemeldeten Zeit und der Gesamtzeit der Anforderung gibt:
Die Leistung des Routings wird anhand von Tausenden von Endpunkten getestet. Es ist unwahrscheinlich, dass eine typische App auf ein Leistungsproblem stößt, nur weil diese zu umfangreich ist. Die häufigste Ursache für eine langsames Routing ist üblicherweise eine schlecht funktionierende benutzerdefinierte Middleware.
Das folgende Codebeispiel veranschaulicht eine grundlegende Technik zur Eingrenzung der Verzögerungsquelle:
var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.MapGet("/", () => "Timing Test.");
Auf das Zeitrouting:
Dies ist ein einfacher Weg, um die Verzögerung zu verringern, wenn sie signifikant ist, zum Beispiel größer als 10ms
. Wenn Time 2
von Time 1
subtrahiert wird, ergibt sich die in der UseRouting
-Middleware benötigte Zeit.
Der folgende Code verwendet einen kompakteren Ansatz als der vorangegangene Zeitcode:
public sealed class AutoStopwatch : IDisposable
{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());
public void Dispose()
{
if (_disposed)
{
return;
}
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");
Die folgende Liste gibt einen Einblick in Routingfeatures, die im Vergleich zu einfachen Routenvorlagen relativ teuer sind:
{x}-{y}-{z}
): ASP.NET Core verwendet standardmäßig einen Routingalgorithmus, bei dem Arbeitsspeicher zugunsten von CPU-Zeit geopfert wird. Dies hat den angenehmen Effekt, dass die Zeit für den Routenabgleich nur von der Länge des abzugleichenden Pfads und nicht von der Routenanzahl abhängt. Dieser Ansatz kann jedoch in einigen Fällen problematisch sein, etwa dann, wenn die App eine große Anzahl von Routen umfasst (mehrere Tausend) und die Routen eine große Anzahl variabler Präfixe enthalten. Beispiel: Die Routen weisen Parameter in frühen Segmenten der Route auf, etwa {parameter}/some/literal
.
Es ist unwahrscheinlich, dass eine App in eine Situation gerät, in der dies ein Problem darstellt, es sei denn:
Microsoft.AspNetCore.Routing.Matching.DfaNode
-Instanzen.Es gibt verschiedene Techniken und Optimierungen, die auf Routen angewendet werden können, um dieses Szenario weitgehend zu vermeiden:
{parameter:int}
, {parameter:guid}
, {parameter:regex(\\d+)}
.
MapDynamicControllerRoute
und MapDynamicPageRoute
erreicht werden.Wenn das Routing mit einem Endpunkt übereinstimmt, lässt es in der Regel den Rest der Middlewarepipeline ausführen, bevor die Endpunktlogik aufgerufen wird. Dienste können die Ressourcennutzung reduzieren, indem sie bekannte Anforderungen frühzeitig in der Pipeline herausfiltern. Verwenden Sie die ShortCircuit-Erweiterungsmethode, um das Routing zu veranlassen, die Endpunktlogik sofort aufzurufen und dann die Anforderung zu beenden. Beispielsweise muss eine bestimmte Route möglicherweise keine Authentifizierung oder CORS-Middleware durchlaufen. Im folgenden Beispiel werden Kurzschlüsse angefordert, die mit der /short-circuit
-Route übereinstimmen:
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
Die ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>)-Methode kann optional einen Statuscode verwenden.
Verwenden Sie die MapShortCircuit-Methode, um Kurzschlüsse für mehrere Routen gleichzeitig einzurichten, indem Sie ihr ein Parameterarray von URL-Präfixen übergeben. Beispielsweise testen Browser und Bots häufig Server auf bekannte Pfade wie robots.txt
und favicon.ico
. Wenn die App nicht über diese Dateien verfügt, kann eine Codezeile beide Routen konfigurieren:
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
MapShortCircuit
gibt IEndpointConventionBuilder zurück, sodass zusätzliche Routeneinschränkungen wie die Hostfilterung hinzugefügt werden können.
Die Methoden ShortCircuit
und MapShortCircuit
wirken sich nicht auf Middleware aus, die vor UseRouting
platziert wird. Wenn Sie versuchen, diese Methoden mit Endpunkten zu verwenden, die ebenfalls über [Authorize]
- oder [RequireCors]
-Metadaten verfügen, führen Anforderungen mit einer InvalidOperationException
zu einem Fehler. Diese Metadaten werden durch [Authorize]
- oder [EnableCors]
-Attribute oder RequireCors- oder RequireAuthorization-Methoden angewendet.
Um die Auswirkung des Middleware-Kurzschlusses anzuzeigen, legen Sie die Protokollierungskategorie „Microsoft“ in appsettings.Development.json
auf „Information“ fest:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Führen Sie den folgenden Code aus:
var app = WebApplication.Create();
app.UseHttpLogging();
app.MapGet("/", () => "No short-circuiting!");
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
app.Run();
Das folgende Beispiel stammt aus den Konsolenprotokollen, die beim Ausführen des /
-Endpunkts erstellt werden. Es enthält die Ausgabe der Middleware für die Protokollierung:
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8
Date: Wed, 03 May 2023 21:05:59 GMT
Server: Kestrel
Alt-Svc: h3=":5182"; ma=86400
Transfer-Encoding: chunked
Das folgende Beispiel wird vom /short-circuit
-Endpunkt aus ausgeführt. Es enthält nichts von der Middleware für die Protokollierung, da die Middleware kurzgeschlossen wurde:
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[4]
The endpoint 'HTTP: GET /short-circuit' is being executed without running additional middleware.
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[5]
The endpoint 'HTTP: GET /short-circuit' has been executed without running additional middleware.
Dieser Abschnitt enthält Hinweise für Bibliotheksautoren, die auf dem Routing aufbauen. Diese Details sollen sicherstellen, dass App-Entwickler gute Erfahrungen mit Bibliotheken und Frameworks machen, die das Routing erweitern.
Wenn Sie ein Framework erstellen möchten, das das Routing für den URL-Abgleich verwendet, sollten Sie zunächst eine Benutzeroberfläche definieren, die auf UseEndpoints aufbaut.
Es wird empfohlen, dass Sie auf IEndpointRouteBuilder aufbauen. Auf diese Weise können Benutzer Ihr Framework mit anderen ASP.NET Core-Features problemlos zusammenstellen. Jede ASP.NET Core-Vorlage umfasst die Routingfunktionalität. Gehen Sie davon aus, dass das Routing vorhanden und den Benutzern vertraut ist.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
Es wird empfohlen, dass Sie einen versiegelten konkreten Typ aus einem Aufruf an MapMyFramework(...)
zurückgeben, der IEndpointConventionBuilder implementiert. Die meisten Map...
-Methoden in Frameworks folgen diesem Muster. Die IEndpointConventionBuilder
-Schnittstelle:
Wenn Sie Ihren eigenen Typ deklarieren, können Sie dem Generator Ihre eigene frameworkspezifische Funktionalität hinzufügen. Es ist in Ordnung, einen vom Framework deklarierten Generator zu umschließen und Aufrufe an ihn weiterzuleiten.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
Ziehen Sie in Betracht, Ihre eigene EndpointDataSource-Klasse zu schreiben. Die EndpointDataSource
-Klasse vom primitiven Typ auf niedriger Ebene eignet sich zum Deklarieren und Aktualisieren einer Sammlung von Endpunkten. EndpointDataSource
ist eine leistungsstarke API, die von Controllern und Razor Pages verwendet wird. Weitere Informationen finden Sie unter Dynamisches Endpunktrouting.
Die Routingtests haben ein grundlegendes Beispiel für eine Datenquelle, die nicht aktualisiert wird.
ERWÄGEN Sie die Implementierung von GetGroupedEndpoints. Dadurch erhalten Sie vollständige Kontrolle über ausgeführte Gruppenkonventionen und die endgültigen Metadaten für die gruppierten Endpunkte. Dies ermöglicht z. B. benutzerdefinierten EndpointDataSource
Implementierungen das Ausführen von Endpunktfiltern, die Gruppen hinzugefügt werden.
Versuchen Sie nicht, eine EndpointDataSource
-Klasse standardmäßig zu registrieren. Fordern Sie die Benutzer auf, Ihr Framework in UseEndpoints zu registrieren. Der Grundgedanke beim Routing ist, dass standardmäßig nichts enthalten ist, und dass die Endpunkte bei UseEndpoints
registriert werden müssen.
Ziehen Sie in Betracht, Metadatentypen als Schnittstelle zu definieren.
Ermöglichen Sie, Metadatentypen als Attribut für Klassen und Methoden zu verwenden.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks wie Controller und Razor Pages unterstützen die Anwendung von Metadatenattributen auf Typen und Methoden. Für das Deklarieren von Metadatentypen gilt:
Durch die Deklaration eines Metadatentyps als Schnittstelle wird die Flexibilität zusätzlich erhöht:
Ermöglichen Sie, Metadaten außer Kraft zu setzen, wie in folgendem Beispiel gezeigt:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
Die beste Möglichkeit, diese Richtlinien zu befolgen, besteht darin, die Definition von Markermetadaten zu vermeiden:
Die Metadatensammlung ist geordnet und unterstützt das Außerkraftsetzen nach Priorität. Im Fall von Controllern sind Metadaten für die Aktionsmethode am spezifischsten.
Nutzen Sie Middleware mit und ohne Routing.
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Ein gutes Beispiel für diese Vorgabe ist die UseAuthorization
-Middleware. Mithilfe der Autorisierungsmiddleware können Sie eine Fallbackrichtlinie einbauen. Die Fallbackrichtlinie gilt, falls angegeben, für beide:
Dies macht die Autorisierungsmiddleware außerhalb des Routingkontexts sehr nützlich. Die Autorisierungsmiddleware kann für die traditionelle Middlewareprogrammierung verwendet werden.
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"
}
}
}
Das Routing wird für das Abgleichen von HTTP-Anforderungen und das Verteilen an ausführbare Endpunkte der App eingesetzt. Endpunkte sind die Einheiten des ausführbaren Codes für die Anforderungsverarbeitung in der App. Endpunkte werden in der App definiert und beim Start der App konfiguriert. Beim Endpunktabgleich können Werte aus der Anforderungs-URL extrahiert und für die Verarbeitung der Anforderung bereitgestellt werden. Mithilfe von Endpunktinformationen aus der App lassen sich durch das Routing URLs generieren, die Endpunkten zugeordnet werden.
Apps können das Routing mit folgenden Funktionen konfigurieren:
In diesem Artikel werden die grundlegenden Details zum ASP.NET Core-Routing beschrieben. Informationen zur Routingkonfiguration finden Sie wie folgt:
Der folgende Code veranschaulicht ein einfaches Beispiel für das Routing:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Das vorherige Beispiel enthält einen einzelnen Endpunkt unter Verwendung der MapGet-Methode:
GET
-HTTP-Anforderung an die Stamm-URL /
gesendet wird: Hello World!
wird in die HTTP-Antwort geschrieben.GET
bzw. die Stamm-URL nicht /
ist, gibt es keinen Routenabgleich und es wird ein HTTP-404-Fehler zurückgegeben.Beim Routing wird ein Middlewarepaar verwendet, das durch UseRouting und UseEndpoints registriert wird:
UseRouting
fügt der Middlewarepipeline einen Routenabgleich hinzu. Diese Middleware prüft die in der App definierten Endpunkte und wählt anhand der Anforderung die beste Übereinstimmung aus.UseEndpoints
fügt der Middlewarepipeline die Endpunktausführung hinzu. Dabei wird der mit dem ausgewählten Endpunkt verknüpfte Delegat ausgeführt.Apps müssen UseRouting
oder UseEndpoints
normalerweise nicht aufrufen. WebApplicationBuilder konfiguriert eine Middlewarepipeline, die die in Program.cs
hinzugefügte Middleware mit UseRouting
und UseEndpoints
umschließt. Apps können jedoch die Reihenfolge ändern, in der UseRouting
und UseEndpoints
ausgeführt werden, indem sie diese Methoden explizit aufrufen. Der folgende Code ruft beispielsweise UseRouting
explizit auf:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
Für den Code oben gilt:
app.Use
wird eine benutzerdefinierte Middleware registriert, die am Anfang der Pipeline ausgeführt wird.UseRouting
konfiguriert die Middleware für den Routenabgleich zur Ausführung nach der benutzerdefinierten Middleware.MapGet
registrierte Endpunkt wird am Ende der Pipeline ausgeführt.Wenn das vorhergehende Beispiel keinen Aufruf an UseRouting
enthält, wird die benutzerdefinierte Middleware nach der Middleware zum Routenabgleich ausgeführt.
Die MapGet
-Methode wird verwendet, um einen Endpunkt zu definieren. Ein Endpunkt kann Folgendes sein:
Endpunkte, die von der App zugeordnet und ausgeführt werden können, sind in UseEndpoints
konfiguriert. Mit MapGet, MapPost und ähnlichen Methoden werden beispielsweise Anforderungsdelegate mit dem Routingsystem verbunden. Zudem können weitere Methoden zur Verbindung von ASP.NET Core-Frameworkfunktionen mit dem Routingsystem verwendet werden:
Das folgende Beispiel zeigt das Routing mit einer anspruchsvolleren Routenvorlage:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
Die Zeichenfolge /hello/{name:alpha}
ist eine Routenvorlage. Eine Routenvorlage, um zu konfigurieren, wie der Endpunkt abgeglichen wird. In diesem Fall gleicht die Vorlage Folgendes ab:
/hello/Docs
/hello/
beginnen, gefolgt von einer Sequenz alphabetischer Zeichen. :alpha
wendet eine Routeneinschränkung an, die nur alphabetische Zeichen abgleicht. Routeneinschränkungen werden weiter unten in diesem Artikel erläutert.Das zweite Segment des URL-Pfads, {name:alpha}
:
name
gebunden.Das folgende Beispiel zeigt das Routing mit Integritätsprüfungen und Autorisierung:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
Im vorherigen Beispiel wird veranschaulicht, wie Sie:
Der MapHealthChecks-Aufruf fügt einen Endpunkt für eine Integritätsprüfung hinzu. Durch die Verkettung von RequireAuthorization mit diesem Aufruf wird eine Autorisierungsrichtlinie an den Endpunkt angefügt.
Der Aufruf von UseAuthentication und UseAuthorization wird die Authentifizierungs- und Autorisierungsmiddleware hinzugefügt. Diese Middleware wird zum Ausführen folgender Aktionen zwischen UseRouting und UseEndpoints
platziert:
UseRouting
ausgewählten Endpunkts.Im vorangehenden Beispiel gibt es zwei Endpunkte, aber nur dem für die Integritätsprüfung ist eine Autorisierungsrichtlinie angefügt. Wenn die Anforderung mit dem Endpunkt der Integritätsprüfung, /healthz
, übereinstimmt, wird eine Autorisierungsprüfung durchgeführt. Dadurch wird veranschaulicht, dass Endpunkten zusätzliche Daten zugeordnet werden können. Diese zusätzlichen Daten werden als Metadaten des Endpunkts bezeichnet:
Durch Hinzufügen des effizienten Endpunkt-Konzepts stellt das Routingsystem eine Ergänzung der Middlewarepipeline dar. Endpunkte stehen für Funktionseinheiten der App, die sich in Bezug auf Routing, Autorisierung und die Anzahl der ASP.NET Core-Systeme voneinander unterscheiden.
Ein ASP.NET Core-Endpunkt ist:
Der folgende Code zeigt, wie der Endpunkt, der mit der aktuellen Anforderung übereinstimmt, abgerufen und geprüft werden kann:
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}
await next(context);
});
app.MapGet("/", () => "Inspect Endpoint.");
Der Endpunkt, falls ausgewählt, kann aus dem HttpContext
-Element abgerufen werden. Seine Eigenschaften können geprüft werden. Endpunktobjekte sind unveränderlich und können nach der Erstellung nicht mehr geändert werden. Der häufigste Typ des Endpunkts ist eine RouteEndpoint-Klasse. RouteEndpoint
enthält Informationen, die eine Auswahl durch das Routingsystem ermöglichen.
Im vorangehenden Code wird mit app.Use eine Middleware inline konfiguriert.
Der folgende Code zeigt, dass es, je nachdem, wo app.Use
in der Pipeline aufgerufen wird, möglicherweise keinen Endpunkt gibt:
// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");
app.UseEndpoints(_ => { });
// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
Im vorangehenden Beispiel werden Console.WriteLine
-Anweisungen hinzugefügt, die anzeigen, ob ein Endpunkt ausgewählt wurde oder nicht. Aus Gründen der Übersichtlichkeit wird in dem Beispiel dem bereitgestellten /
-Endpunkt ein Anzeigename zugewiesen.
Das vorherige Beispiel enthält auch Aufrufe an UseRouting
und UseEndpoints
, um genau zu steuern, wann diese Middleware in der Pipeline ausgeführt wird.
Wenn Sie diesen Code mit einer URL /
ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Wenn Sie diesen Code mit einer anderen URL ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Diese Ausgabe zeigt Folgendes:
UseRouting
aufgerufen wird.UseRouting
und UseEndpoints ungleich NULL.UseEndpoints
-Middleware ist eine Terminalmiddleware, wenn eine Übereinstimmung gefunden wird. Terminalmiddleware wird weiter unten in diesem Artikel definiert.UseEndpoints
wird nur ausgeführt, wenn keine Übereinstimmung gefunden wird.Die UseRouting
-Middleware verwendet die SetEndpoint-Methode, um den Endpunkt an den aktuellen Kontext anzufügen. Es ist möglich, die UseRouting
-Middleware durch benutzerdefinierte Logik zu ersetzen und dennoch die Vorteile durch die Verwendung von Endpunkten zu nutzen. Endpunkte befinden sich auf niedriger Ebene, wie Middleware, und sind nicht an die Routingimplementierung gekoppelt. Die meisten Apps müssen UseRouting
nicht durch eigene Logik ersetzen.
Die UseEndpoints
-Middleware ist so konzipiert, dass Sie zusammen mit der UseRouting
-Middleware verwendet werden kann. Die Hauptlogik zum Ausführen eines Endpunkts ist nicht kompliziert. Mit GetEndpoint können Sie einen Endpunkt abrufen und dann dessen RequestDelegate-Eigenschaft aufrufen.
Der folgende Code veranschaulicht, wie Middleware das Routing beeinflussen oder darauf reagieren kann:
app.UseHttpMethodOverride();
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
await next(context);
});
app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }
Im vorherigen Beispiel werden zwei wichtige Konzepte dargestellt:
UseRouting
ausgeführt werden, um die Daten zu ändern, auf denen das Routing basiert.
UseRouting
und UseEndpoints ausgeführt werden, um die Ergebnisse des Routings vor der Ausführung des Endpunkts zu verarbeiten.
UseRouting
und UseEndpoints
ausgeführt wird: UseAuthorization
und UseCors
.Der vorangehende Code zeigt ein Beispiel für eine benutzerdefinierte Middleware, die entpunktbezogene Richtlinien unterstützt. Die Middleware schreibt ein Überwachungsprotokoll für den Zugriff auf vertrauliche Daten in der Konsole. Die Middleware kann so konfiguriert werden, dass ein Endpunkt mit den -Metadaten RequiresAuditAttribute
wird. In diesem Beispiel wird ein Opt-In-Muster veranschaulicht, bei dem nur Endpunkte überwacht werden, die als vertraulich markiert sind. Es ist möglich, diese Logik umgekehrt zu definieren, indem beispielsweise alles geprüft wird, was nicht als sicher markiert ist. Das Endpunktmetadaten-System ist flexibel. Diese Logik lässt sich für jeden Anwendungsfall passend schreiben.
Der vorherige Beispielcode soll die grundlegenden Konzepte von Endpunkten veranschaulichen. Das Beispiel ist nicht für Produktionsumgebungen vorgesehen. Eine vollständigere Version einer Middleware für Überwachungsprotokolle würde Folgendes bieten:
Die Metadaten der Überwachungsrichtlinie RequiresAuditAttribute
sind als Attribute
definiert, um die Verwendung mit klassenbasierten Frameworks wie Controllern und SignalR zu erleichtern. Bei Verwendung von Route-zu-Code:
Die bewährten Methoden für Metadatentypen sind, sie entweder als Schnittstellen oder als Attribute zu definieren. Schnittstellen und Attribute ermöglichen die Wiederverwendung von Code. Das Metadatensystem ist flexibel und weist keine Einschränkungen auf.
Im folgenden Beispiel werden sowohl Terminalmiddleware als auch Routing veranschaulicht:
// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
app.UseRouting();
// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");
Beim in Approach 1:
gezeigten Stil von Middleware handelt es sich um Terminalmiddleware. Sie wird als Terminalmiddleware bezeichnet, da sie einen Abgleich durchgeführt.
Path == "/"
für die Middleware und Path == "/Routing"
für das Routing.next
-Middleware aufzurufen.Sie wird „Terminal Middleware“ genannt, da sie die Suche beendet, einige Funktionen ausführt und dann zurückkehrt.
In der folgenden Liste werden Terminalmiddleware und Routing verglichen:
next
aufzurufen.UseAuthorization
und UseCors
.
UseAuthorization
oder UseCors
erfordert eine manuelle Verknüpfung mit dem Autorisierungssystem.Ein Endpunkt definiert beides:
Terminalmiddleware kann sehr nützlich sein, erfordert aber möglicherweise auch:
Ziehen Sie daher zunächst die Integration von Routingfunktionen in Betracht, bevor Sie damit beginnen, Terminalmiddleware zu schreiben.
Vorhandene Terminalmiddleware, die in Map oder MapWhen integriert ist, kann in der Regel in einen routingfähigen Endpunkt umgewandelt werden. MapHealthChecks zeigt das Muster für eine Routinglösung:
Map
auf, und stellen Sie die neue Middlewarepipeline bereit.Map
aus der Erweiterungsmethode bereitgestellt wurde.Im folgenden Code ist die Verwendung von MapHealthChecks gezeigt:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
Das vorangehende Beispiel zeigt, warum das Zurückgeben des Generatorobjekts wichtig ist. Wenn das Generatorobjekt zurückgegeben wird, kann der App-Entwickler Richtlinien konfigurieren, z. B. die Autorisierung für den Endpunkt. In diesem Beispiel ist die Middleware für Integritätsprüfungen nicht direkt in das Autorisierungssystem integriert.
Das Metadatensystem wurde als Antwort auf die Probleme erstellt, die von Erweiterbarkeitsautoren mithilfe von Terminalmiddleware aufgetreten sind. Für jede Middleware ist es problematisch, deren eigene Integration in das Autorisierungssystem umzusetzen.
Wenn eine Routingmiddleware ausgeführt wird, legt sie ein Endpoint
-Element fest und leitet Werte an eine Anforderungsfunktion in der HttpContext-Klasse der aktuellen Anforderung weiter:
HttpRequest.RouteValues
ruft die Sammlung der Routenwerte ab.Middleware wird nach der Routingmiddleware ausgeführt und kann den Endpunkt erkennen und Maßnahmen ergreifen. So kann beispielsweise eine Autorisierungsmiddleware die Erfassung der Metadaten des Endpunkts für eine Autorisierungsrichtlinie abfragen. Nachdem die gesamte Middleware in der Anforderungsverarbeitungspipeline ausgeführt wurde, wird der Delegat des ausgewählten Endpunkts aufgerufen.
Das Routingsystem ist beim Endpunktrouting für alle Weiterleitungsentscheidungen zuständig. Da die Middleware Richtlinien auf der Grundlage des ausgewählten Endpunkts anwendet, ist Folgendes wichtig:
Warnung
Wenn für Abwärtskompatibilität ein Controller- oder Razor Pages-Endpunktdelegat ausgeführt wird, werden die Eigenschaften von RouteContext.RouteData auf Grundlage der bisher verarbeiteten Anforderungen auf entsprechende Werte festgelegt.
Der Typ RouteContext
wird in einer zukünftigen Version als veraltet markiert:
RouteData.Values
zu HttpRequest.RouteValues
.RouteData.DataTokens
, um IDataTokensMetadata aus den Endpunktmetadaten abzurufen.Der URL-Abgleich erfolgt in mehreren Phasen und kann konfiguriert werden. In jeder Phase werden mehrere Übereinstimmungen ausgegeben. Diese Übereinstimmungen lassen sich in der nächsten Phase weiter eingrenzen. Die Routingimplementierung garantiert keine Verarbeitungsreihenfolge für übereinstimmende Endpunkte. Alle möglichen Übereinstimmungen werden gleichzeitig verarbeitet. Für die URL-Abgleichsphasen gilt folgende Reihenfolge. ASP.NET Core:
Die Liste der Endpunkte wird entsprechend den folgenden Punkten priorisiert:
Alle übereinstimmenden Endpunkte werden in jeder Phase verarbeitet, bis EndpointSelector erreicht ist. EndpointSelector
stellt die abschließende Phase dar. Darin wird der Endpunkt mit der höchsten Priorität aus den Übereinstimmungen als beste Übereinstimmung ausgewählt. Wenn es andere Übereinstimmungen mit derselben Priorität wie die beste Übereinstimmung gibt, wird ein Ausnahmefehler wegen einer nicht eindeutigen Übereinstimmung ausgelöst.
Die Routenpriorität wird anhand einer spezifischeren Routenvorlage berechnet, der eine höhere Priorität eingeräumt wird. Dies wird z. B. anhand der Vorlagen /hello
und /{message}
deutlich:
/hello
überein./hello
ist spezifischer und hat daher höhere Priorität.Im Allgemeinen eignet sich die Routenpriorität gut, um die beste Übereinstimmung für die in der Praxis verwendeten URL-Schemata zu finden. Verwenden Sie Order nur bei Bedarf, um eine Mehrdeutigkeit zu vermeiden.
Aufgrund der Erweiterungsmöglichkeiten, die das Routing bietet, kann das Routingsystem die mehrdeutigen Routen nicht im Voraus berechnen. Betrachten Sie ein Beispiel wie die Routenvorlagen /{message:alpha}
und /{message:int}
:
alpha
-Einschränkung gleicht nur alphabetische Zeichen ab.int
-Einschränkung gleicht nur Zahlen ab.Warnung
Die Reihenfolge der Vorgänge in UseEndpoints wirkt sich nicht auf das Routingverhalten aus, mit einer Ausnahme. MapControllerRoute und MapAreaRoute weisen ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Dadurch wird das Langzeitverhalten von Controllern simuliert, ohne dass das Routingsystem die gleichen Garantien bietet wie ältere Routingimplementierungen.
Endpunktrouting in ASP.NET Core:
Die Routenvorlagenpriorität ist ein System, bei dem jeder Routenvorlage ein Wert zugewiesen wird, je nachdem, wie spezifisch diese ist. Routenvorlagenpriorität:
Dies wird z. B. anhand der Vorlagen /Products/List
und /Products/{id}
deutlich. Es wäre begründet, anzunehmen, dass /Products/List
eine bessere Übereinstimmung als /Products/{id}
für den URL-Pfad /Products/List
ist. Dies funktioniert, weil das Literalsegment /List
eine höhere Priorität als das Parametersegment /{id}
hat.
Wie die Priorisierung im Einzelnen funktioniert, ist an die Definition der Routenvorlagen gekoppelt:
URL-Generierung:
Das Endpunktrouting umfasst die API zur Linkgenerierung (LinkGenerator). LinkGenerator
ist ein Singleton-Dienst, der in DI verfügbar ist. Die LinkGenerator
-API kann außerhalb des Kontexts einer ausgeführten Anforderung verwendet werden. Mvc.IUrlHelper und Szenarios, die von IUrlHelper abhängig sind (z. B. Taghilfsprogramme, HTML-Hilfsprogramme und Aktionsergebnisse), verwenden die LinkGenerator
-API, um entsprechende Funktionen bereitzustellen.
Die API zur Linkgenerierung wird von Konzepten wie Adressen und Adressschemas unterstützt. Sie können mithilfe eines Adressschemas die Endpunkte bestimmen, die bei der Linkgenerierung berücksichtigt werden sollen. Beispielsweise werden Routennamen und Routenwerte als Adressschemas implementiert. Diese Szenarios kennen viele Benutzer von Controllern und Razor Pages.
Die API zur Linkgenerierung kann Controller und Razor Pages über die folgenden Erweiterungsmethoden miteinander verknüpfen:
Beim Überladen dieser Methoden werden Argumente akzeptiert, die den HttpContext
umfassen. Diese Methoden sind zwar in funktionaler Hinsicht äquivalent zu Url.Action und Url.Page, bieten aber zusätzliche Flexibilität und Optionen.
Die GetPath*
-Methoden sind Url.Action
und Url.Page
in der Hinsicht ähnlich, dass sie einen URI mit einem absoluten Pfad generieren. Die GetUri*
-Methoden generieren immer einen absoluten URI mit einem Schema und einem Host. Die Methoden, die einen HttpContext
akzeptieren, generieren im Kontext der ausgeführten Anforderung einen URI. Die Umgebungsroutenwerte, der URL-basierte Pfad, das Schema und der Host von der ausführenden Anforderung werden so lange verwendet, bis sie außer Kraft gesetzt werden.
LinkGenerator wird mit einer Adresse aufgerufen. Ein URI wird in zwei Schritten generiert:
Die von LinkGenerator bereitgestellten Methoden unterstützen die Standardfunktionen zur Generierung von Links für jeden beliebigen Adresstypen. Am praktischsten ist es, die API zur Linkgenerierung mit Erweiterungsmethoden zu verwenden, die Vorgänge für einen bestimmten Adresstypen ausführen:
Erweiterungsmethode | Beschreibung |
---|---|
GetPathByAddress | Generiert einen URI mit einem absoluten Pfad, der auf den angegebenen Werten basiert. |
GetUriByAddress | Generiert einen absoluten URI, der auf den angegebenen Werten basiert. |
Warnung
Beachten Sie die folgenden Implikationen zum Aufrufen von LinkGenerator-Methoden:
Verwenden Sie GetUri*
-Erweiterungsmethoden in App-Konfigurationen, die den Host
-Header von eingehenden Anforderungen nicht überprüfen, mit Bedacht. Wenn der Host
-Header von eingehenden Anforderungen nicht überprüft wird, können nicht vertrauenswürdige Anforderungseingaben zurück an den Client in URIs einer Ansicht bzw. Seite zurückgesendet werden. Es wird empfohlen, dass alle Produktions-Apps ihren Server so konfigurieren, dass der Host
-Header auf bekannte gültige Werte überprüft wird.
Verwenden Sie LinkGenerator in Kombination mit Map
oder MapWhen
in Middleware mit Bedacht. Map*
ändert den Basispfad der ausgeführten Anforderung. Dies beeinflusst die Ausgabe der Linkgenerierung. Für alle LinkGenerator-APIs ist die Angabe eines Basispfads zulässig. Geben Sie einen leeren Basispfad an, um die Auswirkungen von Map*
auf die Linkgenerierung rückgängig zu machen.
Im folgenden Beispiel verwendet eine Middleware die LinkGenerator-API, um eine Verknüpfung zu einer Aktionsmethode herzustellen, die Speicherprodukte aufführt. Sie können für jede beliebige Klasse in einer App die API zur Linkgenerierung verwenden, indem Sie diese in eine Klasse einfügen und GenerateLink
aufrufen:
public class ProductsMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public async Task InvokeAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
var productsPath = _linkGenerator.GetPathByAction("Products", "Store");
await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}
Token in {}
definieren Routenparameter, die beim Abgleich der Route gebunden werden. In einem Routensegment können mehrere Routenparameter definiert werden, müssen aber durch einen Literalwert getrennt werden. Beispiel:
{controller=Home}{action=Index}
ist keine gültige Route, da sich zwischen {controller}
und {action}
kein Literalwert befindet. Routenparameter müssen einen Namen besitzen und können zusätzliche Attribute aufweisen.
Eine Literalzeichenfolge, die nicht den Routenparametern entspricht (z.B. {id}
), muss zusammen mit dem Pfadtrennzeichen /
mit dem URL-Text übereinstimmen. Beim Abgleich von Text wird nicht zwischen Groß-/Kleinbuchstaben unterschieden, und die Übereinstimmung basiert auf der decodierten Repräsentation des URL-Pfads. Damit das Trennzeichen ({
oder }
) der Routenparameter-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein, was einem Escapezeichen entspricht. Beispielsweise {{
oder }}
.
Sternchen *
oder Doppelsternchen **
:
blog/{**slug}
: blog/
beginnt und einem beliebigen Wert folgt.blog/
wird dem Slug-Routenwert zugewiesen.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:
{**slug}"
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.
Durch Catch-All-Parameter können auch leere Zeichenfolgen gefunden werden.
Der Catch-All-Parameter schützt die entsprechenden Zeichen (Escaping), wenn die Route verwendet wird, um eine URL, einschließlich Pfadtrennzeichen (/
) zu generieren. Z.B. generiert die Route foo/{*path}
mit den Routenwerten { path = "my/path" }
foo/my%2Fpath
. Beachten Sie den umgekehrten Schrägstrich mit Escapezeichen. Um Trennzeichen für Roundtrips einsetzen zu können, verwenden Sie das Routenparameterpräfix **
. Die Route foo/{**path}
mit { path = "my/path" }
generiert foo/my/path
.
Bei einem URL-Muster, durch das ein Dateiname mit einer optionalen Erweiterung erfasst werden soll, sind noch weitere Aspekte zu berücksichtigen. Dies wird z.B. anhand der Vorlage files/{filename}.{ext?}
deutlich. Wenn sowohl für filename
als auch für ext
Werte vorhanden sind, werden beide Werte angegeben. Wenn nur für filename
ein Wert in der URL vorhanden ist, wird für die Route eine Übereinstimmung ermittelt, da der nachstehende Punkt (.
) optional ist. Für die folgenden URLs wird eine Übereinstimmung für die Route ermittelt:
/files/myFile.txt
/files/myFile
Routenparameter können über mehrere Standardwerte verfügen, die nach dem Parameternamen angegeben werden und durch ein Gleichheitszeichen (=
) voneinander getrennt werden. Mit {controller=Home}
wird beispielsweise Home
als Standardwert für controller
definiert. Der Standardwert wird verwendet, wenn kein Wert in der Parameter-URL vorhanden ist. Routenparameter sind optional, wenn am Ende des Parameternamens ein Fragezeichen (?
) angefügt wird. Beispielsweise id?
. Zwischen optionalen Werten und Standardroutenparametern besteht folgender Unterschied:
Routenparameter können Einschränkungen aufweisen, die mit dem gebundenen Routenwert der URL übereinstimmen müssen. Eine Inline-Einschränkung für einen Routenparameter geben Sie an, indem Sie hinter dem Namen des Routenparameters einen Doppelpunkt (:
) und einen Einschränkungsnamen hinzufügen. Wenn für die Einschränkung Argumente erforderlich sind, werden diese nach dem Einschränkungsnamen in Klammern ((...)
) eingeschlossen. Mehrere Inline-Einschränkungen können festgelegt werden, indem ein weiterer Doppelpunkt (:
) und Einschränkungsname hinzugefügt werden.
Der Einschränkungsname und die Argumente werden dem IInlineConstraintResolver-Dienst übergeben, wodurch eine Instanz von IRouteConstraint für die URL-Verarbeitung erstellt werden kann. In der Routenvorlage blog/{article:minlength(10)}
wird beispielsweise die Einschränkung minlength
mit dem Argument 10
festgelegt. Weitere Informationen zu Routeneinschränkungen und eine Liste der vom Framework bereitgestellten Einschränkungen finden Sie im Abschnitt Routeneinschränkungen.
Routenparameter können darüber hinaus über Parametertransformatoren verfügen. Diese wandeln den Wert eines Parameters beim Generieren von Links um und passen Aktionen und Seiten an URLs an. Wie Einschränkungen können auch Parametertransformatoren einem Routenparameter inline hinzugefügt werden, indem ein Doppelpunkt (:
) und der Name des Transformators hinter dem Namen des Routenparameters hinzugefügt werden. In der Routenvorlage blog/{article:slugify}
wird beispielsweise der Transformator slugify
festgelegt. Weitere Informationen zu Parametertransformatoren finden Sie im Abschnitt Parametertransformatoren.
Die folgende Tabelle enthält Beispielvorlagen für Routen und deren Verhalten:
Routenvorlage | Beispiel-URI für Übereinstimmung | Der Anforderungs-URI |
---|---|---|
hello |
/hello |
Nur für den Pfad /hello wird eine Übereinstimmung ermittelt. |
{Page=Home} |
/ |
Eine Übereinstimmung wird ermittelt, und Page wird auf Home festgelegt. |
{Page=Home} |
/Contact |
Eine Übereinstimmung wird ermittelt, und Page wird auf Contact festgelegt. |
{controller}/{action}/{id?} |
/Products/List |
Stimmt mit dem Products -Controller und der List -Aktion überein. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Wird dem Controller Products und der Aktion Details zugeordnet, bei der id auf 123 festgelegt ist. |
{controller=Home}/{action=Index}/{id?} |
/ |
Stimmt mit dem Home -Controller und der Index -Methode überein. id wird ignoriert. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Stimmt mit dem Products -Controller und der Index -Methode überein. id wird ignoriert. |
Mit Vorlagen lässt sich Routing besonders leicht durchführen. Einschränkungen und Standardwerte können auch außerhalb der Routenvorlage angegeben werden.
Komplexe Segmente werden von rechts nach links auf eine nicht gierige Weise durch entsprechende Literaltrennzeichen verarbeitet. Beispielsweise ist [Route("/a{b}c{d}")]
ein komplexes Segment.
Komplexe Segmente funktionieren auf eine bestimmte Weise, die für eine erfolgreiche Verwendung verstanden werden muss. Das Beispiel in diesem Abschnitt zeigt, warum komplexe Segmente nur dann wirklich gut funktionieren, wenn der Trennzeichentext nicht innerhalb der Parameterwerte erscheint. Für komplexere Fälle ist die Verwendung eines RegEx und das anschließende manuelle Extrahieren der Werte erforderlich.
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.
Dies ist eine Zusammenfassung der Schritte, die beim Routing mit der Vorlage /a{b}c{d}
und dem URL-Pfad /abcd
ausgeführt werden. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen:
c
. Daher wird /abcd
von rechts durchsucht und /ab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /ab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.Nachfolgend ist ein Beispiel für einen negativen Fall mit derselben Vorlage /a{b}c{d}
und dem URL-Pfad /aabcd
. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen: Bei diesem Fall handelt es sich nicht um eine Übereinstimmung, was durch denselben Algorithmus belegt wird:
c
. Daher wird /aabcd
von rechts durchsucht und /aab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /aab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /a|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.a
, aber es gibt keine Routenvorlage mehr, die der Algorithmus analysieren kann, weshalb dies keine Übereinstimmung ist.Da der übereinstimmende Algorithmus nicht gierig ist:
Reguläre Ausdrücke bieten eine viel bessere Kontrolle über das Abgleichsverhalten.
Beim gierigen Abgleich, auch als Lazy Matching bezeichnet, wird die größtmögliche Zeichenfolge abgeglichen. Beim nicht gierigen Abgleich ist dies die kürzeste Zeichenfolge.
Ein Routing mit Sonderzeichen kann zu unerwarteten Ergebnissen führen. Stellen Sie sich z. B. einen Controller mit der folgenden Aktionsmethode vor:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Wenn string id
die folgenden codierten Werte enthält, können unerwartete Ergebnisse auftreten:
ASCII | Codiert |
---|---|
/ |
%2F |
|
+ |
Routenparameter sind nicht immer URL-decodiert. Dieses Problem wird möglicherweise in Zukunft behoben. Weitere Informationen finden Sie in diesem GitHub-Issue.
Routeneinschränkungen werden angewendet, wenn eine Übereinstimmung mit der eingehenden URL gefunden wurde und der URL-Pfad in Routenwerten mit Token versehen wird. In der Regel wird mit Routeneinschränkungen der Routenwert der zugehörigen Vorlage geprüft. Dabei wird anhand einer True/False-Entscheidung bestimmt, ob der Wert gültig ist. Für einige Routeneinschränkungen werden anstelle des Routenwerts andere Daten verwendet, um zu ermitteln, ob das Routing einer Anforderung möglich ist. HttpMethodRouteConstraint kann beispielsweise auf der Grundlage des HTTP-Verbs eine Anforderung entweder annehmen oder ablehnen. Einschränkungen werden in Routinganforderungen und bei der Linkgenerierung verwendet.
Warnung
Verwenden Sie keine Einschränkungen für die Eingabeüberprüfung. Wenn Einschränkungen für die Eingabevalidierung verwendet werden, führt eine ungültige Eingabe zu einem 404
-Fehler (Nicht gefunden). Eine ungültige Eingabe sollte zu einer ungültigen Anforderung (400
) mit einer entsprechenden Fehlermeldung führen. Verwenden Sie Routeneinschränkungen nicht, um Eingaben für eine bestimmte Route zu überprüfen, sondern um ähnliche Routen zu unterscheiden.
In der folgenden Tabelle werden Beispiele für Routeneinschränkungen und deren zu erwartendes Verhalten beschrieben:
Einschränkung | Beispiel | Beispiele für Übereinstimmungen | Hinweise |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Für jeden Integer wird eine Übereinstimmung ermittelt. |
bool |
{active:bool} |
true , FALSE |
Entspricht true oder false . Groß-/Kleinschreibung nicht beachten |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Entspricht einem gültigen DateTime -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Entspricht einem gültigen decimal -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen double -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen float -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Für einen gültigen Guid -Wert wird eine Übereinstimmung ermittelt. |
long |
{ticks:long} |
123456789 , -123456789 |
Für einen gültigen long -Wert wird eine Übereinstimmung ermittelt. |
minlength(value) |
{username:minlength(4)} |
Rick |
Die Zeichenfolge muss mindestens eine Länge von 4 Zeichen aufweisen. |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Die Zeichenfolge darf maximal eine Länge von 8 Zeichen aufweisen. |
length(length) |
{filename:length(12)} |
somefile.txt |
Die Zeichenfolge muss genau 12 Zeichen aufweisen. |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Die Zeichenfolge muss mindestens eine Länge von 8 und darf maximal eine Länge von 16 Zeichen aufweisen. |
min(value) |
{age:min(18)} |
19 |
Der Integerwert muss mindestens 18 sein. |
max(value) |
{age:max(120)} |
91 |
Der Integerwert darf nicht größer als 120 sein. |
range(min,max) |
{age:range(18,120)} |
91 |
Der Integerwert muss zwischen 18 und 120 liegen. |
alpha |
{name:alpha} |
Rick |
Die Zeichenfolge muss aus mindestens einem alphabetische Zeichen bestehen, a -z und ohne Unterscheidung zwischen Groß-/Kleinbuchstaben. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Die Zeichenfolge muss mit dem regulären Ausdruck übereinstimmen. Weitere Informationen finden Sie unter Tipps zum Definieren eines regulären Ausdrucks. |
required |
{name:required} |
Rick |
Hierdurch wird erzwungen, dass ein Wert, der kein Parameter ist, für die URL-Generierung vorhanden sein muss. |
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.
Auf einen einzelnen Parameter können mehrere durch Doppelpunkte getrennte Einschränkungen angewendet werden. Durch die folgende Einschränkung wird ein Parameter beispielsweise auf einen Integerwert größer oder gleich 1 beschränkt:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warnung
Für Routeneinschränkungen, mit denen die URL überprüft wird und die in den CLR-Typ umgewandelt werden, wird immer die invariante Kultur verwendet. Dies gilt z. B. für die Konvertierung in den CLR-Typ int
oder DateTime
. Diese Einschränkungen setzen voraus, dass die URL nicht lokalisierbar ist. Die vom Framework bereitgestellten Routeneinschränkungen ändern nicht die Werte, die in Routenwerten gespeichert sind. Alle Routenwerte, die aus der URL analysiert werden, werden als Zeichenfolgen gespeichert. Durch die float
-Einschränkung wird beispielsweise versucht, den Routenwert in einen Gleitkommawert zu konvertieren. Mit dem konvertierten Wert wird allerdings nur überprüft, ob eine Umwandlung überhaupt möglich ist.
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.
Reguläre Ausdrücke können mithilfe der regex(...)
-Routeneinschränkung als Inline-Einschränkungen angegeben werden. Methoden der MapControllerRoute-Familie akzeptieren auch ein Objektliteral von Einschränkungen. Wenn dieses Formular verwendet wird, werden Zeichenfolgenwerte als reguläre Ausdrücke interpretiert.
Der folgende Code verwendet eine Inline-RegEx-Einschränkung:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
Der folgende Code verwendet ein Objektliteral, um eine RegEx-Einschränkung anzugeben:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
Im ASP.NET Core-Framework wird dem Konstruktor für reguläre Ausdrücke RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
hinzugefügt. Eine Beschreibung dieser Member finden Sie unter RegexOptions.
In regulären Ausdrücken werden Trennzeichen und Token verwendet, die auch beim Routing und in der Programmiersprache C# in ähnlicher Weise verwendet werden. Token, die reguläre Ausdrücke enthalten, müssen mit einem Escapezeichen versehen werden. Wenn Sie den regulären Ausdruck ^\d{3}-\d{2}-\d{4}$
in einer Inline-Einschränkung verwenden möchten, nutzen Sie eine der folgenden Optionen:
\
-Zeichen in der Zeichenfolge durch \\
-Zeichen in der C#-Quelldatei, um das Escapezeichen für die Zeichenfolge \
zu setzen.Wenn Sie Trennzeichen für Routenparameter mit Escapezeichen versehen möchten ({
, }
, [
, ]
), geben Sie jedes Zeichen im Ausdruck doppelt ein (z. B. {{
, }}
, [[
, ]]
). In der folgenden Tabelle werden reguläre Ausdrücke und Ausdrücke aufgeführt, die mit Escapezeichen versehen sind:
Regulärer Ausdruck | Mit Escapezeichen versehener regulärer Ausdruck |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Beim Routing verwendete reguläre Ausdrücke beginnen oft mit dem ^
-Zeichen und stellen die Startposition der Zeichenfolge dar. Die Ausdrücke enden häufig mit einem Dollarzeichen ($
) und stellen das Ende der Zeichenfolge dar. Mit den Zeichen ^
und $
wird sichergestellt, dass der reguläre Ausdruck mit dem vollständigen Routenparameterwert übereinstimmt. Ohne die Zeichen ^
und $
werden mit dem regulären Ausdruck alle Teilzeichenfolgen ermittelt, was häufig nicht gewünscht ist. In der folgenden Tabelle finden Sie Beispiele für reguläre Ausdrücke. Außerdem wird erklärt, warum ein Abgleich erfolgreich ist oder fehlschlägt:
expression | Zeichenfolge | Match | Kommentar |
---|---|---|---|
[a-z]{2} |
hello | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
123abc456 | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
mz | Ja | Ausdruck stimmt überein |
[a-z]{2} |
MZ | Ja | keine Unterscheidung zwischen Groß-/Kleinbuchstaben |
^[a-z]{2}$ |
hello | Nein | siehe Erläuterungen zu ^ und $ oben |
^[a-z]{2}$ |
123abc456 | Nein | siehe Erläuterungen zu ^ und $ oben |
Weitere Informationen zur Syntax von regulären Ausdrücken finden Sie unter Sprachelemente für reguläre Ausdrücke – Kurzübersicht.
Einen regulären Ausdruck können Sie verwenden, um einen Parameter auf zulässige Werte einzuschränken. Mit {action:regex(^(list|get|create)$)}
werden beispielsweise für den action
-Routenwert nur die Werte list
, get
oder create
abgeglichen. Wenn die Zeichenfolge ^(list|get|create)$
dem Einschränkungswörterbuch übergeben wird, führt dies zum gleichen Ergebnis. Auch Einschränkungen, die dem zugehörigen Wörterbuch hinzugefügt werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden als reguläre Ausdrücke behandelt. Einschränkungen, die innerhalb einer Vorlage übergeben werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden nicht als reguläre Ausdrücke behandelt.
Benutzerdefinierte Routeneinschränkungen können durch Implementierung der IRouteConstraint-Schnittstelle erstellt werden. Die IRouteConstraint
-Schnittstelle umfasst die Match-Methode, die true
zurückgibt, wenn die Einschränkung erfüllt wird, und andernfalls false
.
Benutzerdefinierte Routeneinschränkungen werden nur selten benötigt. Bevor Sie eine benutzerdefinierte Routeneinschränkung implementieren, sollten Sie Alternativen in Betracht ziehen, wie z. B. Modellbindung.
Der ASP.NET Core-Ordner Constraints bietet nützliche Beispiele für die Erstellung von Einschränkungen. Beispiel: GuidRouteConstraint.
Zum Verwenden eines benutzerdefinierten IRouteConstraint
-Elements muss der Routeneinschränkungstyp bei der ConstraintMap-Eigenschaft der App im Dienstcontainer registriert werden. Eine ConstraintMap
ist ein Wörterbuch, das Routeneinschränkungsschlüssel IRouteConstraint
-Implementierungen zuordnet, die diese Einschränkungen überprüfen. Die ConstraintMap
einer App kann in Program.cs
entweder als Teil eines AddRouting-Aufrufs oder durch direktes Konfigurieren von RouteOptions mit builder.Services.Configure<RouteOptions>
aktualisiert werden. Beispiel:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
Die vorangehende Einschränkung wird im folgenden Code angewendet:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
Die Implementierung von NoZeroesRouteConstraint
verhindert die Verwendung von 0
in einem Routenparameter:
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
public bool Match(
HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
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.
Der vorangehende Code:
0
im {id}
-Segment der Route vorhanden ist.Der folgende Code bietet einen besseren Ansatz, um zu verhindern, dass eine id
mit einer 0
verarbeitet wird:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
Der vorangehende Code bietet im Vergleich zum NoZeroesRouteConstraint
-Ansatz folgende Vorteile:
0
enthält.Parametertransformatoren:
Beispielsweise generiert ein benutzerdefinierter Parametertransformator slugify
im Routenmuster blog\{article:slugify}
mit Url.Action(new { article = "MyTestArticle" })
blog\my-test-article
.
Betrachten Sie die folgende Implementierung von IOutboundParameterTransformer
:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Um einen Parametertransformator in einem Routenmuster zu verwenden, konfigurieren Sie ihn mit ConstraintMap in Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
Das ASP.NET Core-Framework verwendet Parametertransformatoren, um den URI zu transformieren, zu dem ein Endpunkt aufgelöst wird. Beispielsweise wandeln Parametertransformatoren die Routenwerte um, die zum Zuordnen folgender Elemente verwendet werden: area
, controller
, action
und page
.
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Mit der vorstehenden Routenvorlage wird die Aktion SubscriptionManagementController.GetAll
dem URI /subscription-management/get-all
zugeordnet. Ein Parametertransformator ändert nicht die zum Generieren eines Links verwendeten Routenwerte. Beispielsweise gibt Url.Action("GetAll", "SubscriptionManagement")
/subscription-management/get-all
aus.
ASP.NET Core bietet API-Konventionen für die Verwendung von Parametertransformatoren mit generierten Routen:
Dieser Abschnitt enthält eine Referenz für den Algorithmus, der durch die URL-Generierung implementiert wird. In der Praxis werden bei den meisten komplexen Beispielen für die URL-Generierung Controller oder Razor Pages verwendet. Weitere Informationen finden Sie unter Routing in Controllern.
Die URL-Generierung beginnt mit einem Aufruf von LinkGenerator.GetPathByAddress oder einer ähnlichen Methode. Die Methode wird mit einer Adresse, mehreren Routenwerten und optional mit Informationen zur aktuellen Anforderung von HttpContext
versehen.
Im ersten Schritt wird die Adresse verwendet, um bestimmte Endpunktkandidaten mithilfe einer IEndpointAddressScheme<TAddress>-Schnittstelle aufzulösen, die dem Adresstyp entspricht.
Sobald eine Kandidatengruppe anhand des Adressschemas gefunden wurde, werden die Endpunkte geordnet und iterativ verarbeitet, bis die URL-Generierung erfolgreich abgeschlossen ist. Bei der URL-Generierung wird nicht auf Mehrdeutigkeiten geprüft, daher ist das erste zurückgegebene Ergebnis das Endergebnis.
Der erste Schritt bei der Behebung von Problemen bei der URL-Generierung ist die Einstellung des Protokolliergrads von Microsoft.AspNetCore.Routing
auf TRACE
. LinkGenerator
protokolliert viele Details über die Verarbeitung, die bei der Problembehebung nützlich sein können.
Ausführliche Informationen zur URL-Generierung finden Sie unter Referenz für URL-Generierung.
Mithilfe von Adressen wird bei der URL-Generierung ein Aufruf in der API zur Linkgenerierung an mehrere Endpunktkandidaten gebunden.
Adressen sind ein erweiterbares Konzept, das standardmäßig mit zwei Implementierungen bereitgestellt wird:
string
) als Adresse: IUrlHelper
, Taghilfsprogrammen, HTML-Hilfsprogrammen, Aktionsergebnissen usw. verwendet wird.Aufgabe des Adressschemas ist es, die Verbindung zwischen der Adresse und den übereinstimmenden Endpunkten anhand von beliebigen Kriterien herzustellen:
Aus der aktuellen Anforderung greift das Routing auf die Routenwerte der aktuellen Anforderung HttpContext.Request.RouteValues
zu. Die mit der aktuellen Anforderung verbundenen Werte werden als Umgebungswerte bezeichnet. Aus Gründen der Übersichtlichkeit werden in der Dokumentation die an die Methoden übergebenen Routenwerte als explizite Werte bezeichnet.
Das folgende Beispiel zeigt Umgebungswerte und explizite Werte. Er liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte:
public class WidgetController : ControllerBase
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public IActionResult Index()
{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;
return Content(indexPath);
}
// ...
Der vorangehende Code:
/Widget/Index/17
zurück.Der folgende Code liefert nur explizite Werte und keine Umgebungswerte:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
Die vorhergehende Methode gibt /Home/Subscribe/17
zurück.
Der folgende Code in WidgetController
gibt /Widget/Subscribe/17
zurück:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
Der folgende Code stellt den Controller aus den Umgebungswerten in der aktuellen Anforderung und explizite Werte dar:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
Für den Code oben gilt:
/Gadget/Edit/17
wird zurückgegeben.action
-Namen und route
-Werte.Sie liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
Im vorangehenden Code wird url
auf /Edit/17
festgelegt, wenn die Option zum Bearbeiten der Razor Page die folgende Seitenanweisung enthält:
@page "{id:int}"
Wenn die Routenvorlage "{id:int}"
nicht in der Seite „Bearbeiten“ enthalten ist, ist url
gleich /Edit?id=17
.
Das Verhalten der IUrlHelper-Schnittstelle von MVC fügt zusätzlich zu den hier beschriebenen Regeln eine weitere Komplexitätsebene hinzu:
IUrlHelper
liefert immer die Routenwerte aus der aktuellen Anforderung als Umgebungswerte.action
und controller
als explizite Werte, sofern sie nicht vom Entwickler außer Kraft gesetzt werden.page
als expliziten Wert, sofern er nicht außer Kraft gesetzt wird. IUrlHelper.Page
setzt immer den aktuellen Routenwert handler
mit null
als expliziten Wert außer Kraft, sofern er nicht außer Kraft gesetzt wird.Benutzer sind oft von den Verhaltensdetails der Umgebungswerte überrascht, da MVC anscheinend nicht den eigenen Regeln folgt. Aus Verlaufs- und Kompatibilitätsgründen weisen bestimmte Routenwerte wie action
, controller
, page
und handler
ein spezielles Verhalten auf.
Die äquivalente Funktionalität, die durch LinkGenerator.GetPathByAction
und LinkGenerator.GetPathByPage
bereitgestellt wird, verdoppelt diese Anomalien von IUrlHelper
aus Kompatibilitätsgründen.
Sobald die Gruppe der Endpunktkandidaten ermittelt ist, wird der URL-Generierungsalgorithmus angewendet:
Der erste Schritt in diesem Prozess wird als Routenwertinvalidierung bezeichnet. Die Routenwertinvalidierung ist der Prozess, bei dem das Routing entscheidet, welche Routenwerte aus den Umgebungswerten verwendet und welche ignoriert werden sollen. Jeder Umgebungswert wird berücksichtigt und entweder mit den expliziten Werten kombiniert oder aber ignoriert.
Denken Sie daran, dass Umgebungswerte Anwendungsentwicklern in allgemeinen Fällen das Schreiben von Code sparen können. In der Regel sind die Szenarios, in denen Umgebungswerte hilfreich sind, mit MVC verknüpft:
Aufrufe an LinkGenerator
oder IUrlHelper
, die null
zurückgeben, sind meist dadurch bedingt, dass die Routenwertinvalidierung nicht verstanden wurde. Beheben Sie die Routenwertinvalidierung, indem Sie explizit mehr Routenwerte angeben, um zu prüfen, ob das Problem dadurch gelöst wird.
Bei der Routenwertinvalidierung wird davon ausgegangen, dass das URL-Schema der Anwendung hierarchisch ist, mit einer von links nach rechts gebildeten Hierarchie. Sehen Sie sich die einfache Controllerroutenvorlage {controller}/{action}/{id?}
an, um ein Gespür dafür zu bekommen, wie dies in der Praxis funktioniert. Durch eine Änderung auf einen Wert werden alle rechts angezeigten Routenwerte ungültig. Dies spricht für die These von der Hierarchie. Wenn die App einen Umgebungswert für id
hat und der Vorgang einen anderen Wert für controller
angibt:
id
wird nicht wiederverwendet, weil {controller}
links von {id?}
steht.Einige Beispiele veranschaulichen dieses Prinzip:
id
enthalten, wird der Umgebungswert für id
ignoriert. Die Umgebungswerte für controller
und action
können verwendet werden.action
enthalten, wird jeder Umgebungswert für action
ignoriert. Die Umgebungswerte für controller
können verwendet werden. Wenn sich der explizite Wert für action
von dem Umgebungswert für action
unterscheidet, wird der Wert id
nicht verwendet. Wenn der explizite Wert für action
mit dem Umgebungswert für action
übereinstimmt, kann der Wert id
verwendet werden.controller
enthalten, wird jeder Umgebungswert für controller
ignoriert. Wenn sich der explizite Wert für controller
von dem Umgebungswert für controller
unterscheidet, werden die Werte action
und id
nicht verwendet. Wenn der explizite Wert für controller
mit dem Umgebungswert für controller
übereinstimmt, können die Werte action
und id
verwendet werden.Dieser Prozess wird zusätzlich durch die vorhandenen Attributrouten und dedizierten konventionellen Routen erschwert. Konventionelle Routen des Controllers wie {controller}/{action}/{id?}
legen eine Hierarchie mithilfe von Routenparametern fest. Bei bestimmten konventionellen Routen und Attributrouten zu Controllern und Razor Pages:
Für diese Fälle definiert die URL-Generierung das Konzept der erforderlichen Werte. Bei Endpunkten, die von Controllern und Razor Pages erstellt wurden, sind erforderliche Werte angegeben, die eine Routenwertinvalidierung ermöglichen.
Der Algorithmus der Routenwertinvalidierung im Detail:
An diesem Punkt ist der Vorgang zur URL-Generierung bereit, Routeneinschränkungen auszuwerten. Die akzeptierten Werte werden mit den Standardwerten der Parameter kombiniert, die für Einschränkungen bereitgestellt werden. Wenn alle Einschränkungen erfüllt sind, wird der Vorgang fortgesetzt.
Als Nächstes können die akzeptierten Werte verwendet werden, um die Routenvorlage zu erweitern. Die Routenvorlage wird verarbeitet:
Explizit bereitgestellte Werte, für die keine Übereinstimmungen mit einem Routensegment ermittelt werden, werden der Abfragezeichenfolge hinzugefügt. In der folgenden Tabelle werden die Ergebnisse dargestellt, die aus der Verwendung der Routenvorlage {controller}/{action}/{id?}
hervorgehen:
Umgebungswerte | Explizite Werte | Ergebnis |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Optionale Routenparameter müssen nach allen erforderlichen Routenparametern kommen. Im folgenden Code müssen die Parameter id
und name
nach dem Parameter color
kommen:
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers;
[Route("api/[controller]")]
public class MyController : ControllerBase
{
// GET /api/my/red/2/joe
// GET /api/my/red/2
// GET /api/my
[HttpGet("{color}/{id:int?}/{name?}")]
public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
{
return Ok($"{color} {id} {name ?? ""}");
}
}
Der folgende Code zeigt ein Beispiel für ein Schema zur URL-Generierung, das vom Routing nicht unterstützt wird:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
Im vorangehenden Code wird der culture
-Routenparameter für die Lokalisierung verwendet. Ziel ist es, dass der culture
-Parameter immer als Umgebungswert akzeptiert wird. Der culture
-Parameter wird jedoch aufgrund der Art und Weise, wie die erforderlichen Werte funktionieren, nicht als Umgebungswert akzeptiert:
"default"
-Routenvorlage befindet sich der culture
-Routenparameter links von controller
, sodass controller
durch Änderungen an culture
nicht ungültig wird."blog"
-Routenvorlage wird der culture
-Routenparameter rechts von controller
betrachtet, der in den erforderlichen Werten aufgeführt ist.Die LinkParser Klasse fügt die Möglichkeit hinzu, einen URL-Pfads in einen Satz von Routenwerten zu zerlegen. Die ParsePathByEndpointName Methode verwendet einen Endpunktnamen und einen URL-Pfad und gibt einen Satz von aus dem URL-Pfad extrahierten Routenwerten zurück.
Im folgenden Beispielcontroller verwendet die GetProduct
Aktion eine Routenvorlage von api/Products/{id}
und hat eine Name von GetProduct
:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
In derselben Controllerklasse erwartet die AddRelatedProduct
Aktion einen URL-Pfad, pathToRelatedProduct
, der als Abfragezeichenfolgen-Parameter bereitgestellt werden kann:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
Im vorherigen Beispiel extrahiert die AddRelatedProduct
Aktion den id
Routenwert aus dem URL-Pfad. Beispielsweise wird der /api/Products/1
Wert bei einem relatedProductId
URL-Pfad auf 1
festgelegt. Dieser Ansatz ermöglicht es den Clients der API, URL-Pfade beim Verweisen auf Ressourcen zu verwenden, ohne wissen zu müssen, wie eine solche URL strukturiert ist.
Die folgenden Links enthalten Informationen zum Konfigurieren von Endpunktmetadaten:
[MinimumAgeAuthorize]
-AttributRequireHost wendet eine Einschränkung auf die Route an, für die der angegebene Host erforderlich ist. Der Parameter RequireHost
oder [Host] kann wie folgt lauten:
www.domain.com
, entspricht www.domain.com
mit einem beliebigen Port.*.domain.com
, entspricht www.domain.com
, subdomain.domain.com
oder www.subdomain.domain.com
an einem beliebigen Port.*:5000
, entspricht Port 5000 mit einem beliebigen Host.www.domain.com:5000
oder *.domain.com:5000
, entspricht dem Host und Port.Es können mehrere Parameter mit RequireHost
oder [Host]
angegeben werden. Die Einschränkung gleicht die Hosts ab, die für einen der Parameter gültig sind. Beispielsweise entspricht [Host("domain.com", "*.domain.com")]
domain.com
, www.domain.com
und subdomain.domain.com
.
Im folgenden Code wird RequireHost
verwendet, um den angegebenen Host auf der Route anzufordern:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
Im folgenden Code wird das [Host]
-Attribut für den Controller verwendet, um die einzelnen angegebenen Hosts anzufordern:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Wenn das [Host]
-Attribut sowohl auf die Controller- als auch auf die Aktionsmethode angewendet wird, trifft Folgendes zu:
Die MapGroup-Erweiterungsmethode hilft, Gruppen von Endpunkten mit einem gemeinsamen Präfix zu organisieren. Sie reduziert sich wiederholenden Code und ermöglicht die benutzerdefinierte Anpassung ganzer Gruppen von Endpunkten mit einem einzigen Aufruf von Methoden wie RequireAuthorization und WithMetadata,die Endpunktmetadaten hinzufügen.
Der folgende Code erstellt beispielsweise zwei ähnliche Endpunktgruppen:
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
In diesem Szenario können Sie eine relative Adresse für den Location
-Header im 201 Created
-Ergebnis verwenden:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Die erste Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /public/todos
und ist ohne Authentifizierung zugänglich. Die zweite Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /private/todos
und erfordert Authentifizierung.
Die QueryPrivateTodos
-Endpunktfilterfactory ist eine lokale Funktion, die die TodoDb
-Parameter des Routenhandlers so ändert, dass Zugriff auf private Aufgabendaten zulässig ist und diese gespeichert werden können.
Routengruppen unterstützen auch geschachtelte Gruppen und komplexe Präfixmuster mit Routenparametern und -einschränkungen. Im folgenden Beispiel kann der der user
-Gruppe zugeordnete Routenhandler die Routenparameter {org}
und {group}
erfassen, die in den Präfixen der äußeren Gruppe definiert sind.
Das Präfix kann auch leer sein. Dies kann hilfreich sein, um Endpunktmetadaten oder Filter einer Gruppe von Endpunkten hinzuzufügen, ohne das Routenmuster zu ändern.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Das Hinzufügen von Filtern oder Metadaten zu einer Gruppe verhält sich genauso wie das individuelle Hinzufügen zu jedem Endpunkt, bevor zusätzliche Filter oder Metadaten hinzugefügt werden, die einer inneren Gruppe oder einem bestimmten Endpunkt hinzugefügt wurden.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
Im Beispiel oben protokolliert der äußere Filter die eingehende Anforderung vor dem inneren Filter, obwohl er als zweiter hinzugefügt wurde. Da die Filter auf verschiedene Gruppen angewendet wurden, spielt die Reihenfolge, in der sie relativ zueinander hinzugefügt wurden, keine Rolle. Die Reihenfolge, in der Filter hinzugefügt werden, spielt eine Rolle, wenn sie auf dieselbe Gruppe oder einen bestimmten Endpunkt angewendet werden.
Eine Anforderung an /outer/inner/
protokolliert Folgendes:
/outer group filter
/inner group filter
MapGet filter
Wenn eine App Leistungsprobleme hat, wird die Ursache häufig beim Routing vermutet. Das Routing wird deshalb in Betracht gezogen, weil Frameworks wie Controller und Razor Pages in ihren Protokollierungsmeldungen die innerhalb des Frameworks verbrachte Zeit angeben. Wenn es einen signifikanten Unterschied zwischen der von den Controllern gemeldeten Zeit und der Gesamtzeit der Anforderung gibt:
Die Leistung des Routings wird anhand von Tausenden von Endpunkten getestet. Es ist unwahrscheinlich, dass eine typische App auf ein Leistungsproblem stößt, nur weil diese zu umfangreich ist. Die häufigste Ursache für eine langsames Routing ist üblicherweise eine schlecht funktionierende benutzerdefinierte Middleware.
Das folgende Codebeispiel veranschaulicht eine grundlegende Technik zur Eingrenzung der Verzögerungsquelle:
var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.MapGet("/", () => "Timing Test.");
Auf das Zeitrouting:
Dies ist ein einfacher Weg, um die Verzögerung zu verringern, wenn sie signifikant ist, zum Beispiel größer als 10ms
. Wenn Time 2
von Time 1
subtrahiert wird, ergibt sich die in der UseRouting
-Middleware benötigte Zeit.
Der folgende Code verwendet einen kompakteren Ansatz als der vorangegangene Zeitcode:
public sealed class AutoStopwatch : IDisposable
{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());
public void Dispose()
{
if (_disposed)
{
return;
}
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");
Die folgende Liste gibt einen Einblick in Routingfeatures, die im Vergleich zu einfachen Routenvorlagen relativ teuer sind:
{x}-{y}-{z}
): ASP.NET Core verwendet standardmäßig einen Routingalgorithmus, bei dem Arbeitsspeicher zugunsten von CPU-Zeit geopfert wird. Dies hat den angenehmen Effekt, dass die Zeit für den Routenabgleich nur von der Länge des abzugleichenden Pfads und nicht von der Routenanzahl abhängt. Dieser Ansatz kann jedoch in einigen Fällen problematisch sein, etwa dann, wenn die App eine große Anzahl von Routen umfasst (mehrere Tausend) und die Routen eine große Anzahl variabler Präfixe enthalten. Beispiel: Die Routen weisen Parameter in frühen Segmenten der Route auf, etwa {parameter}/some/literal
.
Es ist unwahrscheinlich, dass eine App in eine Situation gerät, in der dies ein Problem darstellt, es sei denn:
Microsoft.AspNetCore.Routing.Matching.DfaNode
-Instanzen.Es gibt verschiedene Techniken und Optimierungen, die auf Routen angewendet werden können, um dieses Szenario weitgehend zu vermeiden:
{parameter:int}
, {parameter:guid}
, {parameter:regex(\\d+)}
.
MapDynamicControllerRoute
und MapDynamicPageRoute
erreicht werden.Dieser Abschnitt enthält Hinweise für Bibliotheksautoren, die auf dem Routing aufbauen. Diese Details sollen sicherstellen, dass App-Entwickler gute Erfahrungen mit Bibliotheken und Frameworks machen, die das Routing erweitern.
Wenn Sie ein Framework erstellen möchten, das das Routing für den URL-Abgleich verwendet, sollten Sie zunächst eine Benutzeroberfläche definieren, die auf UseEndpoints aufbaut.
Es wird empfohlen, dass Sie auf IEndpointRouteBuilder aufbauen. Auf diese Weise können Benutzer Ihr Framework mit anderen ASP.NET Core-Features problemlos zusammenstellen. Jede ASP.NET Core-Vorlage umfasst die Routingfunktionalität. Gehen Sie davon aus, dass das Routing vorhanden und den Benutzern vertraut ist.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
Es wird empfohlen, dass Sie einen versiegelten konkreten Typ aus einem Aufruf an MapMyFramework(...)
zurückgeben, der IEndpointConventionBuilder implementiert. Die meisten Map...
-Methoden in Frameworks folgen diesem Muster. Die IEndpointConventionBuilder
-Schnittstelle:
Wenn Sie Ihren eigenen Typ deklarieren, können Sie dem Generator Ihre eigene frameworkspezifische Funktionalität hinzufügen. Es ist in Ordnung, einen vom Framework deklarierten Generator zu umschließen und Aufrufe an ihn weiterzuleiten.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
Ziehen Sie in Betracht, Ihre eigene EndpointDataSource-Klasse zu schreiben. Die EndpointDataSource
-Klasse vom primitiven Typ auf niedriger Ebene eignet sich zum Deklarieren und Aktualisieren einer Sammlung von Endpunkten. EndpointDataSource
ist eine leistungsstarke API, die von Controllern und Razor Pages verwendet wird.
Die Routingtests haben ein grundlegendes Beispiel für eine Datenquelle, die nicht aktualisiert wird.
ERWÄGEN Sie die Implementierung von GetGroupedEndpoints. Dadurch erhalten Sie vollständige Kontrolle über ausgeführte Gruppenkonventionen und die endgültigen Metadaten für die gruppierten Endpunkte. Dies ermöglicht z. B. benutzerdefinierten EndpointDataSource
Implementierungen das Ausführen von Endpunktfiltern, die Gruppen hinzugefügt werden.
Versuchen Sie nicht, eine EndpointDataSource
-Klasse standardmäßig zu registrieren. Fordern Sie die Benutzer auf, Ihr Framework in UseEndpoints zu registrieren. Der Grundgedanke beim Routing ist, dass standardmäßig nichts enthalten ist, und dass die Endpunkte bei UseEndpoints
registriert werden müssen.
Ziehen Sie in Betracht, Metadatentypen als Schnittstelle zu definieren.
Ermöglichen Sie, Metadatentypen als Attribut für Klassen und Methoden zu verwenden.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks wie Controller und Razor Pages unterstützen die Anwendung von Metadatenattributen auf Typen und Methoden. Für das Deklarieren von Metadatentypen gilt:
Durch die Deklaration eines Metadatentyps als Schnittstelle wird die Flexibilität zusätzlich erhöht:
Ermöglichen Sie, Metadaten außer Kraft zu setzen, wie in folgendem Beispiel gezeigt:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
Die beste Möglichkeit, diese Richtlinien zu befolgen, besteht darin, die Definition von Markermetadaten zu vermeiden:
Die Metadatensammlung ist geordnet und unterstützt das Außerkraftsetzen nach Priorität. Im Fall von Controllern sind Metadaten für die Aktionsmethode am spezifischsten.
Nutzen Sie Middleware mit und ohne Routing.
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Ein gutes Beispiel für diese Vorgabe ist die UseAuthorization
-Middleware. Mithilfe der Autorisierungsmiddleware können Sie eine Fallbackrichtlinie einbauen. Die Fallbackrichtlinie gilt, falls angegeben, für beide:
Dies macht die Autorisierungsmiddleware außerhalb des Routingkontexts sehr nützlich. Die Autorisierungsmiddleware kann für die traditionelle Middlewareprogrammierung verwendet werden.
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"
}
}
}
Das Routing wird für das Abgleichen von HTTP-Anforderungen und das Verteilen an ausführbare Endpunkte der App eingesetzt. Endpunkte sind die Einheiten des ausführbaren Codes für die Anforderungsverarbeitung in der App. Endpunkte werden in der App definiert und beim Start der App konfiguriert. Beim Endpunktabgleich können Werte aus der Anforderungs-URL extrahiert und für die Verarbeitung der Anforderung bereitgestellt werden. Mithilfe von Endpunktinformationen aus der App lassen sich durch das Routing URLs generieren, die Endpunkten zugeordnet werden.
Apps können das Routing mit folgenden Funktionen konfigurieren:
In diesem Artikel werden die grundlegenden Details zum ASP.NET Core-Routing beschrieben. Informationen zur Routingkonfiguration finden Sie wie folgt:
Der folgende Code veranschaulicht ein einfaches Beispiel für das Routing:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Das vorherige Beispiel enthält einen einzelnen Endpunkt unter Verwendung der MapGet-Methode:
GET
-HTTP-Anforderung an die Stamm-URL /
gesendet wird: Hello World!
wird in die HTTP-Antwort geschrieben.GET
bzw. die Stamm-URL nicht /
ist, gibt es keinen Routenabgleich und es wird ein HTTP-404-Fehler zurückgegeben.Beim Routing wird ein Middlewarepaar verwendet, das durch UseRouting und UseEndpoints registriert wird:
UseRouting
fügt der Middlewarepipeline einen Routenabgleich hinzu. Diese Middleware prüft die in der App definierten Endpunkte und wählt anhand der Anforderung die beste Übereinstimmung aus.UseEndpoints
fügt der Middlewarepipeline die Endpunktausführung hinzu. Dabei wird der mit dem ausgewählten Endpunkt verknüpfte Delegat ausgeführt.Apps müssen UseRouting
oder UseEndpoints
normalerweise nicht aufrufen. WebApplicationBuilder konfiguriert eine Middlewarepipeline, die die in Program.cs
hinzugefügte Middleware mit UseRouting
und UseEndpoints
umschließt. Apps können jedoch die Reihenfolge ändern, in der UseRouting
und UseEndpoints
ausgeführt werden, indem sie diese Methoden explizit aufrufen. Der folgende Code ruft beispielsweise UseRouting
explizit auf:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
Für den Code oben gilt:
app.Use
wird eine benutzerdefinierte Middleware registriert, die am Anfang der Pipeline ausgeführt wird.UseRouting
konfiguriert die Middleware für den Routenabgleich zur Ausführung nach der benutzerdefinierten Middleware.MapGet
registrierte Endpunkt wird am Ende der Pipeline ausgeführt.Wenn das vorhergehende Beispiel keinen Aufruf an UseRouting
enthält, wird die benutzerdefinierte Middleware nach der Middleware zum Routenabgleich ausgeführt.
Die MapGet
-Methode wird verwendet, um einen Endpunkt zu definieren. Ein Endpunkt kann Folgendes sein:
Endpunkte, die von der App zugeordnet und ausgeführt werden können, sind in UseEndpoints
konfiguriert. Mit MapGet, MapPost und ähnlichen Methoden werden beispielsweise Anforderungsdelegate mit dem Routingsystem verbunden. Zudem können weitere Methoden zur Verbindung von ASP.NET Core-Frameworkfunktionen mit dem Routingsystem verwendet werden:
Das folgende Beispiel zeigt das Routing mit einer anspruchsvolleren Routenvorlage:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
Die Zeichenfolge /hello/{name:alpha}
ist eine Routenvorlage. Eine Routenvorlage, um zu konfigurieren, wie der Endpunkt abgeglichen wird. In diesem Fall gleicht die Vorlage Folgendes ab:
/hello/Docs
/hello/
beginnen, gefolgt von einer Sequenz alphabetischer Zeichen. :alpha
wendet eine Routeneinschränkung an, die nur alphabetische Zeichen abgleicht. Routeneinschränkungen werden weiter unten in diesem Artikel erläutert.Das zweite Segment des URL-Pfads, {name:alpha}
:
name
gebunden.Das folgende Beispiel zeigt das Routing mit Integritätsprüfungen und Autorisierung:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
Im vorherigen Beispiel wird veranschaulicht, wie Sie:
Der MapHealthChecks-Aufruf fügt einen Endpunkt für eine Integritätsprüfung hinzu. Durch die Verkettung von RequireAuthorization mit diesem Aufruf wird eine Autorisierungsrichtlinie an den Endpunkt angefügt.
Der Aufruf von UseAuthentication und UseAuthorization wird die Authentifizierungs- und Autorisierungsmiddleware hinzugefügt. Diese Middleware wird zum Ausführen folgender Aktionen zwischen UseRouting und UseEndpoints
platziert:
UseRouting
ausgewählten Endpunkts.Im vorangehenden Beispiel gibt es zwei Endpunkte, aber nur dem für die Integritätsprüfung ist eine Autorisierungsrichtlinie angefügt. Wenn die Anforderung mit dem Endpunkt der Integritätsprüfung, /healthz
, übereinstimmt, wird eine Autorisierungsprüfung durchgeführt. Dadurch wird veranschaulicht, dass Endpunkten zusätzliche Daten zugeordnet werden können. Diese zusätzlichen Daten werden als Metadaten des Endpunkts bezeichnet:
Durch Hinzufügen des effizienten Endpunkt-Konzepts stellt das Routingsystem eine Ergänzung der Middlewarepipeline dar. Endpunkte stehen für Funktionseinheiten der App, die sich in Bezug auf Routing, Autorisierung und die Anzahl der ASP.NET Core-Systeme voneinander unterscheiden.
Ein ASP.NET Core-Endpunkt ist:
Der folgende Code zeigt, wie der Endpunkt, der mit der aktuellen Anforderung übereinstimmt, abgerufen und geprüft werden kann:
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}
await next(context);
});
app.MapGet("/", () => "Inspect Endpoint.");
Der Endpunkt, falls ausgewählt, kann aus dem HttpContext
-Element abgerufen werden. Seine Eigenschaften können geprüft werden. Endpunktobjekte sind unveränderlich und können nach der Erstellung nicht mehr geändert werden. Der häufigste Typ des Endpunkts ist eine RouteEndpoint-Klasse. RouteEndpoint
enthält Informationen, die eine Auswahl durch das Routingsystem ermöglichen.
Im vorangehenden Code wird mit app.Use eine Middleware inline konfiguriert.
Der folgende Code zeigt, dass es, je nachdem, wo app.Use
in der Pipeline aufgerufen wird, möglicherweise keinen Endpunkt gibt:
// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");
app.UseEndpoints(_ => { });
// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
Im vorangehenden Beispiel werden Console.WriteLine
-Anweisungen hinzugefügt, die anzeigen, ob ein Endpunkt ausgewählt wurde oder nicht. Aus Gründen der Übersichtlichkeit wird in dem Beispiel dem bereitgestellten /
-Endpunkt ein Anzeigename zugewiesen.
Das vorherige Beispiel enthält auch Aufrufe an UseRouting
und UseEndpoints
, um genau zu steuern, wann diese Middleware in der Pipeline ausgeführt wird.
Wenn Sie diesen Code mit einer URL /
ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Wenn Sie diesen Code mit einer anderen URL ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Diese Ausgabe zeigt Folgendes:
UseRouting
aufgerufen wird.UseRouting
und UseEndpoints ungleich NULL.UseEndpoints
-Middleware ist eine Terminalmiddleware, wenn eine Übereinstimmung gefunden wird. Terminalmiddleware wird weiter unten in diesem Artikel definiert.UseEndpoints
wird nur ausgeführt, wenn keine Übereinstimmung gefunden wird.Die UseRouting
-Middleware verwendet die SetEndpoint-Methode, um den Endpunkt an den aktuellen Kontext anzufügen. Es ist möglich, die UseRouting
-Middleware durch benutzerdefinierte Logik zu ersetzen und dennoch die Vorteile durch die Verwendung von Endpunkten zu nutzen. Endpunkte befinden sich auf niedriger Ebene, wie Middleware, und sind nicht an die Routingimplementierung gekoppelt. Die meisten Apps müssen UseRouting
nicht durch eigene Logik ersetzen.
Die UseEndpoints
-Middleware ist so konzipiert, dass Sie zusammen mit der UseRouting
-Middleware verwendet werden kann. Die Hauptlogik zum Ausführen eines Endpunkts ist nicht kompliziert. Mit GetEndpoint können Sie einen Endpunkt abrufen und dann dessen RequestDelegate-Eigenschaft aufrufen.
Der folgende Code veranschaulicht, wie Middleware das Routing beeinflussen oder darauf reagieren kann:
app.UseHttpMethodOverride();
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
await next(context);
});
app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }
Im vorherigen Beispiel werden zwei wichtige Konzepte dargestellt:
UseRouting
ausgeführt werden, um die Daten zu ändern, auf denen das Routing basiert.
UseRouting
und UseEndpoints ausgeführt werden, um die Ergebnisse des Routings vor der Ausführung des Endpunkts zu verarbeiten.
UseRouting
und UseEndpoints
ausgeführt wird: UseAuthorization
und UseCors
.Der vorangehende Code zeigt ein Beispiel für eine benutzerdefinierte Middleware, die entpunktbezogene Richtlinien unterstützt. Die Middleware schreibt ein Überwachungsprotokoll für den Zugriff auf vertrauliche Daten in der Konsole. Die Middleware kann so konfiguriert werden, dass ein Endpunkt mit den -Metadaten RequiresAuditAttribute
wird. In diesem Beispiel wird ein Opt-In-Muster veranschaulicht, bei dem nur Endpunkte überwacht werden, die als vertraulich markiert sind. Es ist möglich, diese Logik umgekehrt zu definieren, indem beispielsweise alles geprüft wird, was nicht als sicher markiert ist. Das Endpunktmetadaten-System ist flexibel. Diese Logik lässt sich für jeden Anwendungsfall passend schreiben.
Der vorherige Beispielcode soll die grundlegenden Konzepte von Endpunkten veranschaulichen. Das Beispiel ist nicht für Produktionsumgebungen vorgesehen. Eine vollständigere Version einer Middleware für Überwachungsprotokolle würde Folgendes bieten:
Die Metadaten der Überwachungsrichtlinie RequiresAuditAttribute
sind als Attribute
definiert, um die Verwendung mit klassenbasierten Frameworks wie Controllern und SignalR zu erleichtern. Bei Verwendung von Route-zu-Code:
Die bewährten Methoden für Metadatentypen sind, sie entweder als Schnittstellen oder als Attribute zu definieren. Schnittstellen und Attribute ermöglichen die Wiederverwendung von Code. Das Metadatensystem ist flexibel und weist keine Einschränkungen auf.
Im folgenden Beispiel werden sowohl Terminalmiddleware als auch Routing veranschaulicht:
// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
app.UseRouting();
// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");
Beim in Approach 1:
gezeigten Stil von Middleware handelt es sich um Terminalmiddleware. Sie wird als Terminalmiddleware bezeichnet, da sie einen Abgleich durchgeführt.
Path == "/"
für die Middleware und Path == "/Routing"
für das Routing.next
-Middleware aufzurufen.Sie wird „Terminal Middleware“ genannt, da sie die Suche beendet, einige Funktionen ausführt und dann zurückkehrt.
In der folgenden Liste werden Terminalmiddleware und Routing verglichen:
next
aufzurufen.UseAuthorization
und UseCors
.
UseAuthorization
oder UseCors
erfordert eine manuelle Verknüpfung mit dem Autorisierungssystem.Ein Endpunkt definiert beides:
Terminalmiddleware kann sehr nützlich sein, erfordert aber möglicherweise auch:
Ziehen Sie daher zunächst die Integration von Routingfunktionen in Betracht, bevor Sie damit beginnen, Terminalmiddleware zu schreiben.
Vorhandene Terminalmiddleware, die in Map oder MapWhen integriert ist, kann in der Regel in einen routingfähigen Endpunkt umgewandelt werden. MapHealthChecks zeigt das Muster für eine Routinglösung:
Map
auf, und stellen Sie die neue Middlewarepipeline bereit.Map
aus der Erweiterungsmethode bereitgestellt wurde.Im folgenden Code ist die Verwendung von MapHealthChecks gezeigt:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
Das vorangehende Beispiel zeigt, warum das Zurückgeben des Generatorobjekts wichtig ist. Wenn das Generatorobjekt zurückgegeben wird, kann der App-Entwickler Richtlinien konfigurieren, z. B. die Autorisierung für den Endpunkt. In diesem Beispiel ist die Middleware für Integritätsprüfungen nicht direkt in das Autorisierungssystem integriert.
Das Metadatensystem wurde als Antwort auf die Probleme erstellt, die von Erweiterbarkeitsautoren mithilfe von Terminalmiddleware aufgetreten sind. Für jede Middleware ist es problematisch, deren eigene Integration in das Autorisierungssystem umzusetzen.
Wenn eine Routingmiddleware ausgeführt wird, legt sie ein Endpoint
-Element fest und leitet Werte an eine Anforderungsfunktion in der HttpContext-Klasse der aktuellen Anforderung weiter:
HttpRequest.RouteValues
ruft die Sammlung der Routenwerte ab.Middleware wird nach der Routingmiddleware ausgeführt und kann den Endpunkt erkennen und Maßnahmen ergreifen. So kann beispielsweise eine Autorisierungsmiddleware die Erfassung der Metadaten des Endpunkts für eine Autorisierungsrichtlinie abfragen. Nachdem die gesamte Middleware in der Anforderungsverarbeitungspipeline ausgeführt wurde, wird der Delegat des ausgewählten Endpunkts aufgerufen.
Das Routingsystem ist beim Endpunktrouting für alle Weiterleitungsentscheidungen zuständig. Da die Middleware Richtlinien auf der Grundlage des ausgewählten Endpunkts anwendet, ist Folgendes wichtig:
Warnung
Wenn für Abwärtskompatibilität ein Controller- oder Razor Pages-Endpunktdelegat ausgeführt wird, werden die Eigenschaften von RouteContext.RouteData auf Grundlage der bisher verarbeiteten Anforderungen auf entsprechende Werte festgelegt.
Der Typ RouteContext
wird in einer zukünftigen Version als veraltet markiert:
RouteData.Values
zu HttpRequest.RouteValues
.RouteData.DataTokens
, um IDataTokensMetadata aus den Endpunktmetadaten abzurufen.Der URL-Abgleich erfolgt in mehreren Phasen und kann konfiguriert werden. In jeder Phase werden mehrere Übereinstimmungen ausgegeben. Diese Übereinstimmungen lassen sich in der nächsten Phase weiter eingrenzen. Die Routingimplementierung garantiert keine Verarbeitungsreihenfolge für übereinstimmende Endpunkte. Alle möglichen Übereinstimmungen werden gleichzeitig verarbeitet. Für die URL-Abgleichsphasen gilt folgende Reihenfolge. ASP.NET Core:
Die Liste der Endpunkte wird entsprechend den folgenden Punkten priorisiert:
Alle übereinstimmenden Endpunkte werden in jeder Phase verarbeitet, bis EndpointSelector erreicht ist. EndpointSelector
stellt die abschließende Phase dar. Darin wird der Endpunkt mit der höchsten Priorität aus den Übereinstimmungen als beste Übereinstimmung ausgewählt. Wenn es andere Übereinstimmungen mit derselben Priorität wie die beste Übereinstimmung gibt, wird ein Ausnahmefehler wegen einer nicht eindeutigen Übereinstimmung ausgelöst.
Die Routenpriorität wird anhand einer spezifischeren Routenvorlage berechnet, der eine höhere Priorität eingeräumt wird. Dies wird z. B. anhand der Vorlagen /hello
und /{message}
deutlich:
/hello
überein./hello
ist spezifischer und hat daher höhere Priorität.Im Allgemeinen eignet sich die Routenpriorität gut, um die beste Übereinstimmung für die in der Praxis verwendeten URL-Schemata zu finden. Verwenden Sie Order nur bei Bedarf, um eine Mehrdeutigkeit zu vermeiden.
Aufgrund der Erweiterungsmöglichkeiten, die das Routing bietet, kann das Routingsystem die mehrdeutigen Routen nicht im Voraus berechnen. Betrachten Sie ein Beispiel wie die Routenvorlagen /{message:alpha}
und /{message:int}
:
alpha
-Einschränkung gleicht nur alphabetische Zeichen ab.int
-Einschränkung gleicht nur Zahlen ab.Warnung
Die Reihenfolge der Vorgänge in UseEndpoints wirkt sich nicht auf das Routingverhalten aus, mit einer Ausnahme. MapControllerRoute und MapAreaRoute weisen ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Dadurch wird das Langzeitverhalten von Controllern simuliert, ohne dass das Routingsystem die gleichen Garantien bietet wie ältere Routingimplementierungen.
Endpunktrouting in ASP.NET Core:
Die Routenvorlagenpriorität ist ein System, bei dem jeder Routenvorlage ein Wert zugewiesen wird, je nachdem, wie spezifisch diese ist. Routenvorlagenpriorität:
Dies wird z. B. anhand der Vorlagen /Products/List
und /Products/{id}
deutlich. Es wäre begründet, anzunehmen, dass /Products/List
eine bessere Übereinstimmung als /Products/{id}
für den URL-Pfad /Products/List
ist. Dies funktioniert, weil das Literalsegment /List
eine höhere Priorität als das Parametersegment /{id}
hat.
Wie die Priorisierung im Einzelnen funktioniert, ist an die Definition der Routenvorlagen gekoppelt:
URL-Generierung:
Das Endpunktrouting umfasst die API zur Linkgenerierung (LinkGenerator). LinkGenerator
ist ein Singleton-Dienst, der in DI verfügbar ist. Die LinkGenerator
-API kann außerhalb des Kontexts einer ausgeführten Anforderung verwendet werden. Mvc.IUrlHelper und Szenarios, die von IUrlHelper abhängig sind (z. B. Taghilfsprogramme, HTML-Hilfsprogramme und Aktionsergebnisse), verwenden die LinkGenerator
-API, um entsprechende Funktionen bereitzustellen.
Die API zur Linkgenerierung wird von Konzepten wie Adressen und Adressschemas unterstützt. Sie können mithilfe eines Adressschemas die Endpunkte bestimmen, die bei der Linkgenerierung berücksichtigt werden sollen. Beispielsweise werden Routennamen und Routenwerte als Adressschemas implementiert. Diese Szenarios kennen viele Benutzer von Controllern und Razor Pages.
Die API zur Linkgenerierung kann Controller und Razor Pages über die folgenden Erweiterungsmethoden miteinander verknüpfen:
Beim Überladen dieser Methoden werden Argumente akzeptiert, die den HttpContext
umfassen. Diese Methoden sind zwar in funktionaler Hinsicht äquivalent zu Url.Action und Url.Page, bieten aber zusätzliche Flexibilität und Optionen.
Die GetPath*
-Methoden sind Url.Action
und Url.Page
in der Hinsicht ähnlich, dass sie einen URI mit einem absoluten Pfad generieren. Die GetUri*
-Methoden generieren immer einen absoluten URI mit einem Schema und einem Host. Die Methoden, die einen HttpContext
akzeptieren, generieren im Kontext der ausgeführten Anforderung einen URI. Die Umgebungsroutenwerte, der URL-basierte Pfad, das Schema und der Host von der ausführenden Anforderung werden so lange verwendet, bis sie außer Kraft gesetzt werden.
LinkGenerator wird mit einer Adresse aufgerufen. Ein URI wird in zwei Schritten generiert:
Die von LinkGenerator bereitgestellten Methoden unterstützen die Standardfunktionen zur Generierung von Links für jeden beliebigen Adresstypen. Am praktischsten ist es, die API zur Linkgenerierung mit Erweiterungsmethoden zu verwenden, die Vorgänge für einen bestimmten Adresstypen ausführen:
Erweiterungsmethode | Beschreibung |
---|---|
GetPathByAddress | Generiert einen URI mit einem absoluten Pfad, der auf den angegebenen Werten basiert. |
GetUriByAddress | Generiert einen absoluten URI, der auf den angegebenen Werten basiert. |
Warnung
Beachten Sie die folgenden Implikationen zum Aufrufen von LinkGenerator-Methoden:
Verwenden Sie GetUri*
-Erweiterungsmethoden in App-Konfigurationen, die den Host
-Header von eingehenden Anforderungen nicht überprüfen, mit Bedacht. Wenn der Host
-Header von eingehenden Anforderungen nicht überprüft wird, können nicht vertrauenswürdige Anforderungseingaben zurück an den Client in URIs einer Ansicht bzw. Seite zurückgesendet werden. Es wird empfohlen, dass alle Produktions-Apps ihren Server so konfigurieren, dass der Host
-Header auf bekannte gültige Werte überprüft wird.
Verwenden Sie LinkGenerator in Kombination mit Map
oder MapWhen
in Middleware mit Bedacht. Map*
ändert den Basispfad der ausgeführten Anforderung. Dies beeinflusst die Ausgabe der Linkgenerierung. Für alle LinkGenerator-APIs ist die Angabe eines Basispfads zulässig. Geben Sie einen leeren Basispfad an, um die Auswirkungen von Map*
auf die Linkgenerierung rückgängig zu machen.
Im folgenden Beispiel verwendet eine Middleware die LinkGenerator-API, um eine Verknüpfung zu einer Aktionsmethode herzustellen, die Speicherprodukte aufführt. Sie können für jede beliebige Klasse in einer App die API zur Linkgenerierung verwenden, indem Sie diese in eine Klasse einfügen und GenerateLink
aufrufen:
public class ProductsMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public async Task InvokeAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
var productsPath = _linkGenerator.GetPathByAction("Products", "Store");
await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}
Token in {}
definieren Routenparameter, die beim Abgleich der Route gebunden werden. In einem Routensegment können mehrere Routenparameter definiert werden, müssen aber durch einen Literalwert getrennt werden. Beispiel:
{controller=Home}{action=Index}
ist keine gültige Route, da sich zwischen {controller}
und {action}
kein Literalwert befindet. Routenparameter müssen einen Namen besitzen und können zusätzliche Attribute aufweisen.
Eine Literalzeichenfolge, die nicht den Routenparametern entspricht (z.B. {id}
), muss zusammen mit dem Pfadtrennzeichen /
mit dem URL-Text übereinstimmen. Beim Abgleich von Text wird nicht zwischen Groß-/Kleinbuchstaben unterschieden, und die Übereinstimmung basiert auf der decodierten Repräsentation des URL-Pfads. Damit das Trennzeichen ({
oder }
) der Routenparameter-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein, was einem Escapezeichen entspricht. Beispielsweise {{
oder }}
.
Sternchen *
oder Doppelsternchen **
:
blog/{**slug}
: blog/
beginnt und einem beliebigen Wert folgt.blog/
wird dem Slug-Routenwert zugewiesen.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:
{**slug}"
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.
Durch Catch-All-Parameter können auch leere Zeichenfolgen gefunden werden.
Der Catch-All-Parameter schützt die entsprechenden Zeichen (Escaping), wenn die Route verwendet wird, um eine URL, einschließlich Pfadtrennzeichen (/
) zu generieren. Z.B. generiert die Route foo/{*path}
mit den Routenwerten { path = "my/path" }
foo/my%2Fpath
. Beachten Sie den umgekehrten Schrägstrich mit Escapezeichen. Um Trennzeichen für Roundtrips einsetzen zu können, verwenden Sie das Routenparameterpräfix **
. Die Route foo/{**path}
mit { path = "my/path" }
generiert foo/my/path
.
Bei einem URL-Muster, durch das ein Dateiname mit einer optionalen Erweiterung erfasst werden soll, sind noch weitere Aspekte zu berücksichtigen. Dies wird z.B. anhand der Vorlage files/{filename}.{ext?}
deutlich. Wenn sowohl für filename
als auch für ext
Werte vorhanden sind, werden beide Werte angegeben. Wenn nur für filename
ein Wert in der URL vorhanden ist, wird für die Route eine Übereinstimmung ermittelt, da der nachstehende Punkt (.
) optional ist. Für die folgenden URLs wird eine Übereinstimmung für die Route ermittelt:
/files/myFile.txt
/files/myFile
Routenparameter können über mehrere Standardwerte verfügen, die nach dem Parameternamen angegeben werden und durch ein Gleichheitszeichen (=
) voneinander getrennt werden. Mit {controller=Home}
wird beispielsweise Home
als Standardwert für controller
definiert. Der Standardwert wird verwendet, wenn kein Wert in der Parameter-URL vorhanden ist. Routenparameter sind optional, wenn am Ende des Parameternamens ein Fragezeichen (?
) angefügt wird. Beispielsweise id?
. Zwischen optionalen Werten und Standardroutenparametern besteht folgender Unterschied:
Routenparameter können Einschränkungen aufweisen, die mit dem gebundenen Routenwert der URL übereinstimmen müssen. Eine Inline-Einschränkung für einen Routenparameter geben Sie an, indem Sie hinter dem Namen des Routenparameters einen Doppelpunkt (:
) und einen Einschränkungsnamen hinzufügen. Wenn für die Einschränkung Argumente erforderlich sind, werden diese nach dem Einschränkungsnamen in Klammern ((...)
) eingeschlossen. Mehrere Inline-Einschränkungen können festgelegt werden, indem ein weiterer Doppelpunkt (:
) und Einschränkungsname hinzugefügt werden.
Der Einschränkungsname und die Argumente werden dem IInlineConstraintResolver-Dienst übergeben, wodurch eine Instanz von IRouteConstraint für die URL-Verarbeitung erstellt werden kann. In der Routenvorlage blog/{article:minlength(10)}
wird beispielsweise die Einschränkung minlength
mit dem Argument 10
festgelegt. Weitere Informationen zu Routeneinschränkungen und eine Liste der vom Framework bereitgestellten Einschränkungen finden Sie im Abschnitt Routeneinschränkungen.
Routenparameter können darüber hinaus über Parametertransformatoren verfügen. Diese wandeln den Wert eines Parameters beim Generieren von Links um und passen Aktionen und Seiten an URLs an. Wie Einschränkungen können auch Parametertransformatoren einem Routenparameter inline hinzugefügt werden, indem ein Doppelpunkt (:
) und der Name des Transformators hinter dem Namen des Routenparameters hinzugefügt werden. In der Routenvorlage blog/{article:slugify}
wird beispielsweise der Transformator slugify
festgelegt. Weitere Informationen zu Parametertransformatoren finden Sie im Abschnitt Parametertransformatoren.
Die folgende Tabelle enthält Beispielvorlagen für Routen und deren Verhalten:
Routenvorlage | Beispiel-URI für Übereinstimmung | Der Anforderungs-URI |
---|---|---|
hello |
/hello |
Nur für den Pfad /hello wird eine Übereinstimmung ermittelt. |
{Page=Home} |
/ |
Eine Übereinstimmung wird ermittelt, und Page wird auf Home festgelegt. |
{Page=Home} |
/Contact |
Eine Übereinstimmung wird ermittelt, und Page wird auf Contact festgelegt. |
{controller}/{action}/{id?} |
/Products/List |
Stimmt mit dem Products -Controller und der List -Aktion überein. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Wird dem Controller Products und der Aktion Details zugeordnet, bei der id auf 123 festgelegt ist. |
{controller=Home}/{action=Index}/{id?} |
/ |
Stimmt mit dem Home -Controller und der Index -Methode überein. id wird ignoriert. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Stimmt mit dem Products -Controller und der Index -Methode überein. id wird ignoriert. |
Mit Vorlagen lässt sich Routing besonders leicht durchführen. Einschränkungen und Standardwerte können auch außerhalb der Routenvorlage angegeben werden.
Komplexe Segmente werden von rechts nach links auf eine nicht gierige Weise durch entsprechende Literaltrennzeichen verarbeitet. Beispielsweise ist [Route("/a{b}c{d}")]
ein komplexes Segment.
Komplexe Segmente funktionieren auf eine bestimmte Weise, die für eine erfolgreiche Verwendung verstanden werden muss. Das Beispiel in diesem Abschnitt zeigt, warum komplexe Segmente nur dann wirklich gut funktionieren, wenn der Trennzeichentext nicht innerhalb der Parameterwerte erscheint. Für komplexere Fälle ist die Verwendung eines RegEx und das anschließende manuelle Extrahieren der Werte erforderlich.
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.
Dies ist eine Zusammenfassung der Schritte, die beim Routing mit der Vorlage /a{b}c{d}
und dem URL-Pfad /abcd
ausgeführt werden. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen:
c
. Daher wird /abcd
von rechts durchsucht und /ab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /ab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.Nachfolgend ist ein Beispiel für einen negativen Fall mit derselben Vorlage /a{b}c{d}
und dem URL-Pfad /aabcd
. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen: Bei diesem Fall handelt es sich nicht um eine Übereinstimmung, was durch denselben Algorithmus belegt wird:
c
. Daher wird /aabcd
von rechts durchsucht und /aab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /aab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /a|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.a
, aber es gibt keine Routenvorlage mehr, die der Algorithmus analysieren kann, weshalb dies keine Übereinstimmung ist.Da der übereinstimmende Algorithmus nicht gierig ist:
Reguläre Ausdrücke bieten eine viel bessere Kontrolle über das Abgleichsverhalten.
Beim gierigen Abgleich, auch als Lazy Matching bezeichnet, wird die größtmögliche Zeichenfolge abgeglichen. Beim nicht gierigen Abgleich ist dies die kürzeste Zeichenfolge.
Ein Routing mit Sonderzeichen kann zu unerwarteten Ergebnissen führen. Stellen Sie sich z. B. einen Controller mit der folgenden Aktionsmethode vor:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Wenn string id
die folgenden codierten Werte enthält, können unerwartete Ergebnisse auftreten:
ASCII | Codiert |
---|---|
/ |
%2F |
|
+ |
Routenparameter sind nicht immer URL-decodiert. Dieses Problem wird möglicherweise in Zukunft behoben. Weitere Informationen finden Sie in diesem GitHub-Issue.
Routeneinschränkungen werden angewendet, wenn eine Übereinstimmung mit der eingehenden URL gefunden wurde und der URL-Pfad in Routenwerten mit Token versehen wird. In der Regel wird mit Routeneinschränkungen der Routenwert der zugehörigen Vorlage geprüft. Dabei wird anhand einer True/False-Entscheidung bestimmt, ob der Wert gültig ist. Für einige Routeneinschränkungen werden anstelle des Routenwerts andere Daten verwendet, um zu ermitteln, ob das Routing einer Anforderung möglich ist. HttpMethodRouteConstraint kann beispielsweise auf der Grundlage des HTTP-Verbs eine Anforderung entweder annehmen oder ablehnen. Einschränkungen werden in Routinganforderungen und bei der Linkgenerierung verwendet.
Warnung
Verwenden Sie keine Einschränkungen für die Eingabeüberprüfung. Wenn Einschränkungen für die Eingabevalidierung verwendet werden, führt eine ungültige Eingabe zu einem 404
-Fehler (Nicht gefunden). Eine ungültige Eingabe sollte zu einer ungültigen Anforderung (400
) mit einer entsprechenden Fehlermeldung führen. Verwenden Sie Routeneinschränkungen nicht, um Eingaben für eine bestimmte Route zu überprüfen, sondern um ähnliche Routen zu unterscheiden.
In der folgenden Tabelle werden Beispiele für Routeneinschränkungen und deren zu erwartendes Verhalten beschrieben:
Einschränkung | Beispiel | Beispiele für Übereinstimmungen | Hinweise |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Für jeden Integer wird eine Übereinstimmung ermittelt. |
bool |
{active:bool} |
true , FALSE |
Entspricht true oder false . Groß-/Kleinschreibung nicht beachten |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Entspricht einem gültigen DateTime -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Entspricht einem gültigen decimal -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen double -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen float -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Für einen gültigen Guid -Wert wird eine Übereinstimmung ermittelt. |
long |
{ticks:long} |
123456789 , -123456789 |
Für einen gültigen long -Wert wird eine Übereinstimmung ermittelt. |
minlength(value) |
{username:minlength(4)} |
Rick |
Die Zeichenfolge muss mindestens eine Länge von 4 Zeichen aufweisen. |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Die Zeichenfolge darf maximal eine Länge von 8 Zeichen aufweisen. |
length(length) |
{filename:length(12)} |
somefile.txt |
Die Zeichenfolge muss genau 12 Zeichen aufweisen. |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Die Zeichenfolge muss mindestens eine Länge von 8 und darf maximal eine Länge von 16 Zeichen aufweisen. |
min(value) |
{age:min(18)} |
19 |
Der Integerwert muss mindestens 18 sein. |
max(value) |
{age:max(120)} |
91 |
Der Integerwert darf nicht größer als 120 sein. |
range(min,max) |
{age:range(18,120)} |
91 |
Der Integerwert muss zwischen 18 und 120 liegen. |
alpha |
{name:alpha} |
Rick |
Die Zeichenfolge muss aus mindestens einem alphabetische Zeichen bestehen, a -z und ohne Unterscheidung zwischen Groß-/Kleinbuchstaben. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Die Zeichenfolge muss mit dem regulären Ausdruck übereinstimmen. Weitere Informationen finden Sie unter Tipps zum Definieren eines regulären Ausdrucks. |
required |
{name:required} |
Rick |
Hierdurch wird erzwungen, dass ein Wert, der kein Parameter ist, für die URL-Generierung vorhanden sein muss. |
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.
Auf einen einzelnen Parameter können mehrere durch Doppelpunkte getrennte Einschränkungen angewendet werden. Durch die folgende Einschränkung wird ein Parameter beispielsweise auf einen Integerwert größer oder gleich 1 beschränkt:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warnung
Für Routeneinschränkungen, mit denen die URL überprüft wird und die in den CLR-Typ umgewandelt werden, wird immer die invariante Kultur verwendet. Dies gilt z. B. für die Konvertierung in den CLR-Typ int
oder DateTime
. Diese Einschränkungen setzen voraus, dass die URL nicht lokalisierbar ist. Die vom Framework bereitgestellten Routeneinschränkungen ändern nicht die Werte, die in Routenwerten gespeichert sind. Alle Routenwerte, die aus der URL analysiert werden, werden als Zeichenfolgen gespeichert. Durch die float
-Einschränkung wird beispielsweise versucht, den Routenwert in einen Gleitkommawert zu konvertieren. Mit dem konvertierten Wert wird allerdings nur überprüft, ob eine Umwandlung überhaupt möglich ist.
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.
Reguläre Ausdrücke können mithilfe der regex(...)
-Routeneinschränkung als Inline-Einschränkungen angegeben werden. Methoden der MapControllerRoute-Familie akzeptieren auch ein Objektliteral von Einschränkungen. Wenn dieses Formular verwendet wird, werden Zeichenfolgenwerte als reguläre Ausdrücke interpretiert.
Der folgende Code verwendet eine Inline-RegEx-Einschränkung:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
Der folgende Code verwendet ein Objektliteral, um eine RegEx-Einschränkung anzugeben:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
Im ASP.NET Core-Framework wird dem Konstruktor für reguläre Ausdrücke RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
hinzugefügt. Eine Beschreibung dieser Member finden Sie unter RegexOptions.
In regulären Ausdrücken werden Trennzeichen und Token verwendet, die auch beim Routing und in der Programmiersprache C# in ähnlicher Weise verwendet werden. Token, die reguläre Ausdrücke enthalten, müssen mit einem Escapezeichen versehen werden. Wenn Sie den regulären Ausdruck ^\d{3}-\d{2}-\d{4}$
in einer Inline-Einschränkung verwenden möchten, nutzen Sie eine der folgenden Optionen:
\
-Zeichen in der Zeichenfolge durch \\
-Zeichen in der C#-Quelldatei, um das Escapezeichen für die Zeichenfolge \
zu setzen.Wenn Sie Trennzeichen für Routenparameter mit Escapezeichen versehen möchten ({
, }
, [
, ]
), geben Sie jedes Zeichen im Ausdruck doppelt ein (z. B. {{
, }}
, [[
, ]]
). In der folgenden Tabelle werden reguläre Ausdrücke und Ausdrücke aufgeführt, die mit Escapezeichen versehen sind:
Regulärer Ausdruck | Mit Escapezeichen versehener regulärer Ausdruck |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Beim Routing verwendete reguläre Ausdrücke beginnen oft mit dem ^
-Zeichen und stellen die Startposition der Zeichenfolge dar. Die Ausdrücke enden häufig mit einem Dollarzeichen ($
) und stellen das Ende der Zeichenfolge dar. Mit den Zeichen ^
und $
wird sichergestellt, dass der reguläre Ausdruck mit dem vollständigen Routenparameterwert übereinstimmt. Ohne die Zeichen ^
und $
werden mit dem regulären Ausdruck alle Teilzeichenfolgen ermittelt, was häufig nicht gewünscht ist. In der folgenden Tabelle finden Sie Beispiele für reguläre Ausdrücke. Außerdem wird erklärt, warum ein Abgleich erfolgreich ist oder fehlschlägt:
expression | Zeichenfolge | Match | Kommentar |
---|---|---|---|
[a-z]{2} |
hello | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
123abc456 | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
mz | Ja | Ausdruck stimmt überein |
[a-z]{2} |
MZ | Ja | keine Unterscheidung zwischen Groß-/Kleinbuchstaben |
^[a-z]{2}$ |
hello | Nein | siehe Erläuterungen zu ^ und $ oben |
^[a-z]{2}$ |
123abc456 | Nein | siehe Erläuterungen zu ^ und $ oben |
Weitere Informationen zur Syntax von regulären Ausdrücken finden Sie unter Sprachelemente für reguläre Ausdrücke – Kurzübersicht.
Einen regulären Ausdruck können Sie verwenden, um einen Parameter auf zulässige Werte einzuschränken. Mit {action:regex(^(list|get|create)$)}
werden beispielsweise für den action
-Routenwert nur die Werte list
, get
oder create
abgeglichen. Wenn die Zeichenfolge ^(list|get|create)$
dem Einschränkungswörterbuch übergeben wird, führt dies zum gleichen Ergebnis. Auch Einschränkungen, die dem zugehörigen Wörterbuch hinzugefügt werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden als reguläre Ausdrücke behandelt. Einschränkungen, die innerhalb einer Vorlage übergeben werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden nicht als reguläre Ausdrücke behandelt.
Benutzerdefinierte Routeneinschränkungen können durch Implementierung der IRouteConstraint-Schnittstelle erstellt werden. Die IRouteConstraint
-Schnittstelle umfasst die Match-Methode, die true
zurückgibt, wenn die Einschränkung erfüllt wird, und andernfalls false
.
Benutzerdefinierte Routeneinschränkungen werden nur selten benötigt. Bevor Sie eine benutzerdefinierte Routeneinschränkung implementieren, sollten Sie Alternativen in Betracht ziehen, wie z. B. Modellbindung.
Der ASP.NET Core-Ordner Constraints bietet nützliche Beispiele für die Erstellung von Einschränkungen. Beispiel: GuidRouteConstraint.
Zum Verwenden eines benutzerdefinierten IRouteConstraint
-Elements muss der Routeneinschränkungstyp bei der ConstraintMap-Eigenschaft der App im Dienstcontainer registriert werden. Eine ConstraintMap
ist ein Wörterbuch, das Routeneinschränkungsschlüssel IRouteConstraint
-Implementierungen zuordnet, die diese Einschränkungen überprüfen. Die ConstraintMap
einer App kann in Program.cs
entweder als Teil eines AddRouting-Aufrufs oder durch direktes Konfigurieren von RouteOptions mit builder.Services.Configure<RouteOptions>
aktualisiert werden. Beispiel:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
Die vorangehende Einschränkung wird im folgenden Code angewendet:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
Die Implementierung von NoZeroesRouteConstraint
verhindert die Verwendung von 0
in einem Routenparameter:
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
public bool Match(
HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
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.
Der vorangehende Code:
0
im {id}
-Segment der Route vorhanden ist.Der folgende Code bietet einen besseren Ansatz, um zu verhindern, dass eine id
mit einer 0
verarbeitet wird:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
Der vorangehende Code bietet im Vergleich zum NoZeroesRouteConstraint
-Ansatz folgende Vorteile:
0
enthält.Parametertransformatoren:
Beispielsweise generiert ein benutzerdefinierter Parametertransformator slugify
im Routenmuster blog\{article:slugify}
mit Url.Action(new { article = "MyTestArticle" })
blog\my-test-article
.
Betrachten Sie die folgende Implementierung von IOutboundParameterTransformer
:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Um einen Parametertransformator in einem Routenmuster zu verwenden, konfigurieren Sie ihn mit ConstraintMap in Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
Das ASP.NET Core-Framework verwendet Parametertransformatoren, um den URI zu transformieren, zu dem ein Endpunkt aufgelöst wird. Beispielsweise wandeln Parametertransformatoren die Routenwerte um, die zum Zuordnen folgender Elemente verwendet werden: area
, controller
, action
und page
.
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Mit der vorstehenden Routenvorlage wird die Aktion SubscriptionManagementController.GetAll
dem URI /subscription-management/get-all
zugeordnet. Ein Parametertransformator ändert nicht die zum Generieren eines Links verwendeten Routenwerte. Beispielsweise gibt Url.Action("GetAll", "SubscriptionManagement")
/subscription-management/get-all
aus.
ASP.NET Core bietet API-Konventionen für die Verwendung von Parametertransformatoren mit generierten Routen:
Dieser Abschnitt enthält eine Referenz für den Algorithmus, der durch die URL-Generierung implementiert wird. In der Praxis werden bei den meisten komplexen Beispielen für die URL-Generierung Controller oder Razor Pages verwendet. Weitere Informationen finden Sie unter Routing in Controllern.
Die URL-Generierung beginnt mit einem Aufruf von LinkGenerator.GetPathByAddress oder einer ähnlichen Methode. Die Methode wird mit einer Adresse, mehreren Routenwerten und optional mit Informationen zur aktuellen Anforderung von HttpContext
versehen.
Im ersten Schritt wird die Adresse verwendet, um bestimmte Endpunktkandidaten mithilfe einer IEndpointAddressScheme<TAddress>-Schnittstelle aufzulösen, die dem Adresstyp entspricht.
Sobald eine Kandidatengruppe anhand des Adressschemas gefunden wurde, werden die Endpunkte geordnet und iterativ verarbeitet, bis die URL-Generierung erfolgreich abgeschlossen ist. Bei der URL-Generierung wird nicht auf Mehrdeutigkeiten geprüft, daher ist das erste zurückgegebene Ergebnis das Endergebnis.
Der erste Schritt bei der Behebung von Problemen bei der URL-Generierung ist die Einstellung des Protokolliergrads von Microsoft.AspNetCore.Routing
auf TRACE
. LinkGenerator
protokolliert viele Details über die Verarbeitung, die bei der Problembehebung nützlich sein können.
Ausführliche Informationen zur URL-Generierung finden Sie unter Referenz für URL-Generierung.
Mithilfe von Adressen wird bei der URL-Generierung ein Aufruf in der API zur Linkgenerierung an mehrere Endpunktkandidaten gebunden.
Adressen sind ein erweiterbares Konzept, das standardmäßig mit zwei Implementierungen bereitgestellt wird:
string
) als Adresse: IUrlHelper
, Taghilfsprogrammen, HTML-Hilfsprogrammen, Aktionsergebnissen usw. verwendet wird.Aufgabe des Adressschemas ist es, die Verbindung zwischen der Adresse und den übereinstimmenden Endpunkten anhand von beliebigen Kriterien herzustellen:
Aus der aktuellen Anforderung greift das Routing auf die Routenwerte der aktuellen Anforderung HttpContext.Request.RouteValues
zu. Die mit der aktuellen Anforderung verbundenen Werte werden als Umgebungswerte bezeichnet. Aus Gründen der Übersichtlichkeit werden in der Dokumentation die an die Methoden übergebenen Routenwerte als explizite Werte bezeichnet.
Das folgende Beispiel zeigt Umgebungswerte und explizite Werte. Er liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte:
public class WidgetController : ControllerBase
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public IActionResult Index()
{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;
return Content(indexPath);
}
// ...
Der vorangehende Code:
/Widget/Index/17
zurück.Der folgende Code liefert nur explizite Werte und keine Umgebungswerte:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
Die vorhergehende Methode gibt /Home/Subscribe/17
zurück.
Der folgende Code in WidgetController
gibt /Widget/Subscribe/17
zurück:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
Der folgende Code stellt den Controller aus den Umgebungswerten in der aktuellen Anforderung und explizite Werte dar:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
Für den Code oben gilt:
/Gadget/Edit/17
wird zurückgegeben.action
-Namen und route
-Werte.Sie liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
Im vorangehenden Code wird url
auf /Edit/17
festgelegt, wenn die Option zum Bearbeiten der Razor Page die folgende Seitenanweisung enthält:
@page "{id:int}"
Wenn die Routenvorlage "{id:int}"
nicht in der Seite „Bearbeiten“ enthalten ist, ist url
gleich /Edit?id=17
.
Das Verhalten der IUrlHelper-Schnittstelle von MVC fügt zusätzlich zu den hier beschriebenen Regeln eine weitere Komplexitätsebene hinzu:
IUrlHelper
liefert immer die Routenwerte aus der aktuellen Anforderung als Umgebungswerte.action
und controller
als explizite Werte, sofern sie nicht vom Entwickler außer Kraft gesetzt werden.page
als expliziten Wert, sofern er nicht außer Kraft gesetzt wird. IUrlHelper.Page
setzt immer den aktuellen Routenwert handler
mit null
als expliziten Wert außer Kraft, sofern er nicht außer Kraft gesetzt wird.Benutzer sind oft von den Verhaltensdetails der Umgebungswerte überrascht, da MVC anscheinend nicht den eigenen Regeln folgt. Aus Verlaufs- und Kompatibilitätsgründen weisen bestimmte Routenwerte wie action
, controller
, page
und handler
ein spezielles Verhalten auf.
Die äquivalente Funktionalität, die durch LinkGenerator.GetPathByAction
und LinkGenerator.GetPathByPage
bereitgestellt wird, verdoppelt diese Anomalien von IUrlHelper
aus Kompatibilitätsgründen.
Sobald die Gruppe der Endpunktkandidaten ermittelt ist, wird der URL-Generierungsalgorithmus angewendet:
Der erste Schritt in diesem Prozess wird als Routenwertinvalidierung bezeichnet. Die Routenwertinvalidierung ist der Prozess, bei dem das Routing entscheidet, welche Routenwerte aus den Umgebungswerten verwendet und welche ignoriert werden sollen. Jeder Umgebungswert wird berücksichtigt und entweder mit den expliziten Werten kombiniert oder aber ignoriert.
Denken Sie daran, dass Umgebungswerte Anwendungsentwicklern in allgemeinen Fällen das Schreiben von Code sparen können. In der Regel sind die Szenarios, in denen Umgebungswerte hilfreich sind, mit MVC verknüpft:
Aufrufe an LinkGenerator
oder IUrlHelper
, die null
zurückgeben, sind meist dadurch bedingt, dass die Routenwertinvalidierung nicht verstanden wurde. Beheben Sie die Routenwertinvalidierung, indem Sie explizit mehr Routenwerte angeben, um zu prüfen, ob das Problem dadurch gelöst wird.
Bei der Routenwertinvalidierung wird davon ausgegangen, dass das URL-Schema der Anwendung hierarchisch ist, mit einer von links nach rechts gebildeten Hierarchie. Sehen Sie sich die einfache Controllerroutenvorlage {controller}/{action}/{id?}
an, um ein Gespür dafür zu bekommen, wie dies in der Praxis funktioniert. Durch eine Änderung auf einen Wert werden alle rechts angezeigten Routenwerte ungültig. Dies spricht für die These von der Hierarchie. Wenn die App einen Umgebungswert für id
hat und der Vorgang einen anderen Wert für controller
angibt:
id
wird nicht wiederverwendet, weil {controller}
links von {id?}
steht.Einige Beispiele veranschaulichen dieses Prinzip:
id
enthalten, wird der Umgebungswert für id
ignoriert. Die Umgebungswerte für controller
und action
können verwendet werden.action
enthalten, wird jeder Umgebungswert für action
ignoriert. Die Umgebungswerte für controller
können verwendet werden. Wenn sich der explizite Wert für action
von dem Umgebungswert für action
unterscheidet, wird der Wert id
nicht verwendet. Wenn der explizite Wert für action
mit dem Umgebungswert für action
übereinstimmt, kann der Wert id
verwendet werden.controller
enthalten, wird jeder Umgebungswert für controller
ignoriert. Wenn sich der explizite Wert für controller
von dem Umgebungswert für controller
unterscheidet, werden die Werte action
und id
nicht verwendet. Wenn der explizite Wert für controller
mit dem Umgebungswert für controller
übereinstimmt, können die Werte action
und id
verwendet werden.Dieser Prozess wird zusätzlich durch die vorhandenen Attributrouten und dedizierten konventionellen Routen erschwert. Konventionelle Routen des Controllers wie {controller}/{action}/{id?}
legen eine Hierarchie mithilfe von Routenparametern fest. Bei bestimmten konventionellen Routen und Attributrouten zu Controllern und Razor Pages:
Für diese Fälle definiert die URL-Generierung das Konzept der erforderlichen Werte. Bei Endpunkten, die von Controllern und Razor Pages erstellt wurden, sind erforderliche Werte angegeben, die eine Routenwertinvalidierung ermöglichen.
Der Algorithmus der Routenwertinvalidierung im Detail:
An diesem Punkt ist der Vorgang zur URL-Generierung bereit, Routeneinschränkungen auszuwerten. Die akzeptierten Werte werden mit den Standardwerten der Parameter kombiniert, die für Einschränkungen bereitgestellt werden. Wenn alle Einschränkungen erfüllt sind, wird der Vorgang fortgesetzt.
Als Nächstes können die akzeptierten Werte verwendet werden, um die Routenvorlage zu erweitern. Die Routenvorlage wird verarbeitet:
Explizit bereitgestellte Werte, für die keine Übereinstimmungen mit einem Routensegment ermittelt werden, werden der Abfragezeichenfolge hinzugefügt. In der folgenden Tabelle werden die Ergebnisse dargestellt, die aus der Verwendung der Routenvorlage {controller}/{action}/{id?}
hervorgehen:
Umgebungswerte | Explizite Werte | Ergebnis |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Der folgende Code zeigt ein Beispiel für ein Schema zur URL-Generierung, das vom Routing nicht unterstützt wird:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
Im vorangehenden Code wird der culture
-Routenparameter für die Lokalisierung verwendet. Ziel ist es, dass der culture
-Parameter immer als Umgebungswert akzeptiert wird. Der culture
-Parameter wird jedoch aufgrund der Art und Weise, wie die erforderlichen Werte funktionieren, nicht als Umgebungswert akzeptiert:
"default"
-Routenvorlage befindet sich der culture
-Routenparameter links von controller
, sodass controller
durch Änderungen an culture
nicht ungültig wird."blog"
-Routenvorlage wird der culture
-Routenparameter rechts von controller
betrachtet, der in den erforderlichen Werten aufgeführt ist.Die LinkParser Klasse fügt die Möglichkeit hinzu, einen URL-Pfads in einen Satz von Routenwerten zu zerlegen. Die ParsePathByEndpointName Methode verwendet einen Endpunktnamen und einen URL-Pfad und gibt einen Satz von aus dem URL-Pfad extrahierten Routenwerten zurück.
Im folgenden Beispielcontroller verwendet die GetProduct
Aktion eine Routenvorlage von api/Products/{id}
und hat eine Name von GetProduct
:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
In derselben Controllerklasse erwartet die AddRelatedProduct
Aktion einen URL-Pfad, pathToRelatedProduct
, der als Abfragezeichenfolgen-Parameter bereitgestellt werden kann:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
Im vorherigen Beispiel extrahiert die AddRelatedProduct
Aktion den id
Routenwert aus dem URL-Pfad. Beispielsweise wird der /api/Products/1
Wert bei einem relatedProductId
URL-Pfad auf 1
festgelegt. Dieser Ansatz ermöglicht es den Clients der API, URL-Pfade beim Verweisen auf Ressourcen zu verwenden, ohne wissen zu müssen, wie eine solche URL strukturiert ist.
Die folgenden Links enthalten Informationen zum Konfigurieren von Endpunktmetadaten:
[MinimumAgeAuthorize]
-AttributRequireHost wendet eine Einschränkung auf die Route an, für die der angegebene Host erforderlich ist. Der Parameter RequireHost
oder [Host] kann wie folgt lauten:
www.domain.com
, entspricht www.domain.com
mit einem beliebigen Port.*.domain.com
, entspricht www.domain.com
, subdomain.domain.com
oder www.subdomain.domain.com
an einem beliebigen Port.*:5000
, entspricht Port 5000 mit einem beliebigen Host.www.domain.com:5000
oder *.domain.com:5000
, entspricht dem Host und Port.Es können mehrere Parameter mit RequireHost
oder [Host]
angegeben werden. Die Einschränkung gleicht die Hosts ab, die für einen der Parameter gültig sind. Beispielsweise entspricht [Host("domain.com", "*.domain.com")]
domain.com
, www.domain.com
und subdomain.domain.com
.
Im folgenden Code wird RequireHost
verwendet, um den angegebenen Host auf der Route anzufordern:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
Im folgenden Code wird das [Host]
-Attribut für den Controller verwendet, um die einzelnen angegebenen Hosts anzufordern:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Wenn das [Host]
-Attribut sowohl auf die Controller- als auch auf die Aktionsmethode angewendet wird, trifft Folgendes zu:
Wenn eine App Leistungsprobleme hat, wird die Ursache häufig beim Routing vermutet. Das Routing wird deshalb in Betracht gezogen, weil Frameworks wie Controller und Razor Pages in ihren Protokollierungsmeldungen die innerhalb des Frameworks verbrachte Zeit angeben. Wenn es einen signifikanten Unterschied zwischen der von den Controllern gemeldeten Zeit und der Gesamtzeit der Anforderung gibt:
Die Leistung des Routings wird anhand von Tausenden von Endpunkten getestet. Es ist unwahrscheinlich, dass eine typische App auf ein Leistungsproblem stößt, nur weil diese zu umfangreich ist. Die häufigste Ursache für eine langsames Routing ist üblicherweise eine schlecht funktionierende benutzerdefinierte Middleware.
Das folgende Codebeispiel veranschaulicht eine grundlegende Technik zur Eingrenzung der Verzögerungsquelle:
var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.MapGet("/", () => "Timing Test.");
Auf das Zeitrouting:
Dies ist ein einfacher Weg, um die Verzögerung zu verringern, wenn sie signifikant ist, zum Beispiel größer als 10ms
. Wenn Time 2
von Time 1
subtrahiert wird, ergibt sich die in der UseRouting
-Middleware benötigte Zeit.
Der folgende Code verwendet einen kompakteren Ansatz als der vorangegangene Zeitcode:
public sealed class AutoStopwatch : IDisposable
{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());
public void Dispose()
{
if (_disposed)
{
return;
}
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");
Die folgende Liste gibt einen Einblick in Routingfeatures, die im Vergleich zu einfachen Routenvorlagen relativ teuer sind:
{x}-{y}-{z}
): ASP.NET Core verwendet standardmäßig einen Routingalgorithmus, bei dem Arbeitsspeicher zugunsten von CPU-Zeit geopfert wird. Dies hat den angenehmen Effekt, dass die Zeit für den Routenabgleich nur von der Länge des abzugleichenden Pfads und nicht von der Routenanzahl abhängt. Dieser Ansatz kann jedoch in einigen Fällen problematisch sein, etwa dann, wenn die App eine große Anzahl von Routen umfasst (mehrere Tausend) und die Routen eine große Anzahl variabler Präfixe enthalten. Beispiel: Die Routen weisen Parameter in frühen Segmenten der Route auf, etwa {parameter}/some/literal
.
Es ist unwahrscheinlich, dass eine App in eine Situation gerät, in der dies ein Problem darstellt, es sei denn:
Microsoft.AspNetCore.Routing.Matching.DfaNode
-Instanzen.Es gibt verschiedene Techniken und Optimierungen, die auf Routen angewendet werden können, um dieses Szenario weitgehend zu vermeiden:
{parameter:int}
, {parameter:guid}
, {parameter:regex(\\d+)}
.
MapDynamicControllerRoute
und MapDynamicPageRoute
erreicht werden.Dieser Abschnitt enthält Hinweise für Bibliotheksautoren, die auf dem Routing aufbauen. Diese Details sollen sicherstellen, dass App-Entwickler gute Erfahrungen mit Bibliotheken und Frameworks machen, die das Routing erweitern.
Wenn Sie ein Framework erstellen möchten, das das Routing für den URL-Abgleich verwendet, sollten Sie zunächst eine Benutzeroberfläche definieren, die auf UseEndpoints aufbaut.
Es wird empfohlen, dass Sie auf IEndpointRouteBuilder aufbauen. Auf diese Weise können Benutzer Ihr Framework mit anderen ASP.NET Core-Features problemlos zusammenstellen. Jede ASP.NET Core-Vorlage umfasst die Routingfunktionalität. Gehen Sie davon aus, dass das Routing vorhanden und den Benutzern vertraut ist.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
Es wird empfohlen, dass Sie einen versiegelten konkreten Typ aus einem Aufruf an MapMyFramework(...)
zurückgeben, der IEndpointConventionBuilder implementiert. Die meisten Map...
-Methoden in Frameworks folgen diesem Muster. Die IEndpointConventionBuilder
-Schnittstelle:
Wenn Sie Ihren eigenen Typ deklarieren, können Sie dem Generator Ihre eigene frameworkspezifische Funktionalität hinzufügen. Es ist in Ordnung, einen vom Framework deklarierten Generator zu umschließen und Aufrufe an ihn weiterzuleiten.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
Ziehen Sie in Betracht, Ihre eigene EndpointDataSource-Klasse zu schreiben. Die EndpointDataSource
-Klasse vom primitiven Typ auf niedriger Ebene eignet sich zum Deklarieren und Aktualisieren einer Sammlung von Endpunkten. EndpointDataSource
ist eine leistungsstarke API, die von Controllern und Razor Pages verwendet wird.
Die Routingtests haben ein grundlegendes Beispiel für eine Datenquelle, die nicht aktualisiert wird.
Versuchen Sie nicht, eine EndpointDataSource
-Klasse standardmäßig zu registrieren. Fordern Sie die Benutzer auf, Ihr Framework in UseEndpoints zu registrieren. Der Grundgedanke beim Routing ist, dass standardmäßig nichts enthalten ist, und dass die Endpunkte bei UseEndpoints
registriert werden müssen.
Ziehen Sie in Betracht, Metadatentypen als Schnittstelle zu definieren.
Ermöglichen Sie, Metadatentypen als Attribut für Klassen und Methoden zu verwenden.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks wie Controller und Razor Pages unterstützen die Anwendung von Metadatenattributen auf Typen und Methoden. Für das Deklarieren von Metadatentypen gilt:
Durch die Deklaration eines Metadatentyps als Schnittstelle wird die Flexibilität zusätzlich erhöht:
Ermöglichen Sie, Metadaten außer Kraft zu setzen, wie in folgendem Beispiel gezeigt:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
Die beste Möglichkeit, diese Richtlinien zu befolgen, besteht darin, die Definition von Markermetadaten zu vermeiden:
Die Metadatensammlung ist geordnet und unterstützt das Außerkraftsetzen nach Priorität. Im Fall von Controllern sind Metadaten für die Aktionsmethode am spezifischsten.
Nutzen Sie Middleware mit und ohne Routing.
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Ein gutes Beispiel für diese Vorgabe ist die UseAuthorization
-Middleware. Mithilfe der Autorisierungsmiddleware können Sie eine Fallbackrichtlinie einbauen. Die Fallbackrichtlinie gilt, falls angegeben, für beide:
Dies macht die Autorisierungsmiddleware außerhalb des Routingkontexts sehr nützlich. Die Autorisierungsmiddleware kann für die traditionelle Middlewareprogrammierung verwendet werden.
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"
}
}
}
Das Routing wird für das Abgleichen von HTTP-Anforderungen und das Verteilen an ausführbare Endpunkte der App eingesetzt. Endpunkte sind die Einheiten des ausführbaren Codes für die Anforderungsverarbeitung in der App. Endpunkte werden in der App definiert und beim Start der App konfiguriert. Beim Endpunktabgleich können Werte aus der Anforderungs-URL extrahiert und für die Verarbeitung der Anforderung bereitgestellt werden. Mithilfe von Endpunktinformationen aus der App lassen sich durch das Routing URLs generieren, die Endpunkten zugeordnet werden.
Apps können das Routing mit folgenden Funktionen konfigurieren:
In diesem Artikel werden die grundlegenden Details zum ASP.NET Core-Routing beschrieben. Informationen zur Routingkonfiguration finden Sie wie folgt:
Das in diesem Dokument beschriebene Endpunktroutingsystem gilt für ASP.NET Core 3.0 und höher. Weitere Informationen zum vorherigen Routingsystem, das auf IRouter basiert, erhalten Sie, wenn Sie die ASP.NET Core 2.1-Version wie folgt auswählen:
Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Die Downloadbeispiele für dieses Dokument werden durch eine bestimmte Startup
-Klasse aktiviert. Um ein bestimmtes Beispiel auszuführen, ändern Sie Program.cs
so, dass die gewünschte Startup
-Klasse aufgerufen wird.
Alle ASP.NET Core-Vorlagen enthalten die Routingfunktionen im generierten Code. Das Routing wird in in der Startup.Configure
registriert.
Der folgende Code veranschaulicht ein einfaches Beispiel für das Routing:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
Beim Routing wird ein Middlewarepaar verwendet, das durch UseRouting und UseEndpoints registriert wird:
UseRouting
fügt der Middlewarepipeline einen Routenabgleich hinzu. Diese Middleware prüft die in der App definierten Endpunkte und wählt anhand der Anforderung die beste Übereinstimmung aus.UseEndpoints
fügt der Middlewarepipeline die Endpunktausführung hinzu. Dabei wird der mit dem ausgewählten Endpunkt verknüpfte Delegat ausgeführt.Das vorherige Beispiel enthält eine einzelnen Route-zu- Code-Endpunkt unter Verwendung der MapGet-Methode:
GET
-HTTP-Anforderung an die Stamm-URL /
gesendet wird: Hello World!
wird in die HTTP-Antwort geschrieben. Die Stamm-URL /
lautet standardmäßig https://localhost:5001/
.GET
bzw. die Stamm-URL nicht /
ist, gibt es keinen Routenabgleich und es wird ein HTTP-404-Fehler zurückgegeben.Die MapGet
-Methode wird verwendet, um einen Endpunkt zu definieren. Ein Endpunkt kann Folgendes sein:
Endpunkte, die von der App zugeordnet und ausgeführt werden können, sind in UseEndpoints
konfiguriert. Mit MapGet, MapPost und ähnlichen Methoden werden beispielsweise Anforderungsdelegate mit dem Routingsystem verbunden. Zudem können weitere Methoden zur Verbindung von ASP.NET Core-Frameworkfunktionen mit dem Routingsystem verwendet werden:
Das folgende Beispiel zeigt das Routing mit einer anspruchsvolleren Routenvorlage:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello/{name:alpha}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
});
Die Zeichenfolge /hello/{name:alpha}
ist eine Routenvorlage. Sie wird verwendet, um zu konfigurieren, wie der Endpunkt abgeglichen wird. In diesem Fall gleicht die Vorlage Folgendes ab:
/hello/Ryan
/hello/
beginnen, gefolgt von einer Sequenz alphabetischer Zeichen. :alpha
wendet eine Routeneinschränkung an, die nur alphabetische Zeichen abgleicht. Routeneinschränkungen werden weiter unten in diesem Dokument erläutert.Das zweite Segment des URL-Pfads, {name:alpha}
:
name
gebunden.Das in diesem Dokument beschriebene Endpunktroutingsystem gilt ab ASP.NET Core 3.0. Alle Versionen von ASP.NET Core unterstützen jedoch dieselben Routenvorlagenfunktionen und Routeneinschränkungen.
Das folgende Beispiel zeigt das Routing mit Integritätsprüfungen und Autorisierung:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Matches request to an endpoint.
app.UseRouting();
// Endpoint aware middleware.
// Middleware can use metadata from the matched endpoint.
app.UseAuthentication();
app.UseAuthorization();
// Execute the matched endpoint.
app.UseEndpoints(endpoints =>
{
// Configure the Health Check endpoint and require an authorized user.
endpoints.MapHealthChecks("/healthz").RequireAuthorization();
// Configure another endpoint, no authorization requirements.
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
Wenn Sie möchten, dass Codekommentare in anderen Sprachen als Englisch angezeigt werden, informieren Sie uns in diesem GitHub-Issue.
Im vorherigen Beispiel wird veranschaulicht, wie Sie:
Der MapHealthChecks-Aufruf fügt einen Endpunkt für eine Integritätsprüfung hinzu. Durch die Verkettung von RequireAuthorization mit diesem Aufruf wird eine Autorisierungsrichtlinie an den Endpunkt angefügt.
Der Aufruf von UseAuthentication und UseAuthorization wird die Authentifizierungs- und Autorisierungsmiddleware hinzugefügt. Diese Middleware wird zum Ausführen folgender Aktionen zwischen UseRouting und UseEndpoints
platziert:
UseRouting
ausgewählten Endpunkts.Im vorangehenden Beispiel gibt es zwei Endpunkte, aber nur dem für die Integritätsprüfung ist eine Autorisierungsrichtlinie angefügt. Wenn die Anforderung mit dem Endpunkt der Integritätsprüfung, /healthz
, übereinstimmt, wird eine Autorisierungsprüfung durchgeführt. Dadurch wird veranschaulicht, dass Endpunkten zusätzliche Daten zugeordnet werden können. Diese zusätzlichen Daten werden als Metadaten des Endpunkts bezeichnet:
Durch Hinzufügen des effizienten Endpunkt-Konzepts stellt das Routingsystem eine Ergänzung der Middlewarepipeline dar. Endpunkte stehen für Funktionseinheiten der App, die sich in Bezug auf Routing, Autorisierung und die Anzahl der ASP.NET Core-Systeme voneinander unterscheiden.
Ein ASP.NET Core-Endpunkt ist:
Der folgende Code zeigt, wie der Endpunkt, der mit der aktuellen Anforderung übereinstimmt, abgerufen und geprüft werden kann:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.Use(next => context =>
{
var endpoint = context.GetEndpoint();
if (endpoint is null)
{
return Task.CompletedTask;
}
Console.WriteLine($"Endpoint: {endpoint.DisplayName}");
if (endpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine("Endpoint has route pattern: " +
routeEndpoint.RoutePattern.RawText);
}
foreach (var metadata in endpoint.Metadata)
{
Console.WriteLine($"Endpoint has metadata: {metadata}");
}
return Task.CompletedTask;
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
Der Endpunkt, falls ausgewählt, kann aus dem HttpContext
-Element abgerufen werden. Seine Eigenschaften können geprüft werden. Endpunktobjekte sind unveränderlich und können nach der Erstellung nicht mehr geändert werden. Der häufigste Typ des Endpunkts ist eine RouteEndpoint-Klasse. RouteEndpoint
enthält Informationen, die eine Auswahl durch das Routingsystem ermöglichen.
Im vorangehenden Code wird mit app.Use eine Middleware inline konfiguriert.
Der folgende Code zeigt, dass es, je nachdem, wo app.Use
in der Pipeline aufgerufen wird, möglicherweise keinen Endpunkt gibt:
// Location 1: before routing runs, endpoint is always null here
app.Use(next => context =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match
app.Use(next => context =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return next(context);
});
app.UseEndpoints(endpoints =>
{
// Location 3: runs when this endpoint matches
endpoints.MapGet("/", context =>
{
Console.WriteLine(
$"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return Task.CompletedTask;
}).WithDisplayName("Hello");
});
// Location 4: runs after UseEndpoints - will only run if there was no match
app.Use(next => context =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return next(context);
});
In diesem vorangehenden Beispiel werden Console.WriteLine
-Anweisungen hinzugefügt, die anzeigen, ob ein Endpunkt ausgewählt wurde oder nicht. Aus Gründen der Übersichtlichkeit wird in dem Beispiel dem bereitgestellten /
-Endpunkt ein Anzeigename zugewiesen.
Wenn Sie diesen Code mit einer URL /
ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Wenn Sie diesen Code mit einer anderen URL ausführen, wird Folgendes angezeigt:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Diese Ausgabe zeigt Folgendes:
UseRouting
aufgerufen wird.UseRouting
und UseEndpoints ungleich NULL.UseEndpoints
-Middleware ist eine Terminalmiddleware, wenn eine Übereinstimmung gefunden wird. Terminalmiddleware wird weiter unten in diesem Dokument definiert.UseEndpoints
wird nur ausgeführt, wenn keine Übereinstimmung gefunden wird.Die UseRouting
-Middleware verwendet die SetEndpoint-Methode, um den Endpunkt an den aktuellen Kontext anzufügen. Es ist möglich, die UseRouting
-Middleware durch benutzerdefinierte Logik zu ersetzen und dennoch die Vorteile durch die Verwendung von Endpunkten zu nutzen. Endpunkte befinden sich auf niedriger Ebene, wie Middleware, und sind nicht an die Routingimplementierung gekoppelt. Die meisten Apps müssen UseRouting
nicht durch eigene Logik ersetzen.
Die UseEndpoints
-Middleware ist so konzipiert, dass Sie zusammen mit der UseRouting
-Middleware verwendet werden kann. Die Hauptlogik zum Ausführen eines Endpunkts ist nicht kompliziert. Mit GetEndpoint können Sie einen Endpunkt abrufen und dann dessen RequestDelegate-Eigenschaft aufrufen.
Der folgende Code veranschaulicht, wie Middleware das Routing beeinflussen oder darauf reagieren kann:
public class IntegratedMiddlewareStartup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Location 1: Before routing runs. Can influence request before routing runs.
app.UseHttpMethodOverride();
app.UseRouting();
// Location 2: After routing runs. Middleware can match based on metadata.
app.Use(next => context =>
{
var endpoint = context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit
== true)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
return next(context);
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello world!");
});
// Using metadata to configure the audit policy.
endpoints.MapGet("/sensitive", async context =>
{
await context.Response.WriteAsync("sensitive data");
})
.WithMetadata(new AuditPolicyAttribute(needsAudit: true));
});
}
}
public class AuditPolicyAttribute : Attribute
{
public AuditPolicyAttribute(bool needsAudit)
{
NeedsAudit = needsAudit;
}
public bool NeedsAudit { get; }
}
Im vorherigen Beispiel werden zwei wichtige Konzepte dargestellt:
UseRouting
ausgeführt werden, um die Daten zu ändern, auf denen das Routing basiert.
UseRouting
und UseEndpoints ausgeführt werden, um die Ergebnisse des Routings vor der Ausführung des Endpunkts zu verarbeiten.
UseRouting
und UseEndpoints
ausgeführt wird: UseAuthorization
und UseCors
.Der vorangehende Code zeigt ein Beispiel für eine benutzerdefinierte Middleware, die entpunktbezogene Richtlinien unterstützt. Die Middleware schreibt ein Überwachungsprotokoll für den Zugriff auf vertrauliche Daten in der Konsole. Die Middleware kann so konfiguriert werden, dass ein Endpunkt mit den -Metadaten AuditPolicyAttribute
wird. In diesem Beispiel wird ein Opt-In-Muster veranschaulicht, bei dem nur Endpunkte überwacht werden, die als vertraulich markiert sind. Es ist möglich, diese Logik umgekehrt zu definieren, indem beispielsweise alles geprüft wird, was nicht als sicher markiert ist. Das Endpunktmetadaten-System ist flexibel. Diese Logik lässt sich für jeden Anwendungsfall passend schreiben.
Der vorherige Beispielcode soll die grundlegenden Konzepte von Endpunkten veranschaulichen. Das Beispiel ist nicht für Produktionsumgebungen vorgesehen. Eine vollständigere Version einer Middleware für Überwachungsprotokolle würde Folgendes bieten:
Die Metadaten der Überwachungsrichtlinie AuditPolicyAttribute
sind als Attribute
definiert, um die Verwendung mit klassenbasierten Frameworks wie Controllern und SignalR zu erleichtern. Bei Verwendung von Route-zu-Code:
Die bewährten Methoden für Metadatentypen sind, sie entweder als Schnittstellen oder als Attribute zu definieren. Schnittstellen und Attribute ermöglichen die Wiederverwendung von Code. Das Metadatensystem ist flexibel und weist keine Einschränkungen auf.
Das folgende Codebeispiel zeigt den Unterschied zwischen Middleware und Routing:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Approach 1: Writing a terminal middleware.
app.Use(next => async context =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Hello terminal middleware!");
return;
}
await next(context);
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
// Approach 2: Using routing.
endpoints.MapGet("/Movie", async context =>
{
await context.Response.WriteAsync("Hello routing!");
});
});
}
Beim in Approach 1:
gezeigten Stil von Middleware handelt es sich um Terminalmiddleware. Sie wird als Terminalmiddleware bezeichnet, da sie einen Abgleich durchgeführt.
Path == "/"
für die Middleware und Path == "/Movie"
für das Routing.next
-Middleware aufzurufen.Sie wird „Terminal Middleware“ genannt, da sie die Suche beendet, einige Funktionen ausführt und dann zurückkehrt.
Vergleichen von Terminalmiddleware und Routing:
next
aufzurufen.UseAuthorization
und UseCors
.
UseAuthorization
oder UseCors
erfordert eine manuelle Verknüpfung mit dem Autorisierungssystem.Ein Endpunkt definiert beides:
Terminalmiddleware kann sehr nützlich sein, erfordert aber möglicherweise auch:
Ziehen Sie daher zunächst die Integration von Routingfunktionen in Betracht, bevor Sie damit beginnen, Terminalmiddleware zu schreiben.
Vorhandene Terminalmiddleware, die in Map oder MapWhen integriert ist, kann in der Regel in einen routingfähigen Endpunkt umgewandelt werden. MapHealthChecks zeigt das Muster für eine Routinglösung:
Map
auf, und stellen Sie die neue Middlewarepipeline bereit.Map
aus der Erweiterungsmethode bereitgestellt wurde.Im folgenden Code ist die Verwendung von MapHealthChecks gezeigt:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Matches request to an endpoint.
app.UseRouting();
// Endpoint aware middleware.
// Middleware can use metadata from the matched endpoint.
app.UseAuthentication();
app.UseAuthorization();
// Execute the matched endpoint.
app.UseEndpoints(endpoints =>
{
// Configure the Health Check endpoint and require an authorized user.
endpoints.MapHealthChecks("/healthz").RequireAuthorization();
// Configure another endpoint, no authorization requirements.
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
Das vorangehende Beispiel zeigt, warum das Zurückgeben des Generatorobjekts wichtig ist. Wenn das Generatorobjekt zurückgegeben wird, kann der App-Entwickler Richtlinien konfigurieren, z. B. die Autorisierung für den Endpunkt. In diesem Beispiel ist die Middleware für Integritätsprüfungen nicht direkt in das Autorisierungssystem integriert.
Das Metadatensystem wurde als Antwort auf die Probleme erstellt, die von Erweiterbarkeitsautoren mithilfe von Terminalmiddleware aufgetreten sind. Für jede Middleware ist es problematisch, deren eigene Integration in das Autorisierungssystem umzusetzen.
Wenn eine Routingmiddleware ausgeführt wird, legt sie ein Endpoint
-Element fest und leitet Werte an eine Anforderungsfunktion in der HttpContext-Klasse der aktuellen Anforderung weiter:
HttpRequest.RouteValues
ruft die Sammlung der Routenwerte ab.Middleware wird nach der Routingmiddleware ausgeführt und kann den Endpunkt erkennen und Maßnahmen ergreifen. So kann beispielsweise eine Autorisierungsmiddleware die Erfassung der Metadaten des Endpunkts für eine Autorisierungsrichtlinie abfragen. Nachdem die gesamte Middleware in der Anforderungsverarbeitungspipeline ausgeführt wurde, wird der Delegat des ausgewählten Endpunkts aufgerufen.
Das Routingsystem ist beim Endpunktrouting für alle Weiterleitungsentscheidungen zuständig. Da die Middleware Richtlinien auf der Grundlage des ausgewählten Endpunkts anwendet, ist Folgendes wichtig:
Warnung
Wenn für Abwärtskompatibilität ein Controller- oder Razor Pages-Endpunktdelegat ausgeführt wird, werden die Eigenschaften von RouteContext.RouteData auf Grundlage der bisher verarbeiteten Anforderungen auf entsprechende Werte festgelegt.
Der Typ RouteContext
wird in einer zukünftigen Version als veraltet markiert:
RouteData.Values
zu HttpRequest.RouteValues
.RouteData.DataTokens
, um IDataTokensMetadata aus den Endpunktmetadaten abzurufen.Der URL-Abgleich erfolgt in mehreren Phasen und kann konfiguriert werden. In jeder Phase werden mehrere Übereinstimmungen ausgegeben. Diese Übereinstimmungen lassen sich in der nächsten Phase weiter eingrenzen. Die Routingimplementierung garantiert keine Verarbeitungsreihenfolge für übereinstimmende Endpunkte. Alle möglichen Übereinstimmungen werden gleichzeitig verarbeitet. Für die URL-Abgleichsphasen gilt folgende Reihenfolge. ASP.NET Core:
Die Liste der Endpunkte wird entsprechend den folgenden Punkten priorisiert:
Alle übereinstimmenden Endpunkte werden in jeder Phase verarbeitet, bis EndpointSelector erreicht ist. EndpointSelector
stellt die abschließende Phase dar. Darin wird der Endpunkt mit der höchsten Priorität aus den Übereinstimmungen als beste Übereinstimmung ausgewählt. Wenn es andere Übereinstimmungen mit derselben Priorität wie die beste Übereinstimmung gibt, wird ein Ausnahmefehler wegen einer nicht eindeutigen Übereinstimmung ausgelöst.
Die Routenpriorität wird anhand einer spezifischeren Routenvorlage berechnet, der eine höhere Priorität eingeräumt wird. Dies wird z. B. anhand der Vorlagen /hello
und /{message}
deutlich:
/hello
überein./hello
ist spezifischer und hat daher höhere Priorität.Im Allgemeinen eignet sich die Routenpriorität gut, um die beste Übereinstimmung für die in der Praxis verwendeten URL-Schemata zu finden. Verwenden Sie Order nur bei Bedarf, um eine Mehrdeutigkeit zu vermeiden.
Aufgrund der Erweiterungsmöglichkeiten, die das Routing bietet, kann das Routingsystem die mehrdeutigen Routen nicht im Voraus berechnen. Betrachten Sie ein Beispiel wie die Routenvorlagen /{message:alpha}
und /{message:int}
:
alpha
-Einschränkung gleicht nur alphabetische Zeichen ab.int
-Einschränkung gleicht nur Zahlen ab.Warnung
Die Reihenfolge der Vorgänge in UseEndpoints wirkt sich nicht auf das Routingverhalten aus, mit einer Ausnahme. MapControllerRoute und MapAreaRoute weisen ihren Endpunkten automatisch einen Reihenfolgenwert zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Dadurch wird das Langzeitverhalten von Controllern simuliert, ohne dass das Routingsystem die gleichen Garantien bietet wie ältere Routingimplementierungen.
Bei der bisherigen Routingimplementierung war es möglich, eine Routingerweiterung vorzunehmen, die von der Verarbeitungsreihenfolge der Routen abhängt. Endpunktrouting in ASP.NET Core 3.0 und höher:
Die Routenvorlagenpriorität ist ein System, bei dem jeder Routenvorlage ein Wert zugewiesen wird, je nachdem, wie spezifisch diese ist. Routenvorlagenpriorität:
Dies wird z. B. anhand der Vorlagen /Products/List
und /Products/{id}
deutlich. Es wäre begründet, anzunehmen, dass /Products/List
eine bessere Übereinstimmung als /Products/{id}
für den URL-Pfad /Products/List
ist. Dies funktioniert, weil das Literalsegment /List
eine höhere Priorität als das Parametersegment /{id}
hat.
Wie die Priorisierung im Einzelnen funktioniert, ist an die Definition der Routenvorlagen gekoppelt:
Einen Verweis auf genaue Werte finden Sie im Quellcode auf GitHub.
URL-Generierung:
Das Endpunktrouting umfasst die API zur Linkgenerierung (LinkGenerator). LinkGenerator
ist ein Singleton-Dienst, der in DI verfügbar ist. Die LinkGenerator
-API kann außerhalb des Kontexts einer ausgeführten Anforderung verwendet werden. Mvc.IUrlHelper und Szenarios, die von IUrlHelper abhängig sind (z. B. Taghilfsprogramme, HTML-Hilfsprogramme und Aktionsergebnisse), verwenden die LinkGenerator
-API, um entsprechende Funktionen bereitzustellen.
Die API zur Linkgenerierung wird von Konzepten wie Adressen und Adressschemas unterstützt. Sie können mithilfe eines Adressschemas die Endpunkte bestimmen, die bei der Linkgenerierung berücksichtigt werden sollen. Beispielsweise werden Routennamen und Routenwerte als Adressschemas implementiert. Diese Szenarios kennen viele Benutzer von Controllern und Razor Pages.
Die API zur Linkgenerierung kann Controller und Razor Pages über die folgenden Erweiterungsmethoden miteinander verknüpfen:
Beim Überladen dieser Methoden werden Argumente akzeptiert, die den HttpContext
umfassen. Diese Methoden sind zwar in funktionaler Hinsicht äquivalent zu Url.Action und Url.Page, bieten aber zusätzliche Flexibilität und Optionen.
Die GetPath*
-Methoden sind Url.Action
und Url.Page
in der Hinsicht ähnlich, dass sie einen URI mit einem absoluten Pfad generieren. Die GetUri*
-Methoden generieren immer einen absoluten URI mit einem Schema und einem Host. Die Methoden, die einen HttpContext
akzeptieren, generieren im Kontext der ausgeführten Anforderung einen URI. Die Umgebungsroutenwerte, der URL-basierte Pfad, das Schema und der Host von der ausführenden Anforderung werden so lange verwendet, bis sie außer Kraft gesetzt werden.
LinkGenerator wird mit einer Adresse aufgerufen. Ein URI wird in zwei Schritten generiert:
Die von LinkGenerator bereitgestellten Methoden unterstützen die Standardfunktionen zur Generierung von Links für jeden beliebigen Adresstypen. Am praktischsten ist es, die API zur Linkgenerierung mit Erweiterungsmethoden zu verwenden, die Vorgänge für einen bestimmten Adresstypen ausführen:
Erweiterungsmethode | Beschreibung |
---|---|
GetPathByAddress | Generiert einen URI mit einem absoluten Pfad, der auf den angegebenen Werten basiert. |
GetUriByAddress | Generiert einen absoluten URI, der auf den angegebenen Werten basiert. |
Warnung
Beachten Sie die folgenden Implikationen zum Aufrufen von LinkGenerator-Methoden:
Verwenden Sie GetUri*
-Erweiterungsmethoden in App-Konfigurationen, die den Host
-Header von eingehenden Anforderungen nicht überprüfen, mit Bedacht. Wenn der Host
-Header von eingehenden Anforderungen nicht überprüft wird, können nicht vertrauenswürdige Anforderungseingaben zurück an den Client in URIs einer Ansicht bzw. Seite zurückgesendet werden. Es wird empfohlen, dass alle Produktions-Apps ihren Server so konfigurieren, dass der Host
-Header auf bekannte gültige Werte überprüft wird.
Verwenden Sie LinkGenerator in Kombination mit Map
oder MapWhen
in Middleware mit Bedacht. Map*
ändert den Basispfad der ausgeführten Anforderung. Dies beeinflusst die Ausgabe der Linkgenerierung. Für alle LinkGenerator-APIs ist die Angabe eines Basispfads zulässig. Geben Sie einen leeren Basispfad an, um die Auswirkungen von Map*
auf die Linkgenerierung rückgängig zu machen.
Im folgenden Beispiel verwendet eine Middleware die LinkGenerator-API, um eine Verknüpfung zu einer Aktionsmethode herzustellen, die Speicherprodukte aufführt. Sie können für jede beliebige Klasse in einer App die API zur Linkgenerierung verwenden, indem Sie diese in eine Klasse einfügen und GenerateLink
aufrufen:
public class ProductsLinkMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var url = _linkGenerator.GetPathByAction("ListProducts", "Store");
httpContext.Response.ContentType = "text/plain";
await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
}
}
Token in {}
definieren Routenparameter, die beim Abgleich der Route gebunden werden. In einem Routensegment können mehrere Routenparameter definiert werden, müssen aber durch einen Literalwert getrennt werden. {controller=Home}{action=Index}
ist z.B. keine gültige Route, da sich zwischen {controller}
und {action}
kein Literalwert befindet. Routenparameter müssen einen Namen besitzen und können zusätzliche Attribute aufweisen.
Eine Literalzeichenfolge, die nicht den Routenparametern entspricht (z.B. {id}
), muss zusammen mit dem Pfadtrennzeichen /
mit dem URL-Text übereinstimmen. Beim Abgleich von Text wird nicht zwischen Groß-/Kleinbuchstaben unterschieden, und die Übereinstimmung basiert auf der decodierten Repräsentation des URL-Pfads. Damit das Trennzeichen ({
oder }
) der Routenparameter-Literalzeichenfolge bei einem Abgleich gefunden wird, muss es doppelt vorhanden sein, was einem Escapezeichen entspricht. Beispielsweise {{
oder }}
.
Sternchen *
oder Doppelsternchen **
:
blog/{**slug}
: /blog
beginnt und einem beliebigen Wert folgt./blog
wird dem Slug-Routenwert zugewiesen.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:
{**slug}"
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.
Durch Catch-All-Parameter können auch leere Zeichenfolgen gefunden werden.
Der Catch-All-Parameter schützt die entsprechenden Zeichen (Escaping), wenn die Route verwendet wird, um eine URL, einschließlich Pfadtrennzeichen (/
) zu generieren. Z.B. generiert die Route foo/{*path}
mit den Routenwerten { path = "my/path" }
foo/my%2Fpath
. Beachten Sie den umgekehrten Schrägstrich mit Escapezeichen. Um Trennzeichen für Roundtrips einsetzen zu können, verwenden Sie das Routenparameterpräfix **
. Die Route foo/{**path}
mit { path = "my/path" }
generiert foo/my/path
.
Bei einem URL-Muster, durch das ein Dateiname mit einer optionalen Erweiterung erfasst werden soll, sind noch weitere Aspekte zu berücksichtigen. Dies wird z.B. anhand der Vorlage files/{filename}.{ext?}
deutlich. Wenn sowohl für filename
als auch für ext
Werte vorhanden sind, werden beide Werte angegeben. Wenn nur für filename
ein Wert in der URL vorhanden ist, wird für die Route eine Übereinstimmung ermittelt, da der nachstehende Punkt (.
) optional ist. Für die folgenden URLs wird eine Übereinstimmung für die Route ermittelt:
/files/myFile.txt
/files/myFile
Routenparameter können über mehrere Standardwerte verfügen, die nach dem Parameternamen angegeben werden und durch ein Gleichheitszeichen (=
) voneinander getrennt werden. Mit {controller=Home}
wird beispielsweise Home
als Standardwert für controller
definiert. Der Standardwert wird verwendet, wenn kein Wert in der Parameter-URL vorhanden ist. Routenparameter sind optional, wenn am Ende des Parameternamens ein Fragezeichen (?
) angefügt wird. Beispielsweise id?
. Zwischen optionalen Werten und Standardroutenparametern besteht folgender Unterschied:
Routenparameter können Einschränkungen aufweisen, die mit dem gebundenen Routenwert der URL übereinstimmen müssen. Eine Inline-Einschränkung für einen Routenparameter geben Sie an, indem Sie hinter dem Namen des Routenparameters einen Doppelpunkt (:
) und einen Einschränkungsnamen hinzufügen. Wenn für die Einschränkung Argumente erforderlich sind, werden diese nach dem Einschränkungsnamen in Klammern ((...)
) eingeschlossen. Mehrere Inline-Einschränkungen können festgelegt werden, indem ein weiterer Doppelpunkt (:
) und Einschränkungsname hinzugefügt werden.
Der Einschränkungsname und die Argumente werden dem IInlineConstraintResolver-Dienst übergeben, wodurch eine Instanz von IRouteConstraint für die URL-Verarbeitung erstellt werden kann. In der Routenvorlage blog/{article:minlength(10)}
wird beispielsweise die Einschränkung minlength
mit dem Argument 10
festgelegt. Weitere Informationen zu Routeneinschränkungen und eine Liste der vom Framework bereitgestellten Einschränkungen finden Sie im Abschnitt Referenz zu Routeneinschränkungen.
Routenparameter können darüber hinaus über Parametertransformatoren verfügen. Diese wandeln den Wert eines Parameters beim Generieren von Links um und passen Aktionen und Seiten an URLs an. Wie Einschränkungen können auch Parametertransformatoren einem Routenparameter inline hinzugefügt werden, indem ein Doppelpunkt (:
) und der Name des Transformators hinter dem Namen des Routenparameters hinzugefügt werden. In der Routenvorlage blog/{article:slugify}
wird beispielsweise der Transformator slugify
festgelegt. Weitere Informationen zu Parametertransformatoren finden Sie im Abschnitt Parametertransformatorreferenz.
Die folgende Tabelle enthält Beispielvorlagen für Routen und deren Verhalten:
Routenvorlage | Beispiel-URI für Übereinstimmung | Der Anforderungs-URI |
---|---|---|
hello |
/hello |
Nur für den Pfad /hello wird eine Übereinstimmung ermittelt. |
{Page=Home} |
/ |
Eine Übereinstimmung wird ermittelt, und Page wird auf Home festgelegt. |
{Page=Home} |
/Contact |
Eine Übereinstimmung wird ermittelt, und Page wird auf Contact festgelegt. |
{controller}/{action}/{id?} |
/Products/List |
Stimmt mit dem Products -Controller und der List -Aktion überein. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Wird dem Controller Products und der Aktion Details zugeordnet, bei der id auf 123 festgelegt ist. |
{controller=Home}/{action=Index}/{id?} |
/ |
Stimmt mit dem Home -Controller und der Index -Methode überein. id wird ignoriert. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Stimmt mit dem Products -Controller und der Index -Methode überein. id wird ignoriert. |
Mit Vorlagen lässt sich Routing besonders leicht durchführen. Einschränkungen und Standardwerte können auch außerhalb der Routenvorlage angegeben werden.
Komplexe Segmente werden von rechts nach links auf eine nicht gierige Weise durch entsprechende Literaltrennzeichen verarbeitet. Beispielsweise ist [Route("/a{b}c{d}")]
ein komplexes Segment.
Komplexe Segmente funktionieren auf eine bestimmte Weise, die für eine erfolgreiche Verwendung verstanden werden muss. Das Beispiel in diesem Abschnitt zeigt, warum komplexe Segmente nur dann wirklich gut funktionieren, wenn der Trennzeichentext nicht innerhalb der Parameterwerte erscheint. Für komplexere Fälle ist die Verwendung eines RegEx und das anschließende manuelle Extrahieren der Werte erforderlich.
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.
Dies ist eine Zusammenfassung der Schritte, die beim Routing mit der Vorlage /a{b}c{d}
und dem URL-Pfad /abcd
ausgeführt werden. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen:
c
. Daher wird /abcd
von rechts durchsucht und /ab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /ab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.Nachfolgend ist ein Beispiel für einen negativen Fall mit derselben Vorlage /a{b}c{d}
und dem URL-Pfad /aabcd
. |
wird verwendet, um die Funktionsweise des Algorithmus besser zu veranschaulichen: Bei diesem Fall handelt es sich nicht um eine Übereinstimmung, was durch denselben Algorithmus belegt wird:
c
. Daher wird /aabcd
von rechts durchsucht und /aab|c|d
gefunden.d
) ist jetzt mit dem Routenparameter {d}
abgeglichen.a
. Also wird /aab|c|d
dort gesucht, wo die Suche unterbrochen wurde, und dann wird a
in /a|a|b|c|d
gefunden.b
) ist jetzt mit dem Routenparameter {b}
abgeglichen.a
, aber es gibt keine Routenvorlage mehr, die der Algorithmus analysieren kann, weshalb dies keine Übereinstimmung ist.Da der übereinstimmende Algorithmus nicht gierig ist:
Reguläre Ausdrücke bieten eine viel bessere Kontrolle über das Abgleichsverhalten.
Beim gierigen Abgleich, auch als Lazy Matching bezeichnet, wird die größtmögliche Zeichenfolge abgeglichen. Beim nicht gierigen Abgleich ist dies die kürzeste Zeichenfolge.
Routeneinschränkungen werden angewendet, wenn eine Übereinstimmung mit der eingehenden URL gefunden wurde und der URL-Pfad in Routenwerten mit Token versehen wird. In der Regel wird mit Routeneinschränkungen der Routenwert der zugehörigen Vorlage geprüft. Dabei wird anhand einer True/False-Entscheidung bestimmt, ob der Wert gültig ist. Für einige Routeneinschränkungen werden anstelle des Routenwerts andere Daten verwendet, um zu ermitteln, ob das Routing einer Anforderung möglich ist. HttpMethodRouteConstraint kann beispielsweise auf der Grundlage des HTTP-Verbs eine Anforderung entweder annehmen oder ablehnen. Einschränkungen werden in Routinganforderungen und bei der Linkgenerierung verwendet.
Warnung
Verwenden Sie keine Einschränkungen für die Eingabeüberprüfung. Wenn Einschränkungen für die Eingabevalidierung verwendet werden, führt eine ungültige Eingabe zu einem 404
-Fehler (Nicht gefunden). Eine ungültige Eingabe sollte zu einer ungültigen Anforderung (400
) mit einer entsprechenden Fehlermeldung führen. Verwenden Sie Routeneinschränkungen nicht, um Eingaben für eine bestimmte Route zu überprüfen, sondern um ähnliche Routen zu unterscheiden.
In der folgenden Tabelle werden Beispiele für Routeneinschränkungen und deren zu erwartendes Verhalten beschrieben:
Einschränkung | Beispiel | Beispiele für Übereinstimmungen | Hinweise |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Für jeden Integer wird eine Übereinstimmung ermittelt. |
bool |
{active:bool} |
true , FALSE |
Entspricht true oder false . Groß-/Kleinschreibung nicht beachten |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Entspricht einem gültigen DateTime -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Entspricht einem gültigen decimal -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen double -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Entspricht einem gültigen float -Wert in der invarianten Kultur. Siehe vorherige Warnung. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Für einen gültigen Guid -Wert wird eine Übereinstimmung ermittelt. |
long |
{ticks:long} |
123456789 , -123456789 |
Für einen gültigen long -Wert wird eine Übereinstimmung ermittelt. |
minlength(value) |
{username:minlength(4)} |
Rick |
Die Zeichenfolge muss mindestens eine Länge von 4 Zeichen aufweisen. |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Die Zeichenfolge darf maximal eine Länge von 8 Zeichen aufweisen. |
length(length) |
{filename:length(12)} |
somefile.txt |
Die Zeichenfolge muss genau 12 Zeichen aufweisen. |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Die Zeichenfolge muss mindestens eine Länge von 8 und darf maximal eine Länge von 16 Zeichen aufweisen. |
min(value) |
{age:min(18)} |
19 |
Der Integerwert muss mindestens 18 sein. |
max(value) |
{age:max(120)} |
91 |
Der Integerwert darf nicht größer als 120 sein. |
range(min,max) |
{age:range(18,120)} |
91 |
Der Integerwert muss zwischen 18 und 120 liegen. |
alpha |
{name:alpha} |
Rick |
Die Zeichenfolge muss aus mindestens einem alphabetische Zeichen bestehen, a -z und ohne Unterscheidung zwischen Groß-/Kleinbuchstaben. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Die Zeichenfolge muss mit dem regulären Ausdruck übereinstimmen. Weitere Informationen finden Sie unter Tipps zum Definieren eines regulären Ausdrucks. |
required |
{name:required} |
Rick |
Hierdurch wird erzwungen, dass ein Wert, der kein Parameter ist, für die URL-Generierung vorhanden sein muss. |
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.
Auf einen einzelnen Parameter können mehrere durch Doppelpunkte getrennte Einschränkungen angewendet werden. Durch die folgende Einschränkung wird ein Parameter beispielsweise auf einen Integerwert größer oder gleich 1 beschränkt:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warnung
Für Routeneinschränkungen, mit denen die URL überprüft wird und die in den CLR-Typ umgewandelt werden, wird immer die invariante Kultur verwendet. Dies gilt z. B. für die Konvertierung in den CLR-Typ int
oder DateTime
. Diese Einschränkungen setzen voraus, dass die URL nicht lokalisierbar ist. Die vom Framework bereitgestellten Routeneinschränkungen ändern nicht die Werte, die in Routenwerten gespeichert sind. Alle Routenwerte, die aus der URL analysiert werden, werden als Zeichenfolgen gespeichert. Durch die float
-Einschränkung wird beispielsweise versucht, den Routenwert in einen Gleitkommawert zu konvertieren. Mit dem konvertierten Wert wird allerdings nur überprüft, ob eine Umwandlung überhaupt möglich ist.
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.
Reguläre Ausdrücke können mithilfe der regex(...)
-Routeneinschränkung als Inline-Einschränkungen angegeben werden. Methoden der MapControllerRoute-Familie akzeptieren auch ein Objektliteral von Einschränkungen. Wenn dieses Formular verwendet wird, werden Zeichenfolgenwerte als reguläre Ausdrücke interpretiert.
Der folgende Code verwendet eine Inline-RegEx-Einschränkung:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
context =>
{
return context.Response.WriteAsync("inline-constraint match");
});
});
Der folgende Code verwendet ein Objektliteral, um eine RegEx-Einschränkung anzugeben:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "people",
pattern: "People/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List", });
});
Im ASP.NET Core-Framework wird dem Konstruktor für reguläre Ausdrücke RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
hinzugefügt. Eine Beschreibung dieser Member finden Sie unter RegexOptions.
In regulären Ausdrücken werden Trennzeichen und Token verwendet, die auch beim Routing und in der Programmiersprache C# in ähnlicher Weise verwendet werden. Token, die reguläre Ausdrücke enthalten, müssen mit einem Escapezeichen versehen werden. Wenn Sie den regulären Ausdruck ^\d{3}-\d{2}-\d{4}$
in einer Inline-Einschränkung verwenden möchten, nutzen Sie eine der folgenden Optionen:
\
-Zeichen in der Zeichenfolge durch \\
-Zeichen in der C#-Quelldatei, um das Escapezeichen für die Zeichenfolge \
zu setzen.Wenn Sie Trennzeichen für Routenparameter mit Escapezeichen versehen möchten ({
, }
, [
, ]
), geben Sie jedes Zeichen im Ausdruck doppelt ein (z. B. {{
, }}
, [[
, ]]
). In der folgenden Tabelle werden reguläre Ausdrücke und Ausdrücke aufgeführt, die mit Escapezeichen versehen sind:
Regulärer Ausdruck | Mit Escapezeichen versehener regulärer Ausdruck |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Beim Routing verwendete reguläre Ausdrücke beginnen oft mit dem ^
-Zeichen und stellen die Startposition der Zeichenfolge dar. Die Ausdrücke enden häufig mit einem Dollarzeichen ($
) und stellen das Ende der Zeichenfolge dar. Mit den Zeichen ^
und $
wird sichergestellt, dass der reguläre Ausdruck mit dem vollständigen Routenparameterwert übereinstimmt. Ohne die Zeichen ^
und $
werden mit dem regulären Ausdruck alle Teilzeichenfolgen ermittelt, was häufig nicht gewünscht ist. In der folgenden Tabelle finden Sie Beispiele für reguläre Ausdrücke. Außerdem wird erklärt, warum ein Abgleich erfolgreich ist oder fehlschlägt:
expression | Zeichenfolge | Match | Kommentar |
---|---|---|---|
[a-z]{2} |
hello | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
123abc456 | Ja | Teilzeichenfolge stimmt überein |
[a-z]{2} |
mz | Ja | Ausdruck stimmt überein |
[a-z]{2} |
MZ | Ja | keine Unterscheidung zwischen Groß-/Kleinbuchstaben |
^[a-z]{2}$ |
hello | Nein | siehe Erläuterungen zu ^ und $ oben |
^[a-z]{2}$ |
123abc456 | Nein | siehe Erläuterungen zu ^ und $ oben |
Weitere Informationen zur Syntax von regulären Ausdrücken finden Sie unter Sprachelemente für reguläre Ausdrücke – Kurzübersicht.
Einen regulären Ausdruck können Sie verwenden, um einen Parameter auf zulässige Werte einzuschränken. Mit {action:regex(^(list|get|create)$)}
werden beispielsweise für den action
-Routenwert nur die Werte list
, get
oder create
abgeglichen. Wenn die Zeichenfolge ^(list|get|create)$
dem Einschränkungswörterbuch übergeben wird, führt dies zum gleichen Ergebnis. Auch Einschränkungen, die dem zugehörigen Wörterbuch hinzugefügt werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden als reguläre Ausdrücke behandelt. Einschränkungen, die innerhalb einer Vorlage übergeben werden und mit keiner vorgegebenen Einschränkung übereinstimmen, werden nicht als reguläre Ausdrücke behandelt.
Benutzerdefinierte Routeneinschränkungen können durch Implementierung der IRouteConstraint-Schnittstelle erstellt werden. Die IRouteConstraint
-Schnittstelle umfasst die Match-Methode, die true
zurückgibt, wenn die Einschränkung erfüllt wird, und andernfalls false
.
Benutzerdefinierte Routeneinschränkungen werden nur selten benötigt. Bevor Sie eine benutzerdefinierte Routeneinschränkung implementieren, sollten Sie Alternativen in Betracht ziehen, wie z. B. Modellbindung.
Der ASP.NET Core-Ordner Constraints bietet nützliche Beispiele für die Erstellung von Einschränkungen. Beispiel: GuidRouteConstraint.
Zum Verwenden eines benutzerdefinierten IRouteConstraint
-Elements muss der Routeneinschränkungstyp bei der ConstraintMap-Eigenschaft der App im Dienstcontainer registriert werden. Eine ConstraintMap
ist ein Wörterbuch, das Routeneinschränkungsschlüssel IRouteConstraint
-Implementierungen zuordnet, die diese Einschränkungen überprüfen. Die ConstraintMap
einer App kann in Startup.ConfigureServices
entweder als Teil eines services.AddRouting-Aufrufs oder durch direktes Konfigurieren von RouteOptions mit services.Configure<RouteOptions>
aktualisiert werden. Beispiel:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});
}
Die vorangehende Einschränkung wird im folgenden Code angewendet:
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
// GET /api/test/3
[HttpGet("{id:customName}")]
public IActionResult Get(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
// GET /api/test/my/3
[HttpGet("my/{id:customName}")]
public IActionResult Get(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
MyDisplayRouteInfo wird von dem NuGet-Paket Rick.Docs.Samples.RouteInfo bereitgestellt und zeigt Routeninformationen an.
Die Implementierung von MyCustomConstraint
verhindert die Anwendung von 0
auf einen Routenparameter:
class MyCustomConstraint : IRouteConstraint
{
private Regex _regex;
public MyCustomConstraint()
{
_regex = new Regex(@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
}
public bool Match(HttpContext httpContext, IRouter route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (values.TryGetValue(routeKey, out object value))
{
var parameterValueString = Convert.ToString(value,
CultureInfo.InvariantCulture);
if (parameterValueString == null)
{
return false;
}
return _regex.IsMatch(parameterValueString);
}
return false;
}
}
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.
Der vorangehende Code:
0
im {id}
-Segment der Route vorhanden ist.Der folgende Code bietet einen besseren Ansatz, um zu verhindern, dass eine id
mit einer 0
verarbeitet wird:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return ControllerContext.MyDisplayRouteInfo(id);
}
Der vorangehende Code bietet im Vergleich zum MyCustomConstraint
-Ansatz folgende Vorteile:
0
enthält.Parametertransformatoren:
Beispielsweise generiert ein benutzerdefinierter Parametertransformator slugify
im Routenmuster blog\{article:slugify}
mit Url.Action(new { article = "MyTestArticle" })
blog\my-test-article
.
Betrachten Sie die folgende Implementierung von IOutboundParameterTransformer
:
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();
}
}
Um einen Parametertransformator in einem Routenmuster zu verwenden, konfigurieren Sie ihn mit ConstraintMap in Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
}
Das ASP.NET Core-Framework verwendet Parametertransformatoren, um den URI zu transformieren, zu dem ein Endpunkt aufgelöst wird. Beispielsweise wandeln Parametertransformatoren die Routenwerte um, die zum Zuordnen folgender Elemente verwendet werden: area
, controller
, action
und page
.
routes.MapControllerRoute(
name: "default",
template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Mit der vorstehenden Routenvorlage wird die Aktion SubscriptionManagementController.GetAll
dem URI /subscription-management/get-all
zugeordnet. Ein Parametertransformator ändert nicht die zum Generieren eines Links verwendeten Routenwerte. Beispielsweise gibt Url.Action("GetAll", "SubscriptionManagement")
/subscription-management/get-all
aus.
ASP.NET Core bietet API-Konventionen für die Verwendung von Parametertransformatoren mit generierten Routen:
Dieser Abschnitt enthält eine Referenz für den Algorithmus, der durch die URL-Generierung implementiert wird. In der Praxis werden bei den meisten komplexen Beispielen für die URL-Generierung Controller oder Razor Pages verwendet. Weitere Informationen finden Sie unter Routing in Controllern.
Die URL-Generierung beginnt mit einem Aufruf von LinkGenerator.GetPathByAddress oder einer ähnlichen Methode. Die Methode wird mit einer Adresse, mehreren Routenwerten und optional mit Informationen zur aktuellen Anforderung von HttpContext
versehen.
Im ersten Schritt wird die Adresse verwendet, um bestimmte Endpunktkandidaten mithilfe einer IEndpointAddressScheme<TAddress>
-Schnittstelle aufzulösen, die dem Adresstyp entspricht.
Sobald eine Kandidatengruppe anhand des Adressschemas gefunden wurde, werden die Endpunkte geordnet und iterativ verarbeitet, bis die URL-Generierung erfolgreich abgeschlossen ist. Bei der URL-Generierung wird nicht auf Mehrdeutigkeiten geprüft, daher ist das erste zurückgegebene Ergebnis das Endergebnis.
Der erste Schritt bei der Behebung von Problemen bei der URL-Generierung ist die Einstellung des Protokolliergrads von Microsoft.AspNetCore.Routing
auf TRACE
. LinkGenerator
protokolliert viele Details über die Verarbeitung, die bei der Problembehebung nützlich sein können.
Ausführliche Informationen zur URL-Generierung finden Sie unter Referenz für URL-Generierung.
Mithilfe von Adressen wird bei der URL-Generierung ein Aufruf in der API zur Linkgenerierung an mehrere Endpunktkandidaten gebunden.
Adressen sind ein erweiterbares Konzept, das standardmäßig mit zwei Implementierungen bereitgestellt wird:
string
) als Adresse: IUrlHelper
, Taghilfsprogrammen, HTML-Hilfsprogrammen, Aktionsergebnissen usw. verwendet wird.Aufgabe des Adressschemas ist es, die Verbindung zwischen der Adresse und den übereinstimmenden Endpunkten anhand von beliebigen Kriterien herzustellen:
Aus der aktuellen Anforderung greift das Routing auf die Routenwerte der aktuellen Anforderung HttpContext.Request.RouteValues
zu. Die mit der aktuellen Anforderung verbundenen Werte werden als Umgebungswerte bezeichnet. Aus Gründen der Übersichtlichkeit werden in der Dokumentation die an die Methoden übergebenen Routenwerte als explizite Werte bezeichnet.
Das folgende Beispiel zeigt Umgebungswerte und explizite Werte. Er liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte: { id = 17, }
:
public class WidgetController : Controller
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public IActionResult Index()
{
var url = _linkGenerator.GetPathByAction(HttpContext,
null, null,
new { id = 17, });
return Content(url);
}
Der vorangehende Code:
/Widget/Index/17
zurück.Der folgende Code liefert keine Umgebungswerte und keine expliziten Werte: { controller = "Home", action = "Subscribe", id = 17, }
:
public IActionResult Index2()
{
var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
new { id = 17, });
return Content(url);
}
Die vorhergehende Methode gibt /Home/Subscribe/17
zurück.
Der folgende Code in WidgetController
gibt /Widget/Subscribe/17
zurück:
var url = _linkGenerator.GetPathByAction("Subscribe", null,
new { id = 17, });
Der folgende Code stellt den Controller aus den Umgebungswerten in der aktuellen Anforderung und explizite Werte dar: { action = "Edit", id = 17, }
:
public class GadgetController : Controller
{
public IActionResult Index()
{
var url = Url.Action("Edit", new { id = 17, });
return Content(url);
}
Für den Code oben gilt:
/Gadget/Edit/17
wird zurückgegeben.action
-Namen und route
-Werte.Sie liefert Umgebungswerte aus der aktuellen Anforderung und explizite Werte: { page = "./Edit, id = 17, }
:
public class IndexModel : PageModel
{
public void OnGet()
{
var url = Url.Page("./Edit", new { id = 17, });
ViewData["URL"] = url;
}
}
Im vorangehenden Code wird url
auf /Edit/17
festgelegt, wenn die Option zum Bearbeiten der Razor Page die folgende Seitenanweisung enthält:
@page "{id:int}"
Wenn die Routenvorlage "{id:int}"
nicht in der Seite „Bearbeiten“ enthalten ist, ist url
gleich /Edit?id=17
.
Das Verhalten der IUrlHelper-Schnittstelle von MVC fügt zusätzlich zu den hier beschriebenen Regeln eine weitere Komplexitätsebene hinzu:
IUrlHelper
liefert immer die Routenwerte aus der aktuellen Anforderung als Umgebungswerte.action
und controller
als explizite Werte, sofern sie nicht vom Entwickler außer Kraft gesetzt werden.page
als expliziten Wert, sofern er nicht außer Kraft gesetzt wird. IUrlHelper.Page
setzt immer den aktuellen Routenwert handler
mit null
als expliziten Wert außer Kraft, sofern er nicht außer Kraft gesetzt wird.Benutzer sind oft von den Verhaltensdetails der Umgebungswerte überrascht, da MVC anscheinend nicht den eigenen Regeln folgt. Aus Verlaufs- und Kompatibilitätsgründen weisen bestimmte Routenwerte wie action
, controller
, page
und handler
ein spezielles Verhalten auf.
Die äquivalente Funktionalität, die durch LinkGenerator.GetPathByAction
und LinkGenerator.GetPathByPage
bereitgestellt wird, verdoppelt diese Anomalien von IUrlHelper
aus Kompatibilitätsgründen.
Sobald die Gruppe der Endpunktkandidaten ermittelt ist, wird der URL-Generierungsalgorithmus angewendet:
Der erste Schritt in diesem Prozess wird als Routenwertinvalidierung bezeichnet. Die Routenwertinvalidierung ist der Prozess, bei dem das Routing entscheidet, welche Routenwerte aus den Umgebungswerten verwendet und welche ignoriert werden sollen. Jeder Umgebungswert wird berücksichtigt und entweder mit den expliziten Werten kombiniert oder aber ignoriert.
Denken Sie daran, dass Umgebungswerte Anwendungsentwicklern in allgemeinen Fällen das Schreiben von Code sparen können. In der Regel sind die Szenarios, in denen Umgebungswerte hilfreich sind, mit MVC verknüpft:
Aufrufe an LinkGenerator
oder IUrlHelper
, die null
zurückgeben, sind meist dadurch bedingt, dass die Routenwertinvalidierung nicht verstanden wurde. Beheben Sie die Routenwertinvalidierung, indem Sie explizit mehr Routenwerte angeben, um zu prüfen, ob das Problem dadurch gelöst wird.
Bei der Routenwertinvalidierung wird davon ausgegangen, dass das URL-Schema der Anwendung hierarchisch ist, mit einer von links nach rechts gebildeten Hierarchie. Sehen Sie sich die einfache Controllerroutenvorlage {controller}/{action}/{id?}
an, um ein Gespür dafür zu bekommen, wie dies in der Praxis funktioniert. Durch eine Änderung auf einen Wert werden alle rechts angezeigten Routenwerte ungültig. Dies spricht für die These von der Hierarchie. Wenn die App einen Umgebungswert für id
hat und der Vorgang einen anderen Wert für controller
angibt:
id
wird nicht wiederverwendet, weil {controller}
links von {id?}
steht.Einige Beispiele veranschaulichen dieses Prinzip:
id
enthalten, wird der Umgebungswert für id
ignoriert. Die Umgebungswerte für controller
und action
können verwendet werden.action
enthalten, wird jeder Umgebungswert für action
ignoriert. Die Umgebungswerte für controller
können verwendet werden. Wenn sich der explizite Wert für action
von dem Umgebungswert für action
unterscheidet, wird der Wert id
nicht verwendet. Wenn der explizite Wert für action
mit dem Umgebungswert für action
übereinstimmt, kann der Wert id
verwendet werden.controller
enthalten, wird jeder Umgebungswert für controller
ignoriert. Wenn sich der explizite Wert für controller
von dem Umgebungswert für controller
unterscheidet, werden die Werte action
und id
nicht verwendet. Wenn der explizite Wert für controller
mit dem Umgebungswert für controller
übereinstimmt, können die Werte action
und id
verwendet werden.Dieser Prozess wird zusätzlich durch die vorhandenen Attributrouten und dedizierten konventionellen Routen erschwert. Konventionelle Routen des Controllers wie {controller}/{action}/{id?}
legen eine Hierarchie mithilfe von Routenparametern fest. Bei bestimmten konventionellen Routen und Attributrouten zu Controllern und Razor Pages:
Für diese Fälle definiert die URL-Generierung das Konzept der erforderlichen Werte. Bei Endpunkten, die von Controllern und Razor Pages erstellt wurden, sind erforderliche Werte angegeben, die eine Routenwertinvalidierung ermöglichen.
Der Algorithmus der Routenwertinvalidierung im Detail:
An diesem Punkt ist der Vorgang zur URL-Generierung bereit, Routeneinschränkungen auszuwerten. Die akzeptierten Werte werden mit den Standardwerten der Parameter kombiniert, die für Einschränkungen bereitgestellt werden. Wenn alle Einschränkungen erfüllt sind, wird der Vorgang fortgesetzt.
Als Nächstes können die akzeptierten Werte verwendet werden, um die Routenvorlage zu erweitern. Die Routenvorlage wird verarbeitet:
Explizit bereitgestellte Werte, für die keine Übereinstimmungen mit einem Routensegment ermittelt werden, werden der Abfragezeichenfolge hinzugefügt. In der folgenden Tabelle werden die Ergebnisse dargestellt, die aus der Verwendung der Routenvorlage {controller}/{action}/{id?}
hervorgehen:
Umgebungswerte | Explizite Werte | Ergebnis |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Ab ASP.NET Core 3.0 funktionieren einige Schemas zur URL-Generierung, die in früheren ASP.NET Core-Versionen verwendet wurden, nicht mehr sehr gut. Das ASP.NET Core-Team plant, in einer zukünftigen Version Features hinzuzufügen, die diese Anforderungen erfüllen. Im Moment ist die beste Lösung die Verwendung von Legacy-Routingfunktionen.
Der folgende Code zeigt ein Beispiel für ein Schema zur URL-Generierung, das vom Routing nicht unterstützt wird.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("blog", "{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost", });
});
Im vorangehenden Code wird der culture
-Routenparameter für die Lokalisierung verwendet. Ziel ist es, dass der culture
-Parameter immer als Umgebungswert akzeptiert wird. Der culture
-Parameter wird jedoch aufgrund der Art und Weise, wie die erforderlichen Werte funktionieren, nicht als Umgebungswert akzeptiert:
"default"
-Routenvorlage befindet sich der culture
-Routenparameter links von controller
, sodass controller
durch Änderungen an culture
nicht ungültig wird."blog"
-Routenvorlage wird der culture
-Routenparameter rechts von controller
betrachtet, der in den erforderlichen Werten aufgeführt ist.Die folgenden Links enthalten Informationen zum Konfigurieren von Endpunktmetadaten:
[MinimumAgeAuthorize]
-AttributRequireHost wendet eine Einschränkung auf die Route an, für die der angegebene Host erforderlich ist. Der RequireHost
- oder [Host]-Parameter kann wie folgt lauten:
www.domain.com
, entspricht www.domain.com
mit einem beliebigen Port.*.domain.com
, entspricht www.domain.com
, subdomain.domain.com
oder www.subdomain.domain.com
an einem beliebigen Port.*:5000
, entspricht Port 5000 mit einem beliebigen Host.www.domain.com:5000
oder *.domain.com:5000
, entspricht dem Host und Port.Es können mehrere Parameter mit RequireHost
oder [Host]
angegeben werden. Die Einschränkung gleicht die Hosts ab, die für einen der Parameter gültig sind. Beispielsweise entspricht [Host("domain.com", "*.domain.com")]
domain.com
, www.domain.com
und subdomain.domain.com
.
Im folgenden Code wird RequireHost
verwendet, um den angegebenen Host auf der Route anzufordern:
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!"))
.RequireHost("contoso.com");
endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!"))
.RequireHost("adventure-works.com");
endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");
});
}
Im folgenden Code wird das [Host]
-Attribut für den Controller verwendet, um die einzelnen angegebenen Hosts anzufordern:
[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Host("example.com:8080")]
public IActionResult Privacy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Wenn das [Host]
-Attribut sowohl auf die Controller- als auch auf die Aktionsmethode angewendet wird, trifft Folgendes zu:
Der Großteil der Routingfunktionalität wurde in ASP.NET Core 3.0 aktualisiert, um die Leistung zu erhöhen.
Wenn eine App Leistungsprobleme hat, wird die Ursache häufig beim Routing vermutet. Das Routing wird deshalb in Betracht gezogen, weil Frameworks wie Controller und Razor Pages in ihren Protokollierungsmeldungen die innerhalb des Frameworks verbrachte Zeit angeben. Wenn es einen signifikanten Unterschied zwischen der von den Controllern gemeldeten Zeit und der Gesamtzeit der Anforderung gibt:
Die Leistung des Routings wird anhand von Tausenden von Endpunkten getestet. Es ist unwahrscheinlich, dass eine typische App auf ein Leistungsproblem stößt, nur weil diese zu umfangreich ist. Die häufigste Ursache für eine langsames Routing ist üblicherweise eine schlecht funktionierende benutzerdefinierte Middleware.
Das folgende Codebeispiel veranschaulicht eine grundlegende Technik zur Eingrenzung der Verzögerungsquelle:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
app.Use(next => async context =>
{
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(next => async context =>
{
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(next => async context =>
{
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Timing test.");
});
});
}
Auf das Zeitrouting:
Dies ist ein einfacher Weg, um die Verzögerung zu verringern, wenn sie signifikant ist, zum Beispiel größer als 10ms
. Wenn Time 2
von Time 1
subtrahiert wird, ergibt sich die in der UseRouting
-Middleware benötigte Zeit.
Der folgende Code verwendet einen kompakteren Ansatz als der vorangegangene Zeitcode:
public sealed class MyStopwatch : IDisposable
{
ILogger<Startup> _logger;
string _message;
Stopwatch _sw;
public MyStopwatch(ILogger<Startup> logger, string message)
{
_logger = logger;
_message = message;
_sw = Stopwatch.StartNew();
}
private bool disposed = false;
public void Dispose()
{
if (!disposed)
{
_logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",
_message, _sw.ElapsedMilliseconds);
disposed = true;
}
}
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
int count = 0;
app.Use(next => async context =>
{
using (new MyStopwatch(logger, $"Time {++count}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(next => async context =>
{
using (new MyStopwatch(logger, $"Time {++count}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(next => async context =>
{
using (new MyStopwatch(logger, $"Time {++count}"))
{
await next(context);
}
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Timing test.");
});
});
}
Die folgende Liste gibt einen Einblick in Routingfeatures, die im Vergleich zu einfachen Routenvorlagen relativ teuer sind:
{x}-{y}-{z}
): Dieser Abschnitt enthält Hinweise für Bibliotheksautoren, die auf dem Routing aufbauen. Diese Details sollen sicherstellen, dass App-Entwickler gute Erfahrungen mit Bibliotheken und Frameworks machen, die das Routing erweitern.
Wenn Sie ein Framework erstellen möchten, das das Routing für den URL-Abgleich verwendet, sollten Sie zunächst eine Benutzeroberfläche definieren, die auf UseEndpoints aufbaut.
Es wird empfohlen, dass Sie auf IEndpointRouteBuilder aufbauen. Auf diese Weise können Benutzer Ihr Framework mit anderen ASP.NET Core-Features problemlos zusammenstellen. Jede ASP.NET Core-Vorlage umfasst die Routingfunktionalität. Gehen Sie davon aus, dass das Routing vorhanden und den Benutzern vertraut ist.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...);
endpoints.MapHealthChecks("/healthz");
});
Es wird empfohlen, dass Sie einen versiegelten konkreten Typ aus einem Aufruf an MapMyFramework(...)
zurückgeben, der IEndpointConventionBuilder implementiert. Die meisten Map...
-Methoden in Frameworks folgen diesem Muster. Die IEndpointConventionBuilder
-Schnittstelle:
Wenn Sie Ihren eigenen Typ deklarieren, können Sie dem Generator Ihre eigene frameworkspezifische Funktionalität hinzufügen. Es ist in Ordnung, einen vom Framework deklarierten Generator zu umschließen und Aufrufe an ihn weiterzuleiten.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
endpoints.MapHealthChecks("/healthz");
});
Ziehen Sie in Betracht, Ihre eigene EndpointDataSource-Klasse zu schreiben. Die EndpointDataSource
-Klasse vom primitiven Typ auf niedriger Ebene eignet sich zum Deklarieren und Aktualisieren einer Sammlung von Endpunkten. EndpointDataSource
ist eine leistungsstarke API, die von Controllern und Razor Pages verwendet wird.
Die Routingtests haben ein grundlegendes Beispiel für eine Datenquelle, die nicht aktualisiert wird.
Versuchen Sie nicht, eine EndpointDataSource
-Klasse standardmäßig zu registrieren. Fordern Sie die Benutzer auf, Ihr Framework in UseEndpoints zu registrieren. Der Grundgedanke beim Routing ist, dass standardmäßig nichts enthalten ist, und dass die Endpunkte bei UseEndpoints
registriert werden müssen.
Ziehen Sie in Betracht, Metadatentypen als Schnittstelle zu definieren.
Ermöglichen Sie, Metadatentypen als Attribut für Klassen und Methoden zu verwenden.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks wie Controller und Razor Pages unterstützen die Anwendung von Metadatenattributen auf Typen und Methoden. Für das Deklarieren von Metadatentypen gilt:
Durch die Deklaration eines Metadatentyps als Schnittstelle wird die Flexibilität zusätzlich erhöht:
Ermöglichen Sie, Metadaten außer Kraft zu setzen, wie in folgendem Beispiel gezeigt:
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
Die beste Möglichkeit, diese Richtlinien zu befolgen, besteht darin, die Definition von Markermetadaten zu vermeiden:
Die Metadatensammlung ist geordnet und unterstützt das Außerkraftsetzen nach Priorität. Im Fall von Controllern sind Metadaten für die Aktionsmethode am spezifischsten.
Nutzen Sie Middleware mit und ohne Routing.
app.UseRouting();
app.UseAuthorization(new AuthorizationPolicy() { ... });
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization();
});
Ein gutes Beispiel für diese Vorgabe ist die UseAuthorization
-Middleware. Mithilfe der Autorisierungsmiddleware können Sie eine Fallbackrichtlinie einbauen. Die Fallbackrichtlinie gilt, falls angegeben, für beide:
Dies macht die Autorisierungsmiddleware außerhalb des Routingkontexts sehr nützlich. Die Autorisierungsmiddleware kann für die traditionelle Middlewareprogrammierung verwendet werden.
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"
}
}
}
Feedback zu ASP.NET Core
ASP.NET Core ist ein Open Source-Projekt. Wählen Sie einen Link aus, um Feedback zu geben:
Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenTraining
Modul
Verwenden von Seiten, Routing und Layouts zur Verbesserung der Blazor-Navigation - Training
Erfahren Sie, wie Sie die Navigation Ihrer App optimieren, Parameter aus der URL verwenden und wiederverwendbare Layouts in einer Blazor-Web-App erstellen.