Routing
Diese Seite wurde automatisch aus dem Englischen übersetzt. Original lesen
Der Router ist der erste Schritt beim Aufbau des Servers. Er ist dafür verantwortlich, Route Objekte zu beherbergen, die Endpunkte sind, die URLs und deren Methoden zu Aktionen abbilden, die vom Server ausgeführt werden. Jede Aktion ist dafür verantwortlich, eine Anfrage zu empfangen und eine Antwort an den Client zu liefern.
Routen sind Paare aus Pfadausdrücken („Pfadmuster“) und der HTTP‑Methode, auf die sie hören können. Wenn eine Anfrage an den Server gestellt wird, versucht er, eine Route zu finden, die zur empfangenen Anfrage passt, ruft dann die Aktion dieser Route auf und liefert die resultierende Antwort an den Client.
Es gibt mehrere Möglichkeiten, Routen in Sisk zu definieren: Sie können statisch, dynamisch oder automatisch gescannt sein, über Attribute definiert werden oder direkt im Router‑Objekt.
Router mainRouter = new Router();
// maps the GET / route into the following action
mainRouter.MapGet("/", request => {
return new HttpResponse("Hello, world!");
});Um zu verstehen, was eine Route leisten kann, müssen wir verstehen, was eine Anfrage leisten kann. Ein HttpRequest enthält alles, was Sie benötigen. Sisk enthält außerdem einige zusätzliche Funktionen, die die Gesamtentwicklung beschleunigen.
Für jede vom Server empfangene Aktion wird ein Delegat vom Typ RouteAction aufgerufen. Dieser Delegat enthält einen Parameter, der ein HttpRequest mit allen notwendigen Informationen über die vom Server empfangene Anfrage hält. Das von diesem Delegaten zurückgegebene Objekt muss ein HttpResponse sein oder ein Objekt, das über implizite Antworttypen darauf abgebildet wird.
Matching routes #
Wenn eine Anfrage vom HTTP‑Server empfangen wird, sucht Sisk nach einer Route, die den Ausdruck des von der Anfrage empfangenen Pfads erfüllt. Der Ausdruck wird immer zwischen der Route und dem Anfrage‑Pfad getestet, ohne die Abfragezeichenfolge zu berücksichtigen.
Die erste Route, deren Pfad und Methode mit der Anfrage übereinstimmen, verarbeitet sie. Wenn keine Route mit dieser Anfrage übereinstimmt, wird die Router.NotFoundErrorHandler‑Antwort an den Client zurückgegeben. Wenn das Pfadmuster übereinstimmt, die HTTP‑Methode jedoch nicht, wird die Router.MethodNotAllowedErrorHandler‑Antwort an den Client gesendet. Die Reihenfolge, in der Routen getestet werden, ist in matching precedence beschrieben.
Sisk prüft die Möglichkeit von Routenkollisionen zwischen statischen Routen. Beim Serverstart sucht Sisk nach statischen Routen, deren Pfadmuster und Methoden sich überschneiden, und wirft eine Ausnahme, wenn welche gefunden werden. In derselben Prüfung wirft Sisk eine ArgumentException, wenn zwei oder mehr Routen denselben Route.Name teilen. Beide Prüfungen werden von Router.CheckForRouteCollisions gesteuert, das standardmäßig aktiviert ist.
Matching precedence #
Jede Route ist entweder statisch oder dynamisch, wie durch Route.IsStatic angezeigt:
- Statische Routen folgen strikt ihrem Pfadmuster, z. B.
/users/<id>. MitMap*‑Methoden erstellte Routen,Route‑Instanzen und Routen‑Attribute sind statisch. - Dynamische Routen interpretieren ihren Pfad auf eigene Weise und können Pfade abgleichen, die nicht durch ein Pfadmuster beschrieben werden können. RegexRoute, PrefixRoute (verwendet von file servers) und custom matching routes sind dynamisch.
Wenn der Server startet, ordnet der Router seine Routen einmal neu, sodass jede statische Route vor jeder dynamischen Route getestet wird. Innerhalb jeder Gruppe behalten die Routen ihre Registrierungsreihenfolge bei. Das bedeutet, dass eine statische Route immer Vorrang vor einer dynamischen Route hat, unabhängig von der Reihenfolge, in der sie definiert wurden:
// dynamic: serves any GET path from the "wwwroot" directory
mainRouter.MapFileSystem("/", "wwwroot");
// static: handles GET /api/status, even though it was defined after the file server
mainRouter.MapGet("/api/status", request => new HttpResponse("ok"));Im obigen Beispiel fungiert der Dateiserver als Rückfallback für jede GET‑Anfrage, die nicht von einer statischen Route verarbeitet wird.
Die beiden Gruppen unterscheiden sich außerdem darin, wie sie validiert und transformiert werden:
| Statische Routen | Dynamische Routen | |
|---|---|---|
| Matching order | Zuerst, in Registrierungsreihenfolge | Nach allen statischen Routen, in Registrierungsreihenfolge |
| Route collision check | Ja | Nein |
| Router.Prefix wird angewendet | Ja | Nein |
Warnung
Dynamische Routen werden nicht auf Kollisionen geprüft, da Sisk nicht wissen kann, welche Pfade sie matchen. Wenn zwei dynamische Routen dieselbe Anfrage matchen können, gewinnt die zuerst definierte. Zum Beispiel macht ein Dateiserver, der auf / gemappt ist und vor einem Dateiserver, der auf /static gemappt ist, definiert wird, den zweiten unerreichbar.
Wichtig
In Versionen vor 1.7 wurden Routen nur in ihrer Registrierungsreihenfolge getestet, und Regex‑Routen konnten Vorrang vor statischen Routen haben, die danach definiert wurden. Wenn Ihre Anwendung darauf angewiesen ist, dass eine Regex‑Route einen Pfad verarbeitet, der auch von einer statischen Route gematcht wird, verschieben Sie diese Logik in die statische Route oder schränken Sie das statische Pfadmuster ein.
Creating routes using path patterns #
Für neue Anwendungen sollten Sie die Map*‑Methoden bevorzugen. Sie halten die HTTP‑Methode an der Aufrufstelle sichtbar und entsprechen der aktuellen Router‑API. Die älteren SetRoute‑Methoden existieren noch als Kompatibilitäts‑Wrapper, aber neue Beispiele sollten Map, MapGet, MapPost, MapPut, MapDelete, MapPatch, MapAny, MapOptions oder MapHead verwenden.
// Map* methods are the usual way to define method-specific routes.
mainRouter.MapGet("/hey/<name>", (request) =>
{
string name = request.RouteParameters["name"].GetString();
return new HttpResponse($"Hello, {name}");
});
mainRouter.MapPost("/form", (request) =>
{
var formData = request.GetFormContent();
return new HttpResponse(); // empty 200 ok
});
// Map can also receive a Route instance when you need route options.
mainRouter.Map(Route.Get("/image.png", (request) =>
{
var imageStream = File.OpenRead("image.png");
return new HttpResponse()
{
// the StreamContent inner
// stream is disposed after sending
// the response.
Content = new StreamContent(imageStream)
};
}));
// multiple parameters
mainRouter.MapGet("/hey/<name>/surname/<surname>", (request) =>
{
string name = request.RouteParameters["name"].GetString();
string surname = request.RouteParameters["surname"].GetString();
return new HttpResponse($"Hello, {name} {surname}!");
});Die RouteParameters‑Eigenschaft von HttpRequest enthält alle Informationen über die Pfadvariablen der empfangenen Anfrage.
Jeder vom Server empfangene Pfad wird vor dem Test des Pfadmusters normalisiert, nach folgenden Regeln:
- Alle leeren Segmente werden aus dem Pfad entfernt, z. B.:
////foo//barwird zu/foo/bar. - Die Pfad‑Übereinstimmung ist case‑sensitive, es sei denn, Router.MatchRoutesIgnoreCase ist auf
truegesetzt.
Die Query und RouteParameters Eigenschaften von HttpRequest geben ein StringValueCollection Objekt zurück, wobei jede indizierte Eigenschaft ein nicht‑null StringValue liefert, das als Option/Monade verwendet werden kann, um seinen Rohwert in ein verwaltetes Objekt zu konvertieren.
Das nachstehende Beispiel liest den Routenparameter „id“ und erhält daraus ein Guid. Ist der Parameter kein gültiges Guid, wird eine Ausnahme geworfen, und ein 500‑Fehler wird an den Client zurückgegeben, wenn der Server nicht Router.CallbackErrorHandler behandelt.
mainRouter.MapGet("/user/<id>", (request) =>
{
Guid id = request.RouteParameters["id"].GetGuid();
return new HttpResponse($"User id: {id}");
});Hinweis
Pfadenden / werden sowohl in Anfragen als auch in Routenpfaden ignoriert, das heißt, wenn Sie versuchen, auf eine Route zuzugreifen, die als /index/page definiert ist, können Sie sie auch mit /index/page/ erreichen.
Sie können URLs auch dazu zwingen, mit / zu enden, indem Sie HttpServerConfiguration.ForceTrailingSlash aktivieren.
Creating routes using class instances #
Sie können Routen auch dynamisch mittels Reflexion mit dem Attribut RouteAttribute definieren. Auf diese Weise werden die Instanzen einer Klasse, deren Methoden dieses Attribut implementieren, im Ziel‑Router als Routen definiert.
Damit eine Methode als Route definiert wird, muss sie mit einem [RouteAttribute] markiert sein, etwa dem Attribut selbst oder einem RouteGetAttribute. Die Methode kann statisch, instanziiert, öffentlich oder privat sein. Verwenden Sie MapInstance, wenn Sie Instanz‑ und statische Routenmethoden aus einem Objekt zuordnen möchten. Verwenden Sie MapType, wenn Sie nur statische Routenmethoden aus einem Typ zuordnen möchten.
public class MyController
{
// passt zu GET /
[RouteGet]
HttpResponse Index(HttpRequest request)
{
HttpResponse res = new HttpResponse();
res.Content = new StringContent("Index!");
return res;
}
// statische Methoden funktionieren ebenfalls
[RouteGet("/hello")]
static HttpResponse Hello(HttpRequest request)
{
HttpResponse res = new HttpResponse();
res.Content = new StringContent("Hello world!");
return res;
}
}Die folgende Zeile definiert sowohl die Index‑ als auch die Hello‑Methoden von MyController als Routen, da beide als Routen markiert sind und eine Instanz der Klasse bereitgestellt wurde, nicht ihr Typ. Wäre stattdessen ihr Typ übergeben worden, würden nur die statischen Methoden definiert werden.
var myController = new MyController();
mainRouter.MapInstance(myController);Um nur statische Routenmethoden aus einem Typ zuzuordnen, verwenden Sie:
mainRouter.MapType<MyController>();Seit Sisk Version 0.16 ist es möglich, AutoScan zu aktivieren, wodurch nach benutzerdefinierten Klassen gesucht wird, die RouterModule implementieren, und diese automatisch dem Router zugeordnet werden. Dies wird bei AOT‑Kompilierung nicht unterstützt.
mainRouter.AutoScanModules<ApiController>();Die obige Anweisung sucht nach allen Typen, die ApiController implementieren, jedoch nicht den Typ selbst. Die beiden optionalen Parameter geben an, wie die Methode nach diesen Typen sucht. Das erste Argument bezeichnet die Assembly, in der nach den Typen gesucht wird, und das zweite gibt an, wie die Typen definiert werden.
Regex routes #
Anstatt die standardmäßigen HTTP‑Pfad‑Matching‑Methoden zu verwenden, können Sie eine Route definieren, deren Pfad als regulärer Ausdruck mit der Klasse RegexRoute interpretiert wird. Regex‑Routen sind dynamische Routen.
mainRouter.Map(new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", request =>
{
return new HttpResponse("hello, world");
}));Sie können auch Gruppen aus dem Regex‑Muster in den Inhalt von HttpRequest.RouteParameters erfassen:
public class MyController
{
[RegexRoute(RouteMethod.Get, @"/uploads/(?<filename>.*\.(jpeg|jpg|png))")]
static HttpResponse RegexRoute(HttpRequest request)
{
string filename = request.RouteParameters["filename"].GetString();
return new HttpResponse().WithContent($"Acessing file {filename}");
}
}Hinweis
Der reguläre Ausdruck ist nicht automatisch verankert: ein Muster wie /uploads matcht auch /files/uploads/a. Verwenden Sie ^ und $, um den gesamten Pfad zu matchen.
Wichtig
Seit Sisk 1.7 kann die Eigenschaft Route.UseRegex nicht mehr gesetzt werden. Sie ist veraltet und gibt nur noch an, ob die Route eine RegexRoute ist. Die UseRegex‑Eigenschaft von Routen‑Attributen, wie [Route(RouteMethod.Get, "...", UseRegex = true)], und das [RegexRoute]‑Attribut funktionieren weiterhin.
Um Code zu migrieren, der Regex‑Routen mit Route.UseRegex erstellt hat, ersetzen Sie die Route durch eine RegexRoute:
// before 1.7
mainRouter.Map(new Route(RouteMethod.Get, @"^/items/(?<id>\d+)$", action) { UseRegex = true });
// 1.7 and later
mainRouter.Map(new RegexRoute(RouteMethod.Get, @"^/items/(?<id>\d+)$", action));Prefix routes #
Eine PrefixRoute stimmt mit ihrem Pfad und jedem darunter liegenden Pfad überein, indem ganze Pfadsegmente verglichen werden. Sie ist die von Router.MapFileSystem verwendete Route. Präfix‑Routen sind dynamische Routen.
// stimmt mit /docs, /docs/ und /docs/any/nested/path überein, aber nicht mit /docsfoo
mainRouter.Map(new PrefixRoute(RouteMethod.Get, "/docs", null, request =>
{
return new HttpResponse($"Requested {request.Path}");
}, null));Custom route matching #
Sie können eigene Matching‑Regeln erstellen, indem Sie von Route erben und Route.Match überschreiben. Die Methode erhält den Anfrage‑Pfad und den Router, der die Route abgleicht, und gibt ein RouteMatch mit dem Ergebnis und optional den erfassten Routenparametern zurück.
Der Router prüft die HTTP‑Methode erst, nachdem Match erfolgreich war, daher sollte Match nur den Pfad testen. Dadurch funktionieren die 405 Method Not Allowed und OPTIONS‑Antworten für benutzerdefinierte Routen.
// passt zu jedem Pfad, der mit der angegebenen Dateierweiterung endet, z. B. /reports/2024.csv
public sealed class ExtensionRoute : Route
{
public ExtensionRoute(string extension, RouteAction action)
: base(RouteMethod.Get, extension, action)
{
}
// diese Route folgt keinem Pfadmuster
public override bool IsStatic => false;
// der Pfad ist kein Pfadmuster, daher kann er nicht auf seine Trailing‑Slash‑Form umgeleitet werden
public override bool AllowRewrites => false;
public override RouteMatch Match(string requestPath, Router router)
{
StringComparison comparison = router.MatchRoutesIgnoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
if (!requestPath.EndsWith(Path, comparison))
return RouteMatch.NotMatched;
var parameters = new NameValueCollection
{
["file"] = requestPath[..^Path.Length]
};
return new RouteMatch(true, parameters);
}
}mainRouter.Map(new ExtensionRoute(".csv", request =>
{
string file = request.RouteParameters["file"].GetString();
return new HttpResponse($"Exporting {file} as CSV");
}));Beim Überschreiben von Match sollten Sie Folgendes beachten:
- Überschreiben Sie Route.IsStatic, um
falsezurückzugeben, wann immer die Route nicht strikt einem Pfadmuster folgt. Andernfalls behandelt der Router ihrPathals Pfadmuster bei der Kollisionsprüfung und beim Anwenden von Router.Prefix, was zu falschen Kollisionen oder ungültigen Pfaden führen kann. - Überschreiben Sie Route.AllowRewrites, um
falsezurückzugeben, wenn der Routenpfad nicht durch HttpServerConfiguration.ForceTrailingSlash auf seine Trailing‑Slash‑Form umgeleitet werden kann. Matchwird für jede Anfrage aufgerufen, die die Route erreicht, möglicherweise gleichzeitig von mehreren Threads. Vermeiden Sie Allokationen, wenn die Route nicht passt, und halten Sie jeglichen zwischengespeicherten Zustand thread‑sicher. Geben Sie RouteMatch.NotMatched für erfolglose Übereinstimmungen zurück.- Berücksichtigen Sie Router.MatchRoutesIgnoreCase über den Parameter
router.
Prefixing routes #
Sie können allen Routen in einer Klasse oder einem Modul mit dem Attribut RoutePrefix einen Präfix hinzufügen und den Präfix als Zeichenkette festlegen.
Siehe das nachstehende Beispiel, das die BREAD‑Architektur (Browse, Read, Edit, Add und Delete) verwendet:
[RoutePrefix("/api/users")]
public class UsersController
{
// GET /api/users
[RouteGet]
public async Task<HttpResponse> Browse()
{
...
}
// GET /api/users/<id>
[RouteGet("/<id>")]
public async Task<HttpResponse> Read()
{
...
}
// PATCH /api/users/<id>
[RoutePatch("/<id>")]
public async Task<HttpResponse> Edit()
{
...
}
// POST /api/users
[RoutePost]
public async Task<HttpResponse> Add()
{
...
}
// DELETE /api/users/<id>
[RouteDelete("/<id>")]
public async Task<HttpResponse> Delete()
{
...
}
}Im obigen Beispiel wird der HttpResponse‑Parameter weggelassen, zugunsten der Verwendung über den globalen Kontext HttpContext.Current. Lesen Sie mehr im folgenden Abschnitt.
Routes without request parameter #
Routen können ohne den HttpRequest‑Parameter definiert werden und dennoch ist es möglich, die Anfrage und ihre Komponenten im Anforderungskontext zu erhalten. Betrachten wir eine Abstraktion ControllerBase, die als Grundlage für alle Controller einer API dient und die Request‑Eigenschaft bereitstellt, um den aktuellsten HttpRequest zu erhalten.
public abstract class ControllerBase
{
// holt die Anfrage aus dem aktuellen Thread
public HttpRequest Request { get => HttpContext.Current.Request; }
// die nachfolgende Zeile ruft, wenn sie aufgerufen wird, die Datenbank aus der aktuellen HTTP‑Sitzung ab, oder erstellt eine neue, falls sie nicht existiert
public DbContext Database { get => HttpContext.Current.RequestBag.GetOrAdd<DbContext>(); }
}Und damit alle Nachfolger sie verwenden können, ohne den Anfrage‑Parameter:
[RoutePrefix("/api/users")]
public class UsersController : ControllerBase
{
[RoutePost]
public async Task<HttpResponse> Create()
{
// reads the JSON data from the current request
UserCreationDto? user = await Request.GetJsonContentAsync<UserCreationDto>();
...
Database.Users.Add(user);
return new HttpResponse(201);
}
}Weitere Details zum aktuellen Kontext und zur Dependency Injection finden Sie im dependency injection‑Tutorial.
Any method routes #
Sie können eine Route definieren, die nur anhand ihres Pfads abgeglichen wird und die HTTP‑Methode überspringt. Das kann nützlich sein, um die Methodenvalidierung innerhalb des Routen‑Callbacks durchzuführen.
// will match / on any HTTP method
mainRouter.MapAny("/", callbackFunction);RouteMethod.Any umfasst GET, POST, PUT, PATCH, DELETE, QUERY, HEAD und OPTIONS.
QUERY method routes #
Sisk unterstützt die HTTP‑Methode QUERY, eine sichere und idempotente Methode, die die Abfrage im Anfrage‑Body überträgt. Verwenden Sie Router.MapQuery, Route.Query, RouteMethod.Query oder das RouteQuery‑Attribut, um diese Routen zu definieren:
mainRouter.MapQuery("/search", request =>
{
string filter = request.Body;
return new HttpResponse($"Searching for {filter}");
});public class SearchController
{
[RouteQuery("/products")]
static HttpResponse SearchProducts(HttpRequest request)
{
return new HttpResponse($"Searching for {request.Body}");
}
}Der Header Accept-Query, der die in QUERY‑Anfragen akzeptierten Medientypen ankündigt, ist über HttpHeaderCollection.AcceptQuery und HttpKnownHeaderNames.AcceptQuery verfügbar.
Any path routes #
Routen für beliebige Pfade prüfen jeden vom HTTP‑Server empfangenen Pfad, abhängig von der getesteten Routen‑Methode. Wenn die Routen‑Methode RouteMethod.Any ist und die Route Route.AnyPath in ihrem Pfadausdruck verwendet, hört diese Route auf alle Anfragen des HTTP‑Servers, und es können keine weiteren Routen definiert werden.
// the following route will match all POST requests
mainRouter.Map(RouteMethod.Post, Route.AnyPath, callbackFunction);Ignore case route matching #
Standardmäßig ist die Interpretation von Routen mit Anfragen case‑sensitive. Um die Groß‑/Kleinschreibung zu ignorieren, aktivieren Sie diese Option:
mainRouter.MatchRoutesIgnoreCase = true;Dies aktiviert außerdem die Option RegexOptions.IgnoreCase für Regex‑Routen und den case‑insensitiven Vergleich für Präfix‑Routen.
Not Found (404) callback handler #
Sie können einen benutzerdefinierten Callback erstellen, wenn eine Anfrage keine bekannte Route trifft.
mainRouter.NotFoundErrorHandler = () =>
{
return new HttpResponse(404)
{
// Seit v0.14
Content = new HtmlContent("<h1>Not found</h1>")
// ältere Versionen
Content = new StringContent("<h1>Not found</h1>", Encoding.UTF8, "text/html")
};
};Method not allowed (405) callback handler #
Sie können auch einen benutzerdefinierten Callback erstellen, wenn eine Anfrage mit ihrem Pfad übereinstimmt, aber nicht mit der Methode.
mainRouter.MethodNotAllowedErrorHandler = (context) =>
{
return new HttpResponse(405)
{
Content = new StringContent($"Method not allowed for this route.")
};
};Error Handling #
Ausnahmen können innerhalb des Anforderungs‑Lebenszyklus ausgelöst werden, der vom Pre‑Execution‑Request‑Handler über die Router‑Aktion bis zu den Post‑Execution‑Request‑Handlern und Value‑Handlern reicht. Diese Ausnahmen werden durch den Mechanismus verwaltet:
- Wenn HttpServerConfiguration.ThrowExceptions
trueist, werden Ausnahmen normal geworfen und nicht von Sisk abgefangen, und der HTTP‑Server kann unterbrochen werden, wenn die Ausnahme nicht gefangen wird. - Wenn HttpServerConfiguration.ThrowExceptions
falseist, werden Ausnahmen von Sisk abgefangen und behandelt. Danach, fallsRouter.CallbackErrorHandlerdefiniert ist, wird er mit der abgefangenen Ausnahme und dem Anforderungskontext aufgerufen, und er wird nicht an die Standard‑Fehlerausgabe weitergeleitet. IstRouter.CallbackErrorHandlernicht definiert, wird die Ausnahme an die Standard‑Fehlerausgabe weitergeleitet, und der Client erhält eine HTTP‑500‑Fehlerantwort. Ist die Standard‑Fehlerausgabe nicht definiert, wird der Fehler stillschweigend ignoriert.
Hinweis: Innerhalb von Router.CallbackErrorHandler können Sie den Log‑Modus für Fehler, Zugriffs‑Log, beides oder keinen festlegen und das Standard‑Log‑Schreibverhalten ändern:
router.CallbackErrorHandler = (ex, ctx) =>
{
ctx.LogMode = LogOutput.Both; // override log mode to log the error in both access and error logs
}Internal error handler #
Routen‑Callbacks können während der Serverausführung Fehler werfen. Wenn sie nicht korrekt behandelt werden, kann die Gesamtfunktion des HTTP‑Servers beendet werden. Der Router verfügt über einen Callback für den Fall, dass ein Routen‑Callback fehlschlägt und eine Service‑Unterbrechung verhindert.
Diese Methode ist nur erreichbar, wenn ThrowExceptions auf false gesetzt ist.
mainRouter.CallbackErrorHandler = (ex, context) =>
{
return new HttpResponse(500)
{
Content = new StringContent($"Error: {ex.Message}")
};
};