Routing w ASP.NET Core
Ryan Nowak, Kirk Larkin i Rick Anderson
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Routing jest odpowiedzialny za dopasowywanie przychodzących żądań HTTP i wysyłanie tych żądań do punktów końcowych wykonywalnych aplikacji. Punkty końcowe to jednostki aplikacji kodu obsługi żądań wykonywalnych. Punkty końcowe są definiowane w aplikacji i konfigurowane podczas uruchamiania aplikacji. Proces dopasowywania punktu końcowego może wyodrębnić wartości z adresu URL żądania i podać te wartości do przetwarzania żądań. Korzystając z informacji o punkcie końcowym z aplikacji, routing jest również w stanie wygenerować adresy URL mapujące na punkty końcowe.
Aplikacje mogą konfigurować routing przy użyciu:
- Kontrolery
- Razor Pages
- SignalR
- usługi gRPC
- Oprogramowanie pośredniczące z obsługą punktu końcowego, takie jak kontrole kondycji.
- Delegaty i lambdy zarejestrowane przy użyciu routingu.
W tym artykule opisano szczegóły niskiego poziomu dotyczące routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:
- W przypadku kontrolerów zobacz Routing do akcji kontrolera w ASP.NET Core.
- Aby zapoznać Razor się z konwencjami stron, zobacz Razor Strony route and app conventions in ASP.NET Core (Konwencje tras stron i aplikacji w programie ASP.NET Core).
- Aby uzyskać Blazor wskazówki dotyczące routingu, które dodaje lub zastępuje wskazówki zawarte w tym artykule, zobacz ASP.NET Routing i nawigacja podstawowaBlazor.
Podstawy routingu
Poniższy kod przedstawia podstawowy przykład routingu:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy przykład zawiera pojedynczy punkt końcowy przy użyciu MapGet metody :
- Po wysłaniu żądania HTTP
GET
do głównego adresu URL/
:- Delegat żądania jest wykonywany.
Hello World!
jest zapisywany w odpowiedzi HTTP.
- Jeśli metoda żądania nie
GET
jest lub główny adres URL nie/
jest , nie jest zgodna trasa i zwracany jest http 404.
Routing używa pary oprogramowania pośredniczącego zarejestrowanego przez UseRouting i UseEndpoints:
UseRouting
Dodaje trasę zgodną z potokiem oprogramowania pośredniczącego. To oprogramowanie pośredniczące analizuje zestaw punktów końcowych zdefiniowanych w aplikacji i wybiera najlepsze dopasowanie na podstawie żądania.UseEndpoints
Dodaje wykonywanie punktu końcowego do potoku oprogramowania pośredniczącego. Uruchamia delegata skojarzonego z wybranym punktem końcowym.
Aplikacje zazwyczaj nie muszą wywoływać ani UseRouting
UseEndpoints
. WebApplicationBuilder Konfiguruje potok oprogramowania pośredniczącego, który opakowuje oprogramowanie pośredniczące dodane za Program.cs
pomocą poleceń UseRouting
i UseEndpoints
. Jednak aplikacje mogą zmieniać kolejność, w jakiej UseRouting
i UseEndpoints
uruchamiać, wywołując te metody jawnie. Na przykład następujący kod wykonuje jawne wywołanie metody UseRouting
:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
Powyższy kod:
- Wywołanie w celu
app.Use
zarejestrowania niestandardowego oprogramowania pośredniczącego uruchamianego na początku potoku. - Wywołanie w celu
UseRouting
skonfigurowania odpowiedniego oprogramowania pośredniczącego trasy do uruchomienia po niestandardowym oprogramowaniem pośredniczącym. - Punkt końcowy zarejestrowany
MapGet
przy użyciu jest uruchamiany na końcu potoku.
Jeśli poprzedni przykład nie zawierał wywołania metody UseRouting
, niestandardowe oprogramowanie pośredniczące zostanie uruchomione po dopasowaniu trasy oprogramowania pośredniczącego.
Uwaga: trasy dodane bezpośrednio do WebApplication wykonania na końcu potoku.
Punkty końcowe
Metoda MapGet
służy do definiowania punktu końcowego. Punkt końcowy to coś, co może być następujące:
- Wybrane przez dopasowanie adresu URL i metody HTTP.
- Wykonywane przez uruchomienie delegata.
Punkty końcowe, które mogą być dopasowane i wykonywane przez aplikację, są konfigurowane w programie UseEndpoints
. Na przykład metody MapGet, MapPosti podobne łączą delegatów żądań z systemem routingu. Dodatkowe metody mogą służyć do łączenia funkcji platformy ASP.NET Core z systemem routingu:
- MapRazorPages for Razor Pages
- MapControllers dla kontrolerów
- MapHub THub<> forSignalR
- MapGrpcService TService<> dla gRPC
W poniższym przykładzie przedstawiono routing z bardziej zaawansowanym szablonem trasy:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
Ciąg /hello/{name:alpha}
jest szablonem trasy. Szablon trasy służy do konfigurowania sposobu dopasowania punktu końcowego. W takim przypadku szablon jest zgodny z następującymi elementami:
- Adres URL podobny do
/hello/Docs
- Każda ścieżka adresu URL rozpoczynająca się
/hello/
od sekwencji znaków alfabetycznych.:alpha
stosuje ograniczenie trasy, które pasuje tylko do znaków alfabetycznych. Ograniczenia tras zostały wyjaśnione w dalszej części tego artykułu.
Drugi segment ścieżki adresu URL: {name:alpha}
- Jest powiązany z parametrem
name
. - Jest przechwytywany i przechowywany w pliku HttpRequest.RouteValues.
W poniższym przykładzie przedstawiono routing z kontrolą kondycji i autoryzacją:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
W poprzednim przykładzie pokazano, jak:
- Oprogramowanie pośredniczące autoryzacji może być używane z routingiem.
- Punkty końcowe mogą służyć do konfigurowania zachowania autoryzacji.
Wywołanie MapHealthChecks dodaje punkt końcowy kontroli kondycji. Łączenie RequireAuthorization łańcucha do tego wywołania powoduje dołączenie zasad autoryzacji do punktu końcowego.
Wywoływanie UseAuthentication i UseAuthorization dodawanie oprogramowania pośredniczącego uwierzytelniania i autoryzacji. Te oprogramowanie pośredniczące są umieszczane między elementami UseRouting i UseEndpoints
, dzięki czemu mogą:
- Zobacz, który punkt końcowy został wybrany przez
UseRouting
element . - Przed wysłaniem do punktu końcowego zastosuj zasady UseEndpoints autoryzacji.
Metadane punktu końcowego
W poprzednim przykładzie istnieją dwa punkty końcowe, ale tylko punkt końcowy kontroli kondycji ma dołączone zasady autoryzacji. Jeśli żądanie pasuje do punktu końcowego sprawdzania kondycji, /healthz
zostanie wykonane sprawdzanie autoryzacji. Pokazuje to, że punkty końcowe mogą mieć dołączone dodatkowe dane. Te dodatkowe dane są nazywane metadanymi punktu końcowego:
- Metadane mogą być przetwarzane przez oprogramowanie pośredniczące obsługujące routing.
- Metadane mogą być dowolnego typu platformy .NET.
Pojęcia dotyczące routingu
System routingu opiera się na potoku oprogramowania pośredniczącego, dodając zaawansowaną koncepcję punktu końcowego . Punkty końcowe reprezentują jednostki funkcjonalności aplikacji, które różnią się od siebie pod względem routingu, autoryzacji i dowolnej liczby systemów ASP.NET Core.
definicja punktu końcowego platformy ASP.NET Core
Punkt końcowy platformy ASP.NET Core to:
- Plik wykonywalny: ma element RequestDelegate.
- Rozszerzalne: ma kolekcję metadanych .
- Można wybrać: opcjonalnie zawiera informacje o routingu.
- Wyliczenie: kolekcja punktów końcowych może być wyświetlana przez pobranie elementu EndpointDataSource z di.
Poniższy kod pokazuje, jak pobrać i sprawdzić punkt końcowy pasujący do bieżącego żądania:
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.");
Punkt końcowy, jeśli został wybrany, można pobrać z pliku HttpContext
. Jego właściwości można sprawdzić. Obiekty punktu końcowego są niezmienne i nie można ich modyfikować po utworzeniu. Najczęstszym typem RouteEndpointpunktu końcowego jest . RouteEndpoint
zawiera informacje, które umożliwiają wybranie go przez system routingu.
W poprzednim kodzie aplikacja. Użyj konfiguracji wbudowanego oprogramowania pośredniczącego.
Poniższy kod pokazuje, że w zależności od tego, gdzie app.Use
jest wywoływana w potoku, punkt końcowy może nie być:
// 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);
});
Poprzedni przykład dodaje Console.WriteLine
instrukcje, które wyświetlają, czy wybrano punkt końcowy. Aby uzyskać czytelność, przykład przypisuje nazwę wyświetlaną do podanego /
punktu końcowego.
Powyższy przykład obejmuje również wywołania i UseRouting
UseEndpoints
sterowanie dokładnie tym, kiedy oprogramowanie pośredniczące działa w potoku.
Uruchomienie tego kodu z adresem URL wyświetlania /
:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Uruchomienie tego kodu przy użyciu dowolnego innego adresu URL powoduje wyświetlenie:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Te dane wyjściowe pokazują, że:
- Punkt końcowy ma zawsze wartość null przed
UseRouting
wywołaniem. - Jeśli zostanie znalezione dopasowanie, punkt końcowy nie ma wartości null między
UseRouting
i UseEndpoints. - Oprogramowanie
UseEndpoints
pośredniczące jest terminalem po znalezieniu dopasowania. Oprogramowanie pośredniczące terminala jest zdefiniowane w dalszej części tego artykułu. - Oprogramowanie pośredniczące po
UseEndpoints
wykonaniu tylko wtedy, gdy nie zostanie znalezione dopasowanie.
Oprogramowanie UseRouting
pośredniczące używa SetEndpoint metody w celu dołączenia punktu końcowego do bieżącego kontekstu. Można zastąpić UseRouting
oprogramowanie pośredniczące logiką niestandardową i nadal korzystać z zalet korzystania z punktów końcowych. Punkty końcowe są typami pierwotnymi niskiego poziomu, takimi jak oprogramowanie pośredniczące, i nie są powiązane z implementacją routingu. Większość aplikacji nie musi zastępować UseRouting
logiką niestandardową.
Oprogramowanie UseEndpoints
pośredniczące jest przeznaczone do użycia w parze z oprogramowaniem pośredniczącym UseRouting
. Podstawowa logika do wykonania punktu końcowego nie jest skomplikowana. Użyj GetEndpoint polecenia , aby pobrać punkt końcowy, a następnie wywołać jego RequestDelegate właściwość.
Poniższy kod pokazuje, jak oprogramowanie pośredniczące może wpływać na routing lub reagować na nie:
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 { }
W poprzednim przykładzie przedstawiono dwie ważne pojęcia:
- Oprogramowanie pośredniczące może działać przed
UseRouting
zmodyfikowaniem danych, na których działa routing.- Zwykle oprogramowanie pośredniczące, które pojawia się przed zmodyfikowaniem routingu, modyfikuje niektóre właściwości żądania, takie jak UseRewriter, UseHttpMethodOverridelub UsePathBase.
- Oprogramowanie pośredniczące może działać między elementami
UseRouting
i UseEndpoints w celu przetworzenia wyników routingu przed wykonaniem punktu końcowego.- Oprogramowanie pośredniczące uruchamiane między elementami
UseRouting
iUseEndpoints
:- Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
- Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami
UseAuthorization
iUseCors
.
- Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.
- Oprogramowanie pośredniczące uruchamiane między elementami
Powyższy kod przedstawia przykład niestandardowego oprogramowania pośredniczącego obsługującego zasady poszczególnych punktów końcowych. Oprogramowanie pośredniczące zapisuje dziennik inspekcji dostępu do poufnych danych w konsoli. Oprogramowanie pośredniczące można skonfigurować do inspekcji punktu końcowego za RequiresAuditAttribute
pomocą metadanych. W tym przykładzie pokazano wzorzec zgody, w którym są poddawane inspekcji tylko punkty końcowe oznaczone jako poufne. Tę logikę można zdefiniować w odwrotnej kolejności, przeprowadzając inspekcję wszystkich elementów, które nie są oznaczone jako bezpieczne, na przykład. System metadanych punktu końcowego jest elastyczny. Ta logika może być zaprojektowana w dowolny sposób odpowiednio do przypadku użycia.
Powyższy przykładowy kod ma na celu zademonstrowanie podstawowych pojęć dotyczących punktów końcowych. Przykład nie jest przeznaczony do użytku produkcyjnego. Bardziej kompletna wersja oprogramowania pośredniczącego dziennika inspekcji:
- Zaloguj się do pliku lub bazy danych.
- Dołącz szczegóły, takie jak użytkownik, adres IP, nazwa poufnego punktu końcowego i inne.
Metadane RequiresAuditAttribute
zasad inspekcji są definiowane jako Attribute
łatwiejsze w użyciu z platformami opartymi na klasach, takimi jak kontrolery i SignalR. W przypadku używania trasy do kodu:
- Metadane są dołączane do interfejsu API konstruktora.
- Struktury oparte na klasach obejmują wszystkie atrybuty odpowiedniej metody i klasy podczas tworzenia punktów końcowych.
Najlepszym rozwiązaniem dla typów metadanych jest zdefiniowanie ich jako interfejsów lub atrybutów. Interfejsy i atrybuty umożliwiają ponowne użycie kodu. System metadanych jest elastyczny i nie nakłada żadnych ograniczeń.
Porównanie oprogramowania pośredniczącego terminalu z routingiem
W poniższym przykładzie pokazano zarówno oprogramowanie pośredniczące terminala, jak i routing:
// 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.");
Styl oprogramowania pośredniczącego pokazany za pomocą Approach 1:
programu to oprogramowanie pośredniczące terminalu. Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ wykonuje zgodną operację:
- Zgodna operacja w poprzednim przykładzie dotyczy
Path == "/"
oprogramowania pośredniczącego iPath == "/Routing"
routingu. - Gdy dopasowanie zakończy się pomyślnie, wykonuje pewne funkcje i zwraca, zamiast wywoływania oprogramowania pośredniczącego
next
.
Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ kończy wyszukiwanie, wykonuje niektóre funkcje, a następnie zwraca.
Poniższa lista porównuje oprogramowanie pośredniczące terminala z routingiem:
- Obie metody umożliwiają zakończenie potoku przetwarzania:
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
next
- Punkty końcowe są zawsze terminalne.
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
- Oprogramowanie pośredniczące terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
- Punkty końcowe są wykonywane w pozycji UseEndpoints.
- Oprogramowanie pośredniczące terminalu umożliwia dowolnemu kodowi określenie, kiedy oprogramowanie pośredniczące jest zgodne:
- Niestandardowy kod dopasowania trasy może być pełny i trudny do poprawnego zapisu.
- Routing udostępnia proste rozwiązania dla typowych aplikacji. Większość aplikacji nie wymaga niestandardowego kodu dopasowania tras.
- Interfejs punktów końcowych z oprogramowaniem pośredniczącym, takim jak
UseAuthorization
iUseCors
.- Używanie oprogramowania pośredniczącego terminalu z
UseAuthorization
oprogramowaniem pośredniczącym lubUseCors
wymaga ręcznego łączenia się z systemem autoryzacji.
- Używanie oprogramowania pośredniczącego terminalu z
Punkt końcowy definiuje oba:
- Pełnomocnik do przetwarzania żądań.
- Kolekcja dowolnych metadanych. Metadane służą do implementowania zagadnień krzyżowych na podstawie zasad i konfiguracji dołączonych do każdego punktu końcowego.
Oprogramowanie pośredniczące terminalu może być skutecznym narzędziem, ale może wymagać:
- Znaczna ilość kodowania i testowania.
- Ręczna integracja z innymi systemami w celu osiągnięcia żądanego poziomu elastyczności.
Przed napisaniem oprogramowania pośredniczącego terminalu rozważ integrację z routingiem.
Istniejące oprogramowanie pośredniczące terminalu integrujące się z usługą Map lub MapWhen zwykle może zostać przekształcone w punkt końcowy obsługujący routing. MapHealthChecks demonstruje wzorzec oprogramowania routera:
- Napisz metodę rozszerzenia w pliku IEndpointRouteBuilder.
- Utwórz zagnieżdżony potok oprogramowania pośredniczącego przy użyciu polecenia CreateApplicationBuilder.
- Dołącz oprogramowanie pośredniczące do nowego potoku. W tym przypadku . UseHealthChecks
- Build potok oprogramowania pośredniczącego do pliku RequestDelegate.
- Wywołaj
Map
i podaj nowy potok oprogramowania pośredniczącego. - Zwróć obiekt konstruktora dostarczony przez
Map
metodę rozszerzenia.
Poniższy kod przedstawia użycie narzędzi MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
W poprzednim przykładzie pokazano, dlaczego zwracanie obiektu konstruktora jest ważne. Zwracanie obiektu konstruktora umożliwia deweloperowi aplikacji konfigurowanie zasad, takich jak autoryzacja dla punktu końcowego. W tym przykładzie oprogramowanie pośredniczące kontroli kondycji nie ma bezpośredniej integracji z systemem autoryzacji.
System metadanych został utworzony w odpowiedzi na problemy napotykane przez autorów rozszerzeń przy użyciu oprogramowania pośredniczącego terminalu. Dla każdego oprogramowania pośredniczącego problematyczne jest zaimplementowanie własnej integracji z systemem autoryzacji.
Dopasowywanie adresu URL
- Jest procesem, za pomocą którego routing jest zgodny z żądaniem przychodzącym do punktu końcowego.
- Opiera się na danych w ścieżce i nagłówkach adresu URL.
- Można rozszerzyć, aby uwzględnić dowolne dane w żądaniu.
Po wykonaniu oprogramowania pośredniczącego routingu ustawia Endpoint
ona wartości i kieruje je do funkcji żądania z HttpContext bieżącego żądania:
- Wywołanie metody HttpContext.GetEndpoint pobiera punkt końcowy.
HttpRequest.RouteValues
pobiera kolekcję wartości tras.
Oprogramowanie pośredniczące uruchamiane po uruchomieniu oprogramowania pośredniczącego routingu może sprawdzić punkt końcowy i podjąć działania. Na przykład oprogramowanie pośredniczące autoryzacji może przesłuchić kolekcję metadanych punktu końcowego dla zasad autoryzacji. Po wykonaniu całego oprogramowania pośredniczącego w potoku przetwarzania żądań wywoływany jest delegat wybranego punktu końcowego.
System routingu w routingu punktów końcowych jest odpowiedzialny za wszystkie decyzje dotyczące wysyłania. Ponieważ oprogramowanie pośredniczące stosuje zasady na podstawie wybranego punktu końcowego, ważne jest, aby:
- Każda decyzja, która może mieć wpływ na wysyłanie lub stosowanie zasad zabezpieczeń, jest dokonana wewnątrz systemu routingu.
Ostrzeżenie
W przypadku zgodności z poprzednimi wersjami po wykonaniu delegata punktu końcowego kontroler lub Razor strony właściwości RouteContext.RouteData są ustawione na odpowiednie wartości na podstawie przetwarzania żądań wykonanego do tej pory.
Typ RouteContext
zostanie oznaczony jako przestarzały w przyszłej wersji:
- Przeprowadź migrację
RouteData.Values
doHttpRequest.RouteValues
. - Migrowanie
RouteData.DataTokens
w celu pobrania IDataTokensMetadata z metadanych punktu końcowego.
Dopasowywanie adresów URL działa w konfigurowalnym zestawie faz. W każdej fazie dane wyjściowe są zestawem dopasowań. Zestaw dopasowań można zawęzić dalej przez następną fazę. Implementacja routingu nie gwarantuje kolejności przetwarzania pasujących punktów końcowych. Wszystkie możliwe dopasowania są przetwarzane jednocześnie. Fazy dopasowywania adresów URL są wykonywane w następującej kolejności. ASP.NET Core:
- Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem w MatcherPolicy zestawie wystąpień.
- Używa elementu EndpointSelector , aby podjąć ostateczną decyzję z poprzedniej listy.
Lista punktów końcowych jest priorytetowa zgodnie z:
- RouteEndpoint.Order
- Pierwszeństwo szablonu trasy
Wszystkie pasujące punkty końcowe są przetwarzane w każdej fazie do momentu EndpointSelector osiągnięcia. Jest EndpointSelector
to ostatnia faza. Wybiera punkt końcowy o najwyższym priorytcie z dopasowań jako najlepszy. Jeśli istnieją inne dopasowania z tym samym priorytetem co najlepsze dopasowanie, zgłaszany jest niejednoznaczny wyjątek dopasowania.
Pierwszeństwo trasy jest obliczane na podstawie bardziej szczegółowego szablonu trasy o wyższym priorytecie. Rozważmy na przykład szablony /hello
i /{message}
:
- Oba są zgodne ze ścieżką
/hello
adresu URL . /hello
jest bardziej szczegółowy i dlatego wyższy priorytet.
Ogólnie rzecz biorąc, pierwszeństwo trasy dobrze nadaje się do wyboru najlepszego dopasowania do rodzajów schematów adresów URL używanych w praktyce. Używaj Order tylko wtedy, gdy jest to konieczne, aby uniknąć niejednoznaczności.
Ze względu na rodzaj rozszerzalności zapewnianej przez routing nie jest możliwe, aby system routingu obliczał przed upływem czasu niejednoznaczne trasy. Rozważmy przykład, taki jak szablony /{message:alpha}
tras i /{message:int}
:
- Ograniczenie
alpha
pasuje tylko do znaków alfabetycznych. - Ograniczenie
int
pasuje tylko do liczb. - Te szablony mają taki sam pierwszeństwo trasy, ale nie ma jednego adresu URL, który oba te szablony są zgodne.
- Jeśli system routingu zgłosił błąd niejednoznaczności podczas uruchamiania, zablokuje to prawidłowy przypadek użycia.
Ostrzeżenie
Kolejność operacji wewnątrz UseEndpoints nie ma wpływu na zachowanie routingu z jednym wyjątkiem. MapControllerRoute i MapAreaRoute automatycznie przypisz wartość zamówienia do swoich punktów końcowych na podstawie kolejności, w której są wywoływane. Symuluje to długotrwałe zachowanie kontrolerów bez systemu routingu zapewniające takie same gwarancje jak starsze implementacje routingu.
Routing punktów końcowych w ASP.NET Core:
- Nie ma pojęcia tras.
- Nie zapewnia gwarancji zamawiania. Wszystkie punkty końcowe są przetwarzane jednocześnie.
Pierwszeństwo szablonu trasy i kolejność wyboru punktu końcowego
Pierwszeństwo szablonu trasy to system, który przypisuje każdemu szablonowi trasy wartość na podstawie jego specyfiki. Pierwszeństwo szablonu trasy:
- Pozwala uniknąć konieczności dostosowywania kolejności punktów końcowych w typowych przypadkach.
- Próby dopasowania do zdrowych oczekiwań dotyczących zachowania routingu.
Rozważmy na przykład szablony /Products/List
i /Products/{id}
. Uzasadnione byłoby założenie, że /Products/List
jest to lepsze dopasowanie niż /Products/{id}
dla ścieżki /Products/List
adresu URL . Działa to, ponieważ segment /List
literału jest uznawany za lepszy priorytet niż segment /{id}
parametrów .
Szczegółowe informacje o sposobie działania pierwszeństwa są powiązane z definiowaniem szablonów tras:
- Szablony z większą większa część segmentów są uważane za bardziej szczegółowe.
- Segment z tekstem literału jest uznawany za bardziej szczegółowy niż segment parametrów.
- Segment parametrów z ograniczeniem jest uważany za bardziej szczegółowy niż jeden bez.
- Segment złożony jest uznawany za specyficzny jako segment parametrów z ograniczeniem.
- Parametry catch-all są najmniej specyficzne. Zobacz sekcję Catch-all w sekcji Szablony tras, aby uzyskać ważne informacje na temat tras catch-all.
Pojęcia dotyczące generowania adresów URL
Generowanie adresu URL:
- Jest procesem, za pomocą którego routing może utworzyć ścieżkę adresu URL na podstawie zestawu wartości trasy.
- Umożliwia logiczne rozdzielenie punktów końcowych i adresów URL, które do nich uzyskują dostęp.
Routing punktów końcowych obejmuje LinkGenerator interfejs API. LinkGenerator
jest pojedynczą usługą dostępną z di. Interfejs LinkGenerator
API może być używany poza kontekstem wykonywania żądania. Mvc.IUrlHelper i scenariusze, które opierają się na IUrlHelperelementach , takich jak Pomocnicy tagów, Pomocnicy HTML i Wyniki akcji, używają interfejsu LinkGenerator
API wewnętrznie, aby zapewnić możliwości generowania linków.
Generator linków jest wspierany przez koncepcję schematów adresów i adresów. Schemat adresów to sposób określania punktów końcowych, które powinny być brane pod uwagę podczas generowania linków. Na przykład scenariusze nazw tras i wartości tras, które wielu użytkowników zna z kontrolerów i Razor stron, są implementowane jako schemat adresów.
Generator linków może łączyć się z kontrolerami i Razor stronami za pomocą następujących metod rozszerzeń:
Przeciążenia tych metod akceptują argumenty, które obejmują HttpContext
element . Te metody są funkcjonalnie równoważne url.Action i Url.Page, ale oferują dodatkową elastyczność i opcje.
Metody GetPath*
są najbardziej podobne do Url.Action
metod i Url.Page
, w których generują identyfikator URI zawierający ścieżkę bezwzględną. Metody GetUri*
zawsze generują bezwzględny identyfikator URI zawierający schemat i host. Metody, które akceptują HttpContext
generowanie identyfikatora URI w kontekście wykonywania żądania. Wartości trasy otoczenia , ścieżka podstawowa adresu URL, schemat i host z wykonywanego żądania są używane, chyba że zostaną zastąpione.
LinkGenerator jest wywoływana przy użyciu adresu. Generowanie identyfikatora URI odbywa się w dwóch krokach:
- Adres jest powiązany z listą punktów końcowych pasujących do adresu.
- Każdy punkt końcowy RoutePattern jest oceniany do momentu znalezienia wzorca trasy zgodnego z podanymi wartościami. Wynikowe dane wyjściowe są łączone z innymi częściami identyfikatora URI dostarczonymi do generatora linków i zwracanymi.
Metody udostępniane przez LinkGenerator obsługę standardowych możliwości generowania linków dla dowolnego typu adresu. Najwygodniejszym sposobem korzystania z generatora linków jest użycie metod rozszerzeń, które wykonują operacje dla określonego typu adresu:
Extension, metoda | opis |
---|---|
GetPathByAddress | Generuje identyfikator URI ze ścieżką bezwzględną na podstawie podanych wartości. |
GetUriByAddress | Generuje bezwzględny identyfikator URI na podstawie podanych wartości. |
Ostrzeżenie
Zwróć uwagę na następujące konsekwencje wywoływania LinkGenerator metod:
Użyj
GetUri*
metod rozszerzeń z ostrożnością w konfiguracji aplikacji, która nie weryfikujeHost
nagłówka żądań przychodzących.Host
Jeśli nagłówek żądań przychodzących nie jest weryfikowany, niezaufane dane wejściowe żądania mogą być wysyłane z powrotem do klienta w identyfikatorach URI w widoku lub na stronie. Zalecamy skonfigurowanie serwera przez wszystkie aplikacje produkcyjne w celu zweryfikowania nagłówkaHost
pod kątem znanych prawidłowych wartości.Należy używać LinkGenerator z ostrożnością w oprogramowania pośredniczącego w połączeniu z
Map
lubMapWhen
.Map*
zmienia ścieżkę podstawową wykonywania żądania, co wpływa na dane wyjściowe generowania linków. LinkGenerator Wszystkie interfejsy API umożliwiają określenie ścieżki podstawowej. Określ pustą ścieżkę podstawową, aby cofnąć wpływ na generowanie linkówMap*
.
Przykład oprogramowania pośredniczącego
W poniższym przykładzie oprogramowanie pośredniczące używa interfejsu LinkGenerator API do utworzenia linku do metody akcji zawierającej listę produktów. Użycie generatora linków przez wstrzyknięcie go do klasy i wywołanie GenerateLink
jest dostępne dla dowolnej klasy w aplikacji:
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.");
}
}
Szablony tras
Tokeny w ramach {}
definicji parametrów trasy, które są powiązane, jeśli trasa jest zgodna. W segmencie trasy można zdefiniować więcej niż jeden parametr trasy, ale parametry trasy muszą być rozdzielone wartością literału. Na przykład:
{controller=Home}{action=Index}
nie jest prawidłową trasą, ponieważ nie ma wartości literału między {controller}
i {action}
. Parametry trasy muszą mieć nazwę i mogą mieć określone dodatkowe atrybuty.
Tekst literału inny niż parametry trasy (na przykład {id}
) i separator /
ścieżki musi być zgodny z tekstem w adresie URL. Dopasowywanie tekstu jest bez uwzględniania wielkości liter i oparte na dekodowanej reprezentacji ścieżki adresu URL. Aby dopasować ogranicznik {
parametru trasy literału lub }
, należy uruchomić ogranicznik, powtarzając znak. Na przykład {{
lub }}
.
Gwiazdka *
lub podwójna gwiazdka **
:
- Może służyć jako prefiks do parametru trasy w celu powiązania z rest identyfikatorem URI.
- Są nazywane parametrami catch-all . Na przykład :
blog/{**slug}
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
blog/
i ma dowolną wartość po niej. - Poniższa wartość
blog/
jest przypisywana do wartości trasy slug .
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
Ostrzeżenie
Parametr catch-all może być niepoprawnie zgodny z trasami z powodu błędu w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:
- Trasa typu catch-all, na przykład
{**slug}"
- Trasa catch-all nie pasuje do żądań, które powinna być zgodna.
- Usunięcie innych tras sprawia, że trasa catch-all zacznie działać.
Zobacz Usterki usługi GitHub 18677 i 16579 , na przykład przypadki, w których wystąpiła ta usterka.
Poprawka zgody na tę usterkę jest zawarta w zestawie .NET Core 3.1.301 SDK i nowszych wersjach. Poniższy kod ustawia przełącznik wewnętrzny, który naprawia tę usterkę:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Parametry catch-all mogą być również zgodne z pustym ciągiem.
Parametr catch-all ucieczki odpowiednich znaków, gdy trasa jest używana do generowania adresu URL, w tym znaków separatora /
ścieżki. Na przykład trasa foo/{*path}
z wartościami { path = "my/path" }
tras generuje foo/my%2Fpath
wartość . Zwróć uwagę na unikniętą ukośnik. Aby użyć znaków separatora ścieżki dwukierunkowej, użyj prefiksu parametru **
trasy. Trasa foo/{**path}
z elementem { path = "my/path" }
generuje foo/my/path
wartość .
Wzorce adresów URL, które próbują przechwycić nazwę pliku z opcjonalnym rozszerzeniem pliku, mają dodatkowe uwagi. Rozważmy na przykład szablon files/{filename}.{ext?}
. Gdy wartości obu filename
i ext
istnieją, oba wartości są wypełniane. Jeśli w adresie URL istnieje tylko wartość filename
, trasa jest zgodna, ponieważ końcowy .
jest opcjonalny. Następujące adresy URL pasują do tej trasy:
/files/myFile.txt
/files/myFile
Parametry trasy mogą mieć wartości domyślne wyznaczone przez określenie wartości domyślnej po nazwie parametru oddzielonej znakiem równości (=
). Na przykład {controller=Home}
definiuje Home
jako wartość domyślną dla elementu controller
. Wartość domyślna jest używana, jeśli w adresie URL parametru nie ma żadnej wartości. Parametry trasy są opcjonalne, dołączając znak zapytania (?
) na końcu nazwy parametru. Na przykład id?
. Różnica między opcjonalnymi wartościami a domyślnymi parametrami trasy to:
- Parametr trasy z wartością domyślną zawsze generuje wartość.
- Opcjonalny parametr ma wartość tylko wtedy, gdy wartość jest dostarczana przez adres URL żądania.
Parametry trasy mogą mieć ograniczenia, które muszą być zgodne z wartością trasy powiązaną z adresem URL. Dodanie :
i ograniczenie nazwy po nazwie parametru trasy określa ograniczenie wbudowane w parametrze trasy. Jeśli ograniczenie wymaga argumentów, są one ujęte w nawiasy (...)
po nazwie ograniczenia. Można określić wiele ograniczeń wbudowanych przez dołączenie innej :
nazwy i ograniczenia.
Nazwa ograniczenia i argumenty są przekazywane do IInlineConstraintResolver usługi w celu utworzenia wystąpienia IRouteConstraint do użycia w przetwarzaniu adresów URL. Na przykład szablon blog/{article:minlength(10)}
trasy określa minlength
ograniczenie z argumentem 10
. Aby uzyskać więcej informacji na temat ograniczeń tras i listy ograniczeń udostępnianych przez platformę, zobacz sekcję Ograniczenia trasy.
Parametry trasy mogą również zawierać transformatory parametrów. Transformatory parametrów przekształcają wartość parametru podczas generowania łączy i pasujących akcji i stron do adresów URL. Podobnie jak ograniczenia, transformatory parametrów można dodać w tekście do parametru trasy przez dodanie :
nazwy parametru i transformatora po nazwie parametru trasy. Na przykład szablon blog/{article:slugify}
trasy określa slugify
transformator. Aby uzyskać więcej informacji na temat transformatorów parametrów, zobacz sekcję Transformatory parametrów .
W poniższej tabeli przedstawiono przykładowe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI | Identyfikator URI żądania... |
---|---|---|
hello |
/hello |
Pasuje tylko do pojedynczej ścieżki /hello . |
{Page=Home} |
/ |
Dopasuj i ustawia wartość Page Home . |
{Page=Home} |
/Contact |
Dopasuj i ustawia wartość Page Contact . |
{controller}/{action}/{id?} |
/Products/List |
Mapuje na Products kontroler i List akcję. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapuje na Products kontroler i Details akcję z ustawioną wartościąid 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapuje na kontroler i Index metodęHome . Właściwość id jest ignorowana. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapuje na kontroler i Index metodęProducts . Właściwość id jest ignorowana. |
Użycie szablonu jest zwykle najprostszym podejściem do routingu. Ograniczenia i wartości domyślne można również określić poza szablonem trasy.
Złożone segmenty
Złożone segmenty są przetwarzane przez dopasowanie ograniczników literału od prawej do lewej w sposób niechłanny . Na przykład [Route("/a{b}c{d}")]
jest to złożony segment.
Złożone segmenty działają w określony sposób, aby można było z nich korzystać pomyślnie. W przykładzie w tej sekcji pokazano, dlaczego złożone segmenty działają prawidłowo tylko wtedy, gdy tekst ogranicznika nie pojawia się wewnątrz wartości parametrów. Użycie wyrażenia regularnego, a następnie ręczne wyodrębnienie wartości jest wymagane w przypadku bardziej złożonych przypadków.
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Jest to podsumowanie kroków, które routing wykonuje przy użyciu szablonu /a{b}c{d}
i ścieżki /abcd
adresu URL . Służy |
do wizualizacji sposobu działania algorytmu:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/abcd
jest wyszukiwany z prawej strony i znajduje/ab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/ab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - Brak pozostałego tekstu i brak pozostałego szablonu trasy, więc jest to dopasowanie.
Oto przykład negatywnego przypadku przy użyciu tego samego szablonu /a{b}c{d}
i ścieżki /aabcd
adresu URL . Służy |
do wizualizowania sposobu działania algorytmu. Ten przypadek nie jest zgodny, co zostało wyjaśnione przez ten sam algorytm:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/aabcd
jest wyszukiwany z prawej strony i znajduje/aab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/aab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/a|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - W tym momencie pozostaje tekst
a
, ale algorytm zabrakło szablonu trasy do przeanalizowania, więc nie jest to dopasowanie.
Ponieważ pasujący algorytm nie jest chciwy:
- Pasuje do najmniejszej ilości tekstu możliwego w każdym kroku.
- Każdy przypadek, w którym wartość ogranicznika pojawia się wewnątrz wartości parametrów, powoduje niezgodnie.
Wyrażenia regularne zapewniają znacznie większą kontrolę nad ich zachowaniem dopasowania.
Chciwość dopasowania, znana również jako maksymalna próba dopasowania , aby znaleźć najdłuższe możliwe dopasowanie w tekście wejściowym, który spełnia wzorzec wyrażeń regularnych . Niechłanne dopasowanie, znane również jako leniwe dopasowywanie, szuka najkrótszego możliwego dopasowania w tekście wejściowym, który spełnia wzorzec wyrażeń regularnych.
Routing z znakami specjalnymi
Routing ze znakami specjalnymi może prowadzić do nieoczekiwanych wyników. Rozważmy na przykład kontroler z następującą metodą akcji:
[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;
}
Jeśli string id
zawiera następujące zakodowane wartości, mogą wystąpić nieoczekiwane wyniki:
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
Parametry trasy nie zawsze są dekodowane za pomocą adresu URL. Ten problem może zostać rozwiązany w przyszłości. Aby uzyskać więcej informacji, zobacz ten problem z usługą GitHub;
Ograniczenia trasy
Ograniczenia trasy są wykonywane, gdy wystąpiło dopasowanie do przychodzącego adresu URL, a ścieżka adresu URL jest tokenizowana do wartości tras. Ograniczenia tras zazwyczaj sprawdzają wartość trasy skojarzoną za pośrednictwem szablonu trasy i podejmowania prawdziwej lub fałszywej decyzji o tym, czy wartość jest akceptowalna. Niektóre ograniczenia trasy używają danych poza wartością trasy, aby rozważyć, czy żądanie można kierować. Na przykład HttpMethodRouteConstraint może zaakceptować lub odrzucić żądanie na podstawie jego czasownika HTTP. Ograniczenia są używane w żądaniach routingu i generowaniu linków.
Ostrzeżenie
Nie używaj ograniczeń do walidacji danych wejściowych. Jeśli ograniczenia są używane do walidacji danych wejściowych, nieprawidłowe dane wejściowe będą skutkować odpowiedzią 404
Nie znaleziono. Nieprawidłowe dane wejściowe powinny spowodować wygenerowanie nieprawidłowego 400
żądania z odpowiednim komunikatem o błędzie. Ograniczenia trasy służą do uściślania podobnych tras, a nie sprawdzania poprawności danych wejściowych dla określonej trasy.
W poniższej tabeli przedstawiono przykładowe ograniczenia tras i ich oczekiwane zachowanie:
ograniczenie | Przykład | Przykładowe dopasowania | Uwagi |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Pasuje do dowolnej liczby całkowitej |
bool |
{active:bool} |
true , FALSE |
Dopasuj lub true false . Bez uwzględniania wielkości liter |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Pasuje do prawidłowej DateTime wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Pasuje do prawidłowej decimal wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej double wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej float wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Pasuje do prawidłowej Guid wartości |
long |
{ticks:long} |
123456789 , -123456789 |
Pasuje do prawidłowej long wartości |
minlength(value) |
{username:minlength(4)} |
Rick |
Ciąg musi mieć co najmniej 4 znaki |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Ciąg nie może zawierać więcej niż 8 znaków |
length(length) |
{filename:length(12)} |
somefile.txt |
Ciąg musi mieć długość dokładnie 12 znaków |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Ciąg musi mieć co najmniej 8 znaków i nie więcej niż 16 znaków |
min(value) |
{age:min(18)} |
19 |
Wartość całkowita musi być co najmniej 18 |
max(value) |
{age:max(120)} |
91 |
Wartość całkowita nie może być większa niż 120 |
range(min,max) |
{age:range(18,120)} |
91 |
Wartość całkowita musi być co najmniej 18, ale nie większa niż 120 |
alpha |
{name:alpha} |
Rick |
Ciąg musi składać się z co najmniej jednego znaku a -z alfabetycznego i bez uwzględniania wielkości liter. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Ciąg musi być zgodny z wyrażeniem regularnym. Zobacz porady dotyczące definiowania wyrażenia regularnego. |
required |
{name:required} |
Rick |
Służy do wymuszania, że wartość nieparametrowa jest obecna podczas generowania adresu URL |
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wiele ograniczeń rozdzielonych dwukropkami można zastosować do jednego parametru. Na przykład następujące ograniczenie ogranicza parametr do wartości całkowitej 1 lub większej:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Ostrzeżenie
Ograniczenia trasy, które weryfikują adres URL i są konwertowane na typ CLR, zawsze używają niezmiennej kultury. Na przykład konwersja na typ int
CLR lub DateTime
. Te ograniczenia zakładają, że adres URL nie jest lokalizowalny. Ograniczenia trasy dostarczone przez platformę nie modyfikują wartości przechowywanych w wartościach tras. Wszystkie wartości tras analizowane z adresu URL są przechowywane jako ciągi. Na przykład float
ograniczenie próbuje przekonwertować wartość trasy na zmiennoprzecinkowy, ale przekonwertowana wartość jest używana tylko do sprawdzania, czy można ją przekonwertować na zmiennoprzecinkowy.
Wyrażenia regularne w ograniczeniach
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wyrażenia regularne można określić jako ograniczenia wbudowane przy użyciu regex(...)
ograniczenia trasy. Metody w MapControllerRoute rodzinie akceptują również literał obiektów ograniczeń. Jeśli ten formularz jest używany, wartości ciągów są interpretowane jako wyrażenia regularne.
Poniższy kod używa wbudowanego ograniczenia wyrażeń regularnych:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
Poniższy kod używa literału obiektu do określenia ograniczenia wyrażenia regularnego:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
Struktura ASP.NET Core dodaje RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
do konstruktora wyrażeń regularnych. Zobacz RegexOptions opis tych elementów członkowskich.
Wyrażenia regularne używają ograniczników i tokenów podobnych do tych używanych przez routing i język C#. Tokeny wyrażeń regularnych muszą zostać uniknięci. Aby użyć wyrażenia ^\d{3}-\d{2}-\d{4}$
regularnego w ograniczeniu wbudowanym, użyj jednego z następujących elementów:
- Zastąp
\
znaki podane w ciągu jako\\
znaki w pliku źródłowym języka C#, aby uniknąć\
znaku ucieczki ciągu. - Literały ciągu dosłowne.
Aby uniknąć znaków {
ogranicznika parametru routingu , , [
}
, ]
, podwaja znaki w wyrażeniu, na przykład {{
, , }}
, [[
, ]]
. W poniższej tabeli przedstawiono wyrażenie regularne i jego wersję unikniętą:
Regular expression | Wyrażenie regularne o wartości ucieczki |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Wyrażenia regularne używane w routingu często zaczynają się od ^
znaku i pasują do pozycji początkowej ciągu. Wyrażenia często kończą się znakiem $
i pasują do końca ciągu. Znaki ^
i $
zapewniają, że wyrażenie regularne jest zgodne z całą wartością parametru trasy. ^
Bez znaków i $
wyrażenie regularne pasuje do dowolnego podciągu w ciągu, co jest często niepożądane. W poniższej tabeli przedstawiono przykłady i wyjaśniono, dlaczego są one zgodne lub nie są zgodne:
Wyrażenie | String | Match | Komentarz |
---|---|---|---|
[a-z]{2} |
hello | Tak | Dopasowania podciągów |
[a-z]{2} |
123abc456 | Tak | Dopasowania podciągów |
[a-z]{2} |
mz | Tak | Dopasuj wyrażenie |
[a-z]{2} |
MZ | Tak | Nie uwzględnia wielkości liter |
^[a-z]{2}$ |
hello | Nie. | Zobacz ^ i $ powyżej |
^[a-z]{2}$ |
123abc456 | Nie. | Zobacz ^ i $ powyżej |
Aby uzyskać więcej informacji na temat składni wyrażeń regularnych, zobacz .NET Framework Regular Expressions (Wyrażenia regularne programu .NET Framework).
Aby ograniczyć parametr do znanego zestawu możliwych wartości, użyj wyrażenia regularnego. Na przykład {action:regex(^(list|get|create)$)}
pasuje action
tylko do wartości trasy do list
, get
lub create
. Jeśli zostanie przekazany do słownika ograniczeń, ciąg ^(list|get|create)$
jest równoważny. Ograniczenia przekazywane w słowniku ograniczeń, które nie są zgodne z jednym ze znanych ograniczeń, są również traktowane jako wyrażenia regularne. Ograniczenia przekazywane w szablonie, które nie pasują do jednego ze znanych ograniczeń, nie są traktowane jako wyrażenia regularne.
Ograniczenia trasy niestandardowej
Ograniczenia trasy niestandardowej można utworzyć przez zaimplementowanie interfejsu IRouteConstraint . Interfejs IRouteConstraint
zawiera Matchwartość , która zwraca true
wartość, jeśli ograniczenie jest spełnione i false
w przeciwnym razie.
Ograniczenia trasy niestandardowej są rzadko potrzebne. Przed zaimplementowaniem ograniczenia trasy niestandardowej rozważ alternatywy, takie jak powiązanie modelu.
Folder ASP.NET Core Constraints zawiera dobre przykłady tworzenia ograniczeń. Na przykład GuidRouteConstraint.
Aby użyć niestandardowego IRouteConstraint
, typ ograniczenia trasy musi być zarejestrowany w aplikacji ConstraintMap w kontenerze usługi. Jest ConstraintMap
to słownik, który mapuje klucze ograniczeń trasy na IRouteConstraint
implementacje, które weryfikują te ograniczenia. Aplikację ConstraintMap
można zaktualizować w Program.cs
ramach AddRouting wywołania lub konfigurując bezpośrednio za pomocą builder.Services.Configure<RouteOptions>
polecenia RouteOptions . Na przykład:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
Powyższe ograniczenie jest stosowane w następującym kodzie:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
Implementacja elementu NoZeroesRouteConstraint
uniemożliwia 0
korzystanie z parametru trasy:
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);
}
}
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Powyższy kod ma następujące działanie:
- Zapobiega
0
w{id}
segmencie trasy. - Pokazano, aby podać podstawowy przykład implementacji ograniczenia niestandardowego. Nie należy jej używać w aplikacji produkcyjnej.
Poniższy kod jest lepszym podejściem do zapobiegania przetwarzaniu elementu zawierającego id
0
element:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
Powyższy kod ma następujące zalety w porównaniu z podejściem NoZeroesRouteConstraint
:
- Nie wymaga on ograniczenia niestandardowego.
- Zwraca on bardziej opisowy błąd, gdy parametr trasy zawiera
0
wartość .
Transformatory parametrów
Transformatory parametrów:
- Wykonaj polecenie podczas generowania linku przy użyciu polecenia LinkGenerator.
- Zaimplementuj .Microsoft.AspNetCore.Routing.IOutboundParameterTransformer
- Są konfigurowane przy użyciu polecenia ConstraintMap.
- Weź wartość trasy parametru i przekształć go w nową wartość ciągu.
- Wynik użycia przekształconej wartości w wygenerowanym linku.
Na przykład funkcja przekształcania parametrów niestandardowych slugify
we wzorcu blog\{article:slugify}
trasy z poleceniem Url.Action(new { article = "MyTestArticle" })
generuje blog\my-test-article
wartość .
Rozważmy następującą IOutboundParameterTransformer
implementację:
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();
}
}
Aby użyć transformatora parametrów we wzorcu trasy, skonfiguruj go przy użyciu ConstraintMap polecenia w pliku Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
Struktura ASP.NET Core używa funkcji przekształcania parametrów w celu przekształcenia identyfikatora URI, w którym punkt końcowy jest rozpoznawany. Na przykład transformatory parametrów przekształcają wartości tras używane do dopasowania area
wartości , , action
controller
i page
:
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
W przypadku poprzedniego szablonu trasy akcja SubscriptionManagementController.GetAll
jest zgodna z identyfikatorem URI /subscription-management/get-all
. Funkcja przekształcania parametrów nie zmienia wartości tras używanych do generowania łącza. Na przykład Url.Action("GetAll", "SubscriptionManagement")
dane wyjściowe ./subscription-management/get-all
ASP.NET Core udostępnia konwencje interfejsu API dotyczące używania transformatorów parametrów z wygenerowanymi trasami:
- Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention Konwencja MVC stosuje określony parametr transformer do wszystkich tras atrybutów w aplikacji. Transformator parametru przekształca tokeny tras atrybutu podczas ich wymiany. Aby uzyskać więcej informacji, zobacz Używanie funkcji przekształcania parametrów do dostosowywania zamiany tokenu.
- Razor Strony używają konwencji interfejsu PageRouteTransformerConvention API. Ta konwencja stosuje określony transformator parametru do wszystkich automatycznie odnalezionych Razor stron. Funkcja przekształcania parametrów przekształca segmenty Razor nazw folderów i plików tras stron. Aby uzyskać więcej informacji, zobacz Dostosowywanie tras stron przy użyciu funkcji przekształcania parametrów.
Dokumentacja generowania adresów URL
Ta sekcja zawiera odwołanie do algorytmu zaimplementowanego przez generowanie adresów URL. W praktyce większość złożonych przykładów generowania adresów URL używa kontrolerów lub Razor stron. Aby uzyskać dodatkowe informacje, zobacz routing w kontrolerach .
Proces generowania adresu URL rozpoczyna się od wywołania metody LinkGenerator.GetPathByAddress lub podobnej metody. Metoda jest dostarczana z adresem, zestawem wartości tras i opcjonalnie informacjami o bieżącym żądaniu z .HttpContext
Pierwszym krokiem jest użycie adresu w celu rozpoznania zestawu punktów końcowych kandydata przy użyciu elementu zgodnego z typem IEndpointAddressScheme<TAddress> adresu.
Po znalezieniu zestawu kandydatów przez schemat adresów punkty końcowe są uporządkowane i przetwarzane iteracyjnie, dopóki operacja generowania adresu URL nie powiedzie się. Generowanie adresu URL nie sprawdza niejednoznaczności. Zwracany pierwszy wynik jest wynikiem końcowym.
Rozwiązywanie problemów z generowaniem adresu URL przy użyciu rejestrowania
Pierwszym krokiem generowania adresu URL rozwiązywania problemów jest ustawienie poziomu rejestrowania Microsoft.AspNetCore.Routing
na TRACE
. LinkGenerator
rejestruje wiele szczegółów dotyczących jego przetwarzania, które mogą być przydatne do rozwiązywania problemów.
Aby uzyskać szczegółowe informacje na temat generowania adresów URL, zobacz Dokumentację generowania adresów URL.
Adresy
Adresy to koncepcja generowania adresów URL używana do powiązania wywołania z generatorem linków z zestawem kandydatów punktów końcowych.
Adresy to rozszerzalna koncepcja, która domyślnie zawiera dwie implementacje:
- Użyj nazwy punktu końcowego (
string
) jako adresu:- Zapewnia podobne funkcje do nazwy trasy MVC.
- IEndpointNameMetadata Używa typu metadanych.
- Usuwa podany ciąg względem metadanych wszystkich zarejestrowanych punktów końcowych.
- Zgłasza wyjątek podczas uruchamiania, jeśli wiele punktów końcowych używa tej samej nazwy.
- Zalecane do użytku ogólnego przeznaczenia poza kontrolerami i Razor stronami.
- Użyj wartości tras (RouteValuesAddress) jako adresu:
- Zapewnia podobne funkcje do kontrolerów i Razor starszej generacji adresów URL stron.
- Bardzo złożone rozszerzanie i debugowanie.
- Zapewnia implementację używaną przez
IUrlHelper
pomocników tagów, pomocników HTML, wyniki akcji itp.
Rolą schematu adresów jest skojarzenie adresu i pasujących punktów końcowych według dowolnych kryteriów:
- Schemat nazw punktów końcowych wykonuje wyszukiwanie w słowniku podstawowym.
- Schemat wartości tras ma złożony najlepszy podzbiór ustawionego algorytmu.
Wartości otoczenia i jawne wartości
Z bieżącego żądania routing uzyskuje dostęp do wartości tras bieżącego żądania HttpContext.Request.RouteValues
. Wartości skojarzone z bieżącym żądaniem są określane jako wartości otoczenia. W celu jasności dokumentacja odnosi się do wartości tras przekazywanych do metod jako jawnych wartości.
W poniższym przykładzie przedstawiono wartości otoczenia i jawne wartości. Zapewnia ona wartości otoczenia z bieżącego żądania i jawnych wartości:
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);
}
// ...
Powyższy kod ma następujące działanie:
- Zwraca
/Widget/Index/17
- Pobiera LinkGenerator przez DI.
Poniższy kod zawiera tylko jawne wartości i nie ma wartości otoczenia:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
Poprzednia metoda zwraca /Home/Subscribe/17
Poniższy kod w zwracaniu WidgetController
wartości /Widget/Subscribe/17
:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
Poniższy kod udostępnia kontroler z wartości otoczenia w bieżącym żądaniu i jawnych wartościach:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
Powyższy kod:
/Gadget/Edit/17
jest zwracany.- Url pobiera element IUrlHelper.
- Action generuje adres URL ze ścieżką bezwzględną dla metody akcji. Adres URL zawiera określoną
action
nazwę iroute
wartości.
Poniższy kod zawiera wartości otoczenia z bieżącego żądania i jawnych wartości:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
Powyższy kod ustawia wartość url
, /Edit/17
gdy strona edycji Razor zawiera następującą dyrektywę strony:
@page "{id:int}"
Jeśli strona Edycja nie zawiera szablonu "{id:int}"
trasy, url
to /Edit?id=17
.
Zachowanie wzorca MVC IUrlHelper dodaje warstwę złożoności oprócz reguł opisanych tutaj:
IUrlHelper
zawsze udostępnia wartości tras z bieżącego żądania jako wartości otoczenia.- IUrlHelper.Action zawsze kopiuje bieżące
action
wartości icontroller
trasy jako wartości jawne, chyba że zostanie zastąpione przez dewelopera. - IUrlHelper.Page zawsze kopiuje bieżącą
page
wartość trasy jako jawną wartość, chyba że zostanie zastąpiona. IUrlHelper.Page
zawsze zastępuje bieżącąhandler
wartość trasy wartościąnull
jawną, chyba że zostanie zastąpiona.
Użytkownicy są często zaskoczeni szczegółami zachowania wartości otoczenia, ponieważ MVC nie wydaje się przestrzegać własnych reguł. Ze względów historycznych i zgodności niektóre wartości tras, takie jak action
, controller
, page
i handler
mają własne zachowanie specjalne.
Równoważna funkcjonalność zapewniana przez LinkGenerator.GetPathByAction
program i LinkGenerator.GetPathByPage
duplikuje te anomalie w IUrlHelper
celu zapewnienia zgodności.
Proces generowania adresów URL
Po znalezieniu zestawu punktów końcowych kandydata algorytm generowania adresów URL:
- Przetwarza iteracyjne punkty końcowe.
- Zwraca pierwszy pomyślny wynik.
Pierwszym krokiem w tym procesie jest unieważnienie wartości trasy. Unieważnienie wartości trasy to proces, za pomocą którego routing decyduje, które wartości tras z wartości otoczenia powinny być używane i które powinny być ignorowane. Każda wartość otoczenia jest uwzględniana i połączona z jawnymi wartościami lub ignorowana.
Najlepszym sposobem myślenia o roli wartości otoczenia jest to, że próbują zapisać deweloperów aplikacji wpisywanie, w niektórych typowych przypadkach. Tradycyjnie scenariusze, w których wartości otoczenia są przydatne, są związane z MVC:
- Podczas łączenia z inną akcją w tym samym kontrolerze nie trzeba określać nazwy kontrolera.
- Podczas łączenia z innym kontrolerem w tym samym obszarze nie trzeba określać nazwy obszaru.
- Podczas łączenia z tą samą metodą akcji wartości tras nie muszą być określone.
- Podczas łączenia z inną częścią aplikacji nie chcesz przenosić wartości tras, które nie mają znaczenia w tej części aplikacji.
Wywołania do LinkGenerator
lub IUrlHelper
tego zwracania null
są zwykle spowodowane przez niezrozumienie unieważnienia wartości trasy. Rozwiąż problemy z nieprawidłową wartością trasy, określając jawnie więcej wartości trasy, aby sprawdzić, czy rozwiązuje to problem.
Unieważnienie wartości trasy działa zgodnie z założeniem, że schemat adresu URL aplikacji jest hierarchiczny, z hierarchią utworzoną od lewej do prawej. Rozważmy podstawowy szablon {controller}/{action}/{id?}
trasy kontrolera, aby uzyskać intuicyjne zrozumienie sposobu działania tej metody w praktyce. Zmiana wartości unieważnia wszystkie wartości trasy, które są wyświetlane po prawej stronie. Odzwierciedla to założenie dotyczące hierarchii. Jeśli aplikacja ma wartość otoczenia dla id
, a operacja określa inną wartość dla elementu controller
:
id
nie będzie ponownie używany, ponieważ{controller}
znajduje się po lewej{id?}
stronie .
Kilka przykładów demonstrujących tę zasadę:
- Jeśli jawne wartości zawierają wartość parametru
id
, wartość otoczenia dlaid
elementu jest ignorowana. Wartości otoczenia icontroller
action
mogą być używane. - Jeśli jawne wartości zawierają wartość parametru
action
, każda wartość otoczenia dlaaction
elementu jest ignorowana. Można użyć wartościcontroller
otoczenia. Jeśli jawna wartość parametruaction
jest inna niż wartość otoczenia dlaaction
wartości ,id
wartość nie zostanie użyta. Jeśli jawna wartość parametruaction
jest taka sama jak wartość otoczenia dlaaction
,id
można użyć wartości . - Jeśli jawne wartości zawierają wartość parametru
controller
, każda wartość otoczenia dlacontroller
elementu jest ignorowana. Jeśli jawna wartość parametrucontroller
jest inna niż wartość otoczenia dlacontroller
,action
wartości iid
nie będą używane. Jeśli jawna wartość parametrucontroller
jest taka sama jak wartość otoczenia dlacontroller
,action
można użyć wartości iid
.
Ten proces jest jeszcze bardziej skomplikowany przez istnienie tras atrybutów i dedykowanych tras konwencjonalnych. Kontroler konwencjonalnych tras, takich jak {controller}/{action}/{id?}
określanie hierarchii przy użyciu parametrów trasy. W przypadku dedykowanych tras konwencjonalnych i tras atrybutów do kontrolerów i Razor stron:
- Istnieje hierarchia wartości tras.
- Nie są one wyświetlane w szablonie.
W takich przypadkach generowanie adresów URL definiuje koncepcję wymaganych wartości . Punkty końcowe utworzone przez kontrolery i Razor strony mają wymagane wartości określone, które umożliwiają działanie unieważnienia wartości trasy.
Algorytm unieważniania wartości trasy szczegółowo:
- Wymagane nazwy wartości są łączone z parametrami trasy, a następnie przetwarzane od lewej do prawej.
- Dla każdego parametru porównywana jest wartość otoczenia i jawna wartość:
- Jeśli wartość otoczenia i jawna wartość są takie same, proces będzie kontynuowany.
- Jeśli wartość otoczenia jest obecna, a jawna wartość nie jest, wartość otoczenia jest używana podczas generowania adresu URL.
- Jeśli wartość otoczenia nie jest obecna, a jawna wartość to, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
- Jeśli wartość otoczenia i jawna wartość są obecne, a dwie wartości są różne, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
W tym momencie operacja generowania adresu URL jest gotowa do oceny ograniczeń trasy. Zestaw akceptowanych wartości jest połączony z wartościami domyślnymi parametrów, które są dostarczane do ograniczeń. Jeśli wszystkie ograniczenia przechodzą, operacja będzie kontynuowana.
Następnie akceptowane wartości mogą służyć do rozszerzania szablonu trasy. Szablon trasy jest przetwarzany:
- Od lewej do prawej.
- Każdy parametr ma zastąpioną akceptowaną wartość.
- W następujących przypadkach specjalnych:
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr ma wartość domyślną, zostanie użyta wartość domyślna.
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr jest opcjonalny, przetwarzanie będzie kontynuowane.
- Jeśli dowolny parametr trasy z prawej strony brakującego opcjonalnego parametru ma wartość, operacja kończy się niepowodzeniem.
- Ciągłe parametry domyślne i parametry opcjonalne są zwinięte tam, gdzie to możliwe.
Wartości jawnie podane, które nie pasują do segmentu trasy, są dodawane do ciągu zapytania. W poniższej tabeli przedstawiono wynik użycia szablonu {controller}/{action}/{id?}
trasy .
Wartości otoczenia | Jawne wartości | Result |
---|---|---|
controller = "Home" | action = "Informacje" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "Informacje" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Opcjonalna kolejność parametrów trasy
Opcjonalne parametry trasy muszą pochodzić po wszystkich wymaganych parametrach i literałach trasy. W poniższym kodzie id
parametry i name
muszą pochodzić po parametrze color
:
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 ?? ""}");
}
}
Problemy z unieważnieniem wartości trasy
Poniższy kod przedstawia przykład schematu generowania adresów URL, który nie jest obsługiwany przez routing:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
W poprzednim kodzie culture
parametr trasy jest używany do lokalizacji. Pragnieniem jest, culture
aby parametr zawsze był akceptowany jako wartość otoczenia. culture
Jednak parametr nie jest akceptowany jako wartość otoczenia ze względu na sposób działania wymaganych wartości:
- W szablonie
"default"
culture
trasy parametr trasy znajduje się po lewej stroniecontroller
elementu , więc zmianycontroller
nie będą unieważniaćculture
elementu . - W szablonie
"blog"
culture
trasy parametr trasy jest uznawany za znajdujący się po prawej stroniecontroller
elementu , który jest wyświetlany w wymaganych wartościach.
Analizowanie ścieżek URL za pomocą polecenia LinkParser
Klasa LinkParser dodaje obsługę analizowania ścieżki adresu URL do zestawu wartości tras. Metoda ParsePathByEndpointName przyjmuje nazwę punktu końcowego i ścieżkę adresu URL i zwraca zestaw wartości tras wyodrębnionych ze ścieżki adresu URL.
W poniższym przykładowym kontrolerze akcja GetProduct
używa szablonu api/Products/{id}
trasy i ma Name GetProduct
wartość :
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
W tej samej klasie AddRelatedProduct
kontrolera akcja oczekuje ścieżki adresu URL , pathToRelatedProduct
która może być podana jako parametr ciągu zapytania:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
W poprzednim przykładzie akcja AddRelatedProduct
wyodrębnia id
wartość trasy ze ścieżki adresu URL. Na przykład ze ścieżką /api/Products/1
relatedProductId
adresu URL wartości jest ustawiona wartość 1
. Takie podejście umożliwia klientom interfejsu API używanie ścieżek url podczas odwoływania się do zasobów bez konieczności znajomości struktury takiego adresu URL.
Konfigurowanie metadanych punktu końcowego
Poniższe linki zawierają informacje na temat konfigurowania metadanych punktu końcowego:
- Włączanie mechanizmu Cors z routingiem punktów końcowych
- Przykład IAuthorizationPolicyProvider przy użyciu atrybutu niestandardowego
[MinimumAgeAuthorize]
- Testowanie uwierzytelniania za pomocą atrybutu [Autoryzuj]
- RequireAuthorization
- Wybieranie schematu z atrybutem [Authorize]
- Stosowanie zasad przy użyciu atrybutu [Autoryzuj]
- Autoryzacja oparta na rolach w programie ASP.NET Core
Dopasowywanie hostów w trasach za pomocą elementu RequireHost
RequireHost stosuje ograniczenie do trasy, która wymaga określonego hosta. Parametr RequireHost
lub [Host] może być:
- Host:
www.domain.com
, pasujewww.domain.com
do dowolnego portu. - Host z symbolem wieloznacznymi:
*.domain.com
, pasujewww.domain.com
do ,subdomain.domain.com
lubwww.subdomain.domain.com
na dowolnym porcie. - Port:
*:5000
, pasuje do portu 5000 z dowolnym hostem. - Host i port:
www.domain.com:5000
lub*.domain.com:5000
, pasuje do hosta i portu.
Można określić wiele parametrów przy użyciu polecenia RequireHost
lub [Host]
. Ograniczenie pasuje do hostów prawidłowych dla dowolnego z parametrów. Na przykład pasuje domain.com
do [Host("domain.com", "*.domain.com")]
, www.domain.com
i subdomain.domain.com
.
Poniższy kod używa RequireHost
metody , aby wymagać określonego hosta na trasie:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
Poniższy kod używa atrybutu [Host]
na kontrolerze, aby wymagać któregokolwiek z określonych hostów:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Po zastosowaniu atrybutu [Host]
zarówno do kontrolera, jak i metody akcji:
- Używany jest atrybut akcji.
- Atrybut kontrolera jest ignorowany.
Ostrzeżenie
Interfejs API, który opiera się na nagłówku hosta, takim jak HttpRequest.Host i RequireHost, podlega potencjalnemu fałszowaniu przez klientów.
Aby zapobiec fałszowaniu hostów i portów, użyj jednej z następujących metod:
- Użyj HttpContext.Connection (ConnectionInfo.LocalPort), gdzie sprawdzane są porty.
- Zastosowanie filtrowania hostów.
Grupy tras
Metoda MapGroup rozszerzenia ułatwia organizowanie grup punktów końcowych za pomocą wspólnego prefiksu. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata które dodają metadane punktu końcowego.
Na przykład poniższy kod tworzy dwie podobne grupy punktów końcowych:
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;
}
W tym scenariuszu możesz użyć względnego adresu nagłówka Location
w 201 Created
wyniku:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Pierwsza grupa punktów końcowych będzie pasowała tylko do żądań z prefiksem /public/todos
i jest dostępna bez żadnego uwierzytelniania. Druga grupa punktów końcowych będzie pasowała tylko do żądań poprzedzonych prefiksem /private/todos
i wymaga uwierzytelniania.
QueryPrivateTodos
Fabryka filtrów punktów końcowych to funkcja lokalna, która modyfikuje parametry programu obsługi TodoDb
tras, aby umożliwić dostęp do prywatnych danych zadań do wykonania i przechowywanie ich.
Grupy tras obsługują również grupy zagnieżdżone i złożone wzorce prefiksów z parametrami i ograniczeniami trasy. W poniższym przykładzie program obsługi tras zamapowany na grupę user
może przechwytywać {org}
parametry trasy i {group}
zdefiniowane w prefiksach grup zewnętrznych.
Prefiks może być również pusty. Może to być przydatne w przypadku dodawania metadanych lub filtrów punktu końcowego do grupy punktów końcowych bez zmiany wzorca trasy.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Dodanie filtrów lub metadanych do grupy działa tak samo jak dodawanie ich indywidualnie do każdego punktu końcowego przed dodaniem dodatkowych filtrów lub metadanych, które mogły zostać dodane do grupy wewnętrznej lub określonego punktu końcowego.
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);
});
W powyższym przykładzie zewnętrzny filtr będzie rejestrować żądanie przychodzące przed filtrem wewnętrznym, mimo że został dodany w sekundzie. Ponieważ filtry zostały zastosowane do różnych grup, kolejność, którą zostały dodane względem siebie, nie ma znaczenia. Filtry kolejności są dodawane niezależnie od tego, czy są stosowane do tej samej grupy lub określonego punktu końcowego.
Żądanie, aby zarejestrować /outer/inner/
następujące elementy:
/outer group filter
/inner group filter
MapGet filter
Wskazówki dotyczące wydajności routingu
Gdy aplikacja ma problemy z wydajnością, routing jest często podejrzewany jako problem. Podejrzewa się, że routing polega na tym, że struktury, takie jak kontrolery i Razor strony, zgłaszają ilość czasu spędzonego w strukturze w komunikatach rejestrowania. W przypadku znacznej różnicy między czasem zgłoszonym przez kontrolery a łącznym czasem żądania:
- Deweloperzy eliminują kod aplikacji jako źródło problemu.
- Często zakłada się, że routing jest przyczyną.
Routing jest testowany pod kątem wydajności przy użyciu tysięcy punktów końcowych. Jest mało prawdopodobne, aby typowa aplikacja napotkała problem z wydajnością, ponieważ jest zbyt duża. Najczęstszą główną przyczyną niskiej wydajności routingu jest zwykle nieprawidłowe zachowanie niestandardowego oprogramowania pośredniczącego.
W poniższym przykładzie kodu przedstawiono podstawową technikę zawężania źródła opóźnienia:
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.");
Routing czasowy:
- Przeplataj każde oprogramowanie pośredniczące kopią oprogramowania pośredniczącego chronometrażu pokazanego w poprzednim kodzie.
- Dodaj unikatowy identyfikator, aby skorelować dane chronometrażu z kodem.
Jest to podstawowy sposób zawężenia opóźnienia, gdy jest to znaczące, na przykład więcej niż 10ms
. Odejmowanie Time 2
z Time 1
raportów czasu spędzonego wewnątrz oprogramowania pośredniczącego UseRouting
.
Poniższy kod używa bardziej kompaktowego podejścia do poprzedniego kodu chronometrażu:
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.");
Potencjalnie kosztowne funkcje routingu
Poniższa lista zawiera wgląd w funkcje routingu, które są stosunkowo kosztowne w porównaniu z podstawowymi szablonami tras:
- Wyrażenia regularne: istnieje możliwość zapisywania wyrażeń regularnych, które są złożone lub mają długi czas z niewielką ilością danych wejściowych.
- Segmenty złożone (
{x}-{y}-{z}
):- Są znacznie droższe niż analizowanie zwykłego segmentu ścieżki adresu URL.
- Powoduje przydzielenie wielu podciągów.
- Dostęp do danych synchronicznych: wiele złożonych aplikacji ma dostęp do bazy danych w ramach routingu. Użyj punktów rozszerzalności, takich jak MatcherPolicy i EndpointSelectorContext, które są asynchroniczne.
Wskazówki dotyczące dużych tabel tras
Domyślnie ASP.NET Core używa algorytmu routingu, który wymienia pamięć na czas procesora CPU. Ma to miły wpływ, że czas dopasowywania trasy zależy tylko od długości ścieżki do dopasowania, a nie liczby tras. Jednak takie podejście może być potencjalnie problematyczne w niektórych przypadkach, gdy aplikacja ma dużą liczbę tras (w tysiącach) i istnieje duża ilość zmiennych prefiksów w trasach. Jeśli na przykład trasy mają parametry we wczesnych segmentach trasy, na przykład {parameter}/some/literal
.
Jest mało prawdopodobne, aby aplikacja napotkała sytuację, w której jest to problem, chyba że:
- W aplikacji istnieje duża liczba tras korzystających z tego wzorca.
- W aplikacji istnieje duża liczba tras.
Jak określić, czy aplikacja jest uruchomiona w dużym problemie z tabelą tras
- Istnieją dwa objawy do wyszukania:
- Aplikacja powoli zaczyna się od pierwszego żądania.
- Należy pamiętać, że jest to wymagane, ale nie wystarczające. Istnieje wiele innych problemów niezwiązanych z trasą, które mogą powodować powolne uruchamianie aplikacji. Sprawdź poniższy warunek, aby dokładnie określić, czy aplikacja działa w tej sytuacji.
- Aplikacja zużywa dużo pamięci podczas uruchamiania, a zrzut pamięci pokazuje dużą liczbę
Microsoft.AspNetCore.Routing.Matching.DfaNode
wystąpień.
- Aplikacja powoli zaczyna się od pierwszego żądania.
Jak rozwiązać ten problem
Istnieje kilka technik i optymalizacji, które można zastosować do tras, które w dużej mierze usprawniają ten scenariusz:
- Zastosuj ograniczenia trasy do parametrów, na przykład
{parameter:int}
,{parameter:guid}
,{parameter:regex(\\d+)}
itp., jeśli to możliwe.- Dzięki temu algorytm routingu może wewnętrznie zoptymalizować struktury używane do dopasowywania i drastycznie zmniejszyć ilość używanej pamięci.
- W zdecydowanej większości przypadków wystarczy, aby wrócić do akceptowalnego zachowania.
- Zmień trasy, aby przenieść parametry do późniejszych segmentów w szablonie.
- Zmniejsza to liczbę możliwych "ścieżek", aby dopasować punkt końcowy na daną ścieżkę.
- Użyj trasy dynamicznej i dynamicznie wykonaj mapowanie na kontroler/stronę.
- Można to osiągnąć przy użyciu metod
MapDynamicControllerRoute
iMapDynamicPageRoute
.
- Można to osiągnąć przy użyciu metod
Oprogramowanie pośredniczące zwarcie po routingu
Gdy routing jest zgodny z punktem końcowym, zazwyczaj umożliwia rest uruchomienie potoku oprogramowania pośredniczącego przed wywołaniem logiki punktu końcowego. Usługi mogą zmniejszyć użycie zasobów przez odfiltrowanie znanych żądań na początku potoku. ShortCircuit Użyj metody rozszerzenia, aby natychmiast wywołać logikę punktu końcowego, a następnie zakończyć żądanie. Na przykład dana trasa może nie wymagać uwierzytelniania lub oprogramowania pośredniczącego CORS. Następujące przykładowe żądania zwarć zgodne z trasą /short-circuit
:
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
Metoda ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) może opcjonalnie przyjąć kod stanu.
MapShortCircuit Użyj metody , aby skonfigurować zwarcie dla wielu tras jednocześnie, przekazując do niej tablicę params prefiksów adresów URL. Na przykład przeglądarki i boty często sonduje serwery pod kątem dobrze znanych ścieżek, takich jak robots.txt
i favicon.ico
. Jeśli aplikacja nie ma tych plików, jeden wiersz kodu może skonfigurować obie trasy:
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
MapShortCircuit
funkcja zwraca IEndpointConventionBuilder , aby można było dodać do niego dodatkowe ograniczenia tras, takie jak filtrowanie hostów.
Metody ShortCircuit
i MapShortCircuit
nie mają wpływu na oprogramowanie pośredniczące umieszczone przed UseRouting
. Próba użycia tych metod z punktami końcowymi, które mają również [Authorize]
metadane, [RequireCors]
spowoduje niepowodzenie żądań z elementem InvalidOperationException
. Te metadane są stosowane przez [Authorize]
atrybuty lub RequireCors [EnableCors]
metody lub metodyRequireAuthorization.
Aby zobaczyć efekt oprogramowania pośredniczącego zwarciowego, ustaw kategorię rejestrowania "Microsoft" na "Informacje" w pliku appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Uruchom następujący kod:
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();
Poniższy przykład pochodzi z dzienników konsoli utworzonych przez uruchomienie punktu końcowego /
. Zawiera dane wyjściowe oprogramowania pośredniczącego rejestrowania:
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
Poniższy przykład pochodzi z uruchamiania punktu końcowego /short-circuit
. Nie ma nic z oprogramowania pośredniczącego rejestrowania, ponieważ oprogramowanie pośredniczące zostało zwarcie:
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.
Wskazówki dla autorów bibliotek
Ta sekcja zawiera wskazówki dotyczące autorów bibliotek opartych na routingu. Te szczegóły mają na celu zapewnienie, że deweloperzy aplikacji mają dobre środowisko korzystania z bibliotek i struktur rozszerzających routing.
Definiowanie punktów końcowych
Aby utworzyć strukturę korzystającą z routingu do dopasowywania adresów URL, zacznij od zdefiniowania środowiska użytkownika, które opiera się na systemie UseEndpoints.
Kompilacja DO na podstawie funkcji IEndpointRouteBuilder. Dzięki temu użytkownicy mogą tworzyć struktury przy użyciu innych funkcji ASP.NET Core bez pomyłek. Każdy szablon ASP.NET Core zawiera routing. Załóżmy, że routing jest obecny i znany użytkownikom.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
NALEŻY zwrócić zapieczętowany typ betonowy z wywołania, do MapMyFramework(...)
którego implementuje IEndpointConventionBuilderelement . Większość metod struktury Map...
jest zgodne z tym wzorcem. Interfejs IEndpointConventionBuilder
:
- Umożliwia komponowania metadanych.
- Jest celem różnych metod rozszerzeń.
Deklarowanie własnego typu umożliwia dodanie do konstruktora własnych funkcji specyficznych dla platformy. Można zawinąć konstruktora zadeklarowanego przez platformę i przekazać do niego wywołania.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
ROZWAŻ napisanie własnego EndpointDataSourcepliku . EndpointDataSource
jest elementem pierwotnym niskiego poziomu do deklarowania i aktualizowania kolekcji punktów końcowych. EndpointDataSource
to zaawansowany interfejs API używany przez kontrolery i Razor strony. Aby uzyskać więcej informacji, zobacz Dynamiczny routing punktów końcowych.
Testy routingu mają podstawowy przykład nieakcyjnego źródła danych.
ROZWAŻ zaimplementowanie .GetGroupedEndpoints Zapewnia to pełną kontrolę nad uruchamianiem konwencji grupy i ostatnimi metadanymi w zgrupowanych punktach końcowych. Umożliwia to na przykład niestandardowe EndpointDataSource
implementacje uruchamiania filtrów punktów końcowych dodanych do grup.
NIE próbuj domyślnie rejestrować konta EndpointDataSource
. Wymagaj od użytkowników zarejestrowania struktury w programie UseEndpoints. Filozofią routingu jest to, że nic nie jest dołączane domyślnie i UseEndpoints
jest to miejsce do rejestrowania punktów końcowych.
Tworzenie zintegrowanego z routingiem oprogramowania pośredniczącego
ROZWAŻ zdefiniowanie typów metadanych jako interfejsu.
DO umożliwia używanie typów metadanych jako atrybutu w klasach i metodach.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Struktury, takie jak kontrolery i Razor strony, obsługują stosowanie atrybutów metadanych do typów i metod. W przypadku deklarowania typów metadanych:
- Udostępnij je jako atrybuty.
- Większość użytkowników zna stosowanie atrybutów.
Deklarowanie typu metadanych jako interfejsu zwiększa kolejną warstwę elastyczności:
- Interfejsy są komponowalne.
- Deweloperzy mogą zadeklarować własne typy, które łączą wiele zasad.
NIE umożliwia przesłaniania metadanych, jak pokazano w poniższym przykładzie:
[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() { }
}
Najlepszym sposobem przestrzegania tych wytycznych jest unikanie definiowania metadanych znaczników:
- Nie należy po prostu szukać obecności typu metadanych.
- Zdefiniuj właściwość na metadanych i sprawdź właściwość .
Kolekcja metadanych jest uporządkowana i obsługuje zastępowanie według priorytetu. W przypadku kontrolerów metadane metody akcji są najbardziej specyficzne.
DO sprawia, że oprogramowanie pośredniczące jest przydatne z routingiem i bez tego routingu:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Jako przykład tej wskazówki należy wziąć pod uwagę UseAuthorization
oprogramowanie pośredniczące. Oprogramowanie pośredniczące autoryzacji umożliwia przekazanie zasad rezerwowych. Zasady rezerwowe, jeśli zostały określone, mają zastosowanie do obu następujących elementów:
- Punkty końcowe bez określonych zasad.
- Żądania, które nie są zgodne z punktem końcowym.
Dzięki temu oprogramowanie pośredniczące autoryzacji jest przydatne poza kontekstem routingu. Oprogramowanie pośredniczące autoryzacji może służyć do tradycyjnego programowania oprogramowania pośredniczącego.
Diagnostyka debugowania
Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw wartość Logging:LogLevel:Microsoft
Debug
. W środowisku deweloperów ustaw poziom dziennika w pliku appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Dodatkowe zasoby
Routing jest odpowiedzialny za dopasowywanie przychodzących żądań HTTP i wysyłanie tych żądań do punktów końcowych wykonywalnych aplikacji. Punkty końcowe to jednostki aplikacji kodu obsługi żądań wykonywalnych. Punkty końcowe są definiowane w aplikacji i konfigurowane podczas uruchamiania aplikacji. Proces dopasowywania punktu końcowego może wyodrębnić wartości z adresu URL żądania i podać te wartości do przetwarzania żądań. Korzystając z informacji o punkcie końcowym z aplikacji, routing jest również w stanie wygenerować adresy URL mapujące na punkty końcowe.
Aplikacje mogą konfigurować routing przy użyciu:
- Kontrolery
- Razor Pages
- SignalR
- usługi gRPC
- Oprogramowanie pośredniczące z obsługą punktu końcowego, takie jak kontrole kondycji.
- Delegaty i lambdy zarejestrowane przy użyciu routingu.
W tym artykule opisano szczegóły niskiego poziomu dotyczące routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:
- W przypadku kontrolerów zobacz Routing do akcji kontrolera w ASP.NET Core.
- Aby zapoznać Razor się z konwencjami stron, zobacz Razor Strony route and app conventions in ASP.NET Core (Konwencje tras stron i aplikacji w programie ASP.NET Core).
Podstawy routingu
Poniższy kod przedstawia podstawowy przykład routingu:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy przykład zawiera pojedynczy punkt końcowy przy użyciu MapGet metody :
- Po wysłaniu żądania HTTP
GET
do głównego adresu URL/
:- Delegat żądania jest wykonywany.
Hello World!
jest zapisywany w odpowiedzi HTTP.
- Jeśli metoda żądania nie
GET
jest lub główny adres URL nie/
jest , nie jest zgodna trasa i zwracany jest http 404.
Routing używa pary oprogramowania pośredniczącego zarejestrowanego przez UseRouting i UseEndpoints:
UseRouting
Dodaje trasę zgodną z potokiem oprogramowania pośredniczącego. To oprogramowanie pośredniczące analizuje zestaw punktów końcowych zdefiniowanych w aplikacji i wybiera najlepsze dopasowanie na podstawie żądania.UseEndpoints
Dodaje wykonywanie punktu końcowego do potoku oprogramowania pośredniczącego. Uruchamia delegata skojarzonego z wybranym punktem końcowym.
Aplikacje zazwyczaj nie muszą wywoływać ani UseRouting
UseEndpoints
. WebApplicationBuilder Konfiguruje potok oprogramowania pośredniczącego, który opakowuje oprogramowanie pośredniczące dodane za Program.cs
pomocą poleceń UseRouting
i UseEndpoints
. Jednak aplikacje mogą zmieniać kolejność, w jakiej UseRouting
i UseEndpoints
uruchamiać, wywołując te metody jawnie. Na przykład następujący kod wykonuje jawne wywołanie metody UseRouting
:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
Powyższy kod:
- Wywołanie w celu
app.Use
zarejestrowania niestandardowego oprogramowania pośredniczącego uruchamianego na początku potoku. - Wywołanie w celu
UseRouting
skonfigurowania odpowiedniego oprogramowania pośredniczącego trasy do uruchomienia po niestandardowym oprogramowaniem pośredniczącym. - Punkt końcowy zarejestrowany
MapGet
przy użyciu jest uruchamiany na końcu potoku.
Jeśli poprzedni przykład nie zawierał wywołania metody UseRouting
, niestandardowe oprogramowanie pośredniczące zostanie uruchomione po dopasowaniu trasy oprogramowania pośredniczącego.
Punkty końcowe
Metoda MapGet
służy do definiowania punktu końcowego. Punkt końcowy to coś, co może być następujące:
- Wybrane przez dopasowanie adresu URL i metody HTTP.
- Wykonywane przez uruchomienie delegata.
Punkty końcowe, które mogą być dopasowane i wykonywane przez aplikację, są konfigurowane w programie UseEndpoints
. Na przykład metody MapGet, MapPosti podobne łączą delegatów żądań z systemem routingu. Dodatkowe metody mogą służyć do łączenia funkcji platformy ASP.NET Core z systemem routingu:
- MapRazorPages for Razor Pages
- MapControllers dla kontrolerów
- MapHub THub<> forSignalR
- MapGrpcService TService<> dla gRPC
W poniższym przykładzie przedstawiono routing z bardziej zaawansowanym szablonem trasy:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
Ciąg /hello/{name:alpha}
jest szablonem trasy. Szablon trasy służy do konfigurowania sposobu dopasowania punktu końcowego. W takim przypadku szablon jest zgodny z następującymi elementami:
- Adres URL podobny do
/hello/Docs
- Każda ścieżka adresu URL rozpoczynająca się
/hello/
od sekwencji znaków alfabetycznych.:alpha
stosuje ograniczenie trasy, które pasuje tylko do znaków alfabetycznych. Ograniczenia tras zostały wyjaśnione w dalszej części tego artykułu.
Drugi segment ścieżki adresu URL: {name:alpha}
- Jest powiązany z parametrem
name
. - Jest przechwytywany i przechowywany w pliku HttpRequest.RouteValues.
W poniższym przykładzie przedstawiono routing z kontrolą kondycji i autoryzacją:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
W poprzednim przykładzie pokazano, jak:
- Oprogramowanie pośredniczące autoryzacji może być używane z routingiem.
- Punkty końcowe mogą służyć do konfigurowania zachowania autoryzacji.
Wywołanie MapHealthChecks dodaje punkt końcowy kontroli kondycji. Łączenie RequireAuthorization łańcucha do tego wywołania powoduje dołączenie zasad autoryzacji do punktu końcowego.
Wywoływanie UseAuthentication i UseAuthorization dodawanie oprogramowania pośredniczącego uwierzytelniania i autoryzacji. Te oprogramowanie pośredniczące są umieszczane między elementami UseRouting i UseEndpoints
, dzięki czemu mogą:
- Zobacz, który punkt końcowy został wybrany przez
UseRouting
element . - Przed wysłaniem do punktu końcowego zastosuj zasady UseEndpoints autoryzacji.
Metadane punktu końcowego
W poprzednim przykładzie istnieją dwa punkty końcowe, ale tylko punkt końcowy kontroli kondycji ma dołączone zasady autoryzacji. Jeśli żądanie pasuje do punktu końcowego sprawdzania kondycji, /healthz
zostanie wykonane sprawdzanie autoryzacji. Pokazuje to, że punkty końcowe mogą mieć dołączone dodatkowe dane. Te dodatkowe dane są nazywane metadanymi punktu końcowego:
- Metadane mogą być przetwarzane przez oprogramowanie pośredniczące obsługujące routing.
- Metadane mogą być dowolnego typu platformy .NET.
Pojęcia dotyczące routingu
System routingu opiera się na potoku oprogramowania pośredniczącego, dodając zaawansowaną koncepcję punktu końcowego . Punkty końcowe reprezentują jednostki funkcjonalności aplikacji, które różnią się od siebie pod względem routingu, autoryzacji i dowolnej liczby systemów ASP.NET Core.
definicja punktu końcowego platformy ASP.NET Core
Punkt końcowy platformy ASP.NET Core to:
- Plik wykonywalny: ma element RequestDelegate.
- Rozszerzalne: ma kolekcję metadanych .
- Można wybrać: opcjonalnie zawiera informacje o routingu.
- Wyliczenie: kolekcja punktów końcowych może być wyświetlana przez pobranie elementu EndpointDataSource z di.
Poniższy kod pokazuje, jak pobrać i sprawdzić punkt końcowy pasujący do bieżącego żądania:
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.");
Punkt końcowy, jeśli został wybrany, można pobrać z pliku HttpContext
. Jego właściwości można sprawdzić. Obiekty punktu końcowego są niezmienne i nie można ich modyfikować po utworzeniu. Najczęstszym typem RouteEndpointpunktu końcowego jest . RouteEndpoint
zawiera informacje, które umożliwiają wybranie go przez system routingu.
W poprzednim kodzie aplikacja. Użyj konfiguracji wbudowanego oprogramowania pośredniczącego.
Poniższy kod pokazuje, że w zależności od tego, gdzie app.Use
jest wywoływana w potoku, punkt końcowy może nie być:
// 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);
});
Poprzedni przykład dodaje Console.WriteLine
instrukcje, które wyświetlają, czy wybrano punkt końcowy. Aby uzyskać czytelność, przykład przypisuje nazwę wyświetlaną do podanego /
punktu końcowego.
Powyższy przykład obejmuje również wywołania i UseRouting
UseEndpoints
sterowanie dokładnie tym, kiedy oprogramowanie pośredniczące działa w potoku.
Uruchomienie tego kodu z adresem URL wyświetlania /
:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Uruchomienie tego kodu przy użyciu dowolnego innego adresu URL powoduje wyświetlenie:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Te dane wyjściowe pokazują, że:
- Punkt końcowy ma zawsze wartość null przed
UseRouting
wywołaniem. - Jeśli zostanie znalezione dopasowanie, punkt końcowy nie ma wartości null między
UseRouting
i UseEndpoints. - Oprogramowanie
UseEndpoints
pośredniczące jest terminalem po znalezieniu dopasowania. Oprogramowanie pośredniczące terminala jest zdefiniowane w dalszej części tego artykułu. - Oprogramowanie pośredniczące po
UseEndpoints
wykonaniu tylko wtedy, gdy nie zostanie znalezione dopasowanie.
Oprogramowanie UseRouting
pośredniczące używa SetEndpoint metody w celu dołączenia punktu końcowego do bieżącego kontekstu. Można zastąpić UseRouting
oprogramowanie pośredniczące logiką niestandardową i nadal korzystać z zalet korzystania z punktów końcowych. Punkty końcowe są typami pierwotnymi niskiego poziomu, takimi jak oprogramowanie pośredniczące, i nie są powiązane z implementacją routingu. Większość aplikacji nie musi zastępować UseRouting
logiką niestandardową.
Oprogramowanie UseEndpoints
pośredniczące jest przeznaczone do użycia w parze z oprogramowaniem pośredniczącym UseRouting
. Podstawowa logika do wykonania punktu końcowego nie jest skomplikowana. Użyj GetEndpoint polecenia , aby pobrać punkt końcowy, a następnie wywołać jego RequestDelegate właściwość.
Poniższy kod pokazuje, jak oprogramowanie pośredniczące może wpływać na routing lub reagować na nie:
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 { }
W poprzednim przykładzie przedstawiono dwie ważne pojęcia:
- Oprogramowanie pośredniczące może działać przed
UseRouting
zmodyfikowaniem danych, na których działa routing.- Zwykle oprogramowanie pośredniczące, które pojawia się przed zmodyfikowaniem routingu, modyfikuje niektóre właściwości żądania, takie jak UseRewriter, UseHttpMethodOverridelub UsePathBase.
- Oprogramowanie pośredniczące może działać między elementami
UseRouting
i UseEndpoints w celu przetworzenia wyników routingu przed wykonaniem punktu końcowego.- Oprogramowanie pośredniczące uruchamiane między elementami
UseRouting
iUseEndpoints
:- Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
- Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami
UseAuthorization
iUseCors
.
- Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.
- Oprogramowanie pośredniczące uruchamiane między elementami
Powyższy kod przedstawia przykład niestandardowego oprogramowania pośredniczącego obsługującego zasady poszczególnych punktów końcowych. Oprogramowanie pośredniczące zapisuje dziennik inspekcji dostępu do poufnych danych w konsoli. Oprogramowanie pośredniczące można skonfigurować do inspekcji punktu końcowego za RequiresAuditAttribute
pomocą metadanych. W tym przykładzie pokazano wzorzec zgody, w którym są poddawane inspekcji tylko punkty końcowe oznaczone jako poufne. Tę logikę można zdefiniować w odwrotnej kolejności, przeprowadzając inspekcję wszystkich elementów, które nie są oznaczone jako bezpieczne, na przykład. System metadanych punktu końcowego jest elastyczny. Ta logika może być zaprojektowana w dowolny sposób odpowiednio do przypadku użycia.
Powyższy przykładowy kod ma na celu zademonstrowanie podstawowych pojęć dotyczących punktów końcowych. Przykład nie jest przeznaczony do użytku produkcyjnego. Bardziej kompletna wersja oprogramowania pośredniczącego dziennika inspekcji:
- Zaloguj się do pliku lub bazy danych.
- Dołącz szczegóły, takie jak użytkownik, adres IP, nazwa poufnego punktu końcowego i inne.
Metadane RequiresAuditAttribute
zasad inspekcji są definiowane jako Attribute
łatwiejsze w użyciu z platformami opartymi na klasach, takimi jak kontrolery i SignalR. W przypadku używania trasy do kodu:
- Metadane są dołączane do interfejsu API konstruktora.
- Struktury oparte na klasach obejmują wszystkie atrybuty odpowiedniej metody i klasy podczas tworzenia punktów końcowych.
Najlepszym rozwiązaniem dla typów metadanych jest zdefiniowanie ich jako interfejsów lub atrybutów. Interfejsy i atrybuty umożliwiają ponowne użycie kodu. System metadanych jest elastyczny i nie nakłada żadnych ograniczeń.
Porównanie oprogramowania pośredniczącego terminalu z routingiem
W poniższym przykładzie pokazano zarówno oprogramowanie pośredniczące terminala, jak i routing:
// 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.");
Styl oprogramowania pośredniczącego pokazany za pomocą Approach 1:
programu to oprogramowanie pośredniczące terminalu. Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ wykonuje zgodną operację:
- Zgodna operacja w poprzednim przykładzie dotyczy
Path == "/"
oprogramowania pośredniczącego iPath == "/Routing"
routingu. - Gdy dopasowanie zakończy się pomyślnie, wykonuje pewne funkcje i zwraca, zamiast wywoływania oprogramowania pośredniczącego
next
.
Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ kończy wyszukiwanie, wykonuje niektóre funkcje, a następnie zwraca.
Poniższa lista porównuje oprogramowanie pośredniczące terminala z routingiem:
- Obie metody umożliwiają zakończenie potoku przetwarzania:
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
next
- Punkty końcowe są zawsze terminalne.
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
- Oprogramowanie pośredniczące terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
- Punkty końcowe są wykonywane w pozycji UseEndpoints.
- Oprogramowanie pośredniczące terminalu umożliwia dowolnemu kodowi określenie, kiedy oprogramowanie pośredniczące jest zgodne:
- Niestandardowy kod dopasowania trasy może być pełny i trudny do poprawnego zapisu.
- Routing udostępnia proste rozwiązania dla typowych aplikacji. Większość aplikacji nie wymaga niestandardowego kodu dopasowania tras.
- Interfejs punktów końcowych z oprogramowaniem pośredniczącym, takim jak
UseAuthorization
iUseCors
.- Używanie oprogramowania pośredniczącego terminalu z
UseAuthorization
oprogramowaniem pośredniczącym lubUseCors
wymaga ręcznego łączenia się z systemem autoryzacji.
- Używanie oprogramowania pośredniczącego terminalu z
Punkt końcowy definiuje oba:
- Pełnomocnik do przetwarzania żądań.
- Kolekcja dowolnych metadanych. Metadane służą do implementowania zagadnień krzyżowych na podstawie zasad i konfiguracji dołączonych do każdego punktu końcowego.
Oprogramowanie pośredniczące terminalu może być skutecznym narzędziem, ale może wymagać:
- Znaczna ilość kodowania i testowania.
- Ręczna integracja z innymi systemami w celu osiągnięcia żądanego poziomu elastyczności.
Przed napisaniem oprogramowania pośredniczącego terminalu rozważ integrację z routingiem.
Istniejące oprogramowanie pośredniczące terminalu integrujące się z usługą Map lub MapWhen zwykle może zostać przekształcone w punkt końcowy obsługujący routing. MapHealthChecks demonstruje wzorzec oprogramowania routera:
- Napisz metodę rozszerzenia w pliku IEndpointRouteBuilder.
- Utwórz zagnieżdżony potok oprogramowania pośredniczącego przy użyciu polecenia CreateApplicationBuilder.
- Dołącz oprogramowanie pośredniczące do nowego potoku. W tym przypadku . UseHealthChecks
- Build potok oprogramowania pośredniczącego do pliku RequestDelegate.
- Wywołaj
Map
i podaj nowy potok oprogramowania pośredniczącego. - Zwróć obiekt konstruktora dostarczony przez
Map
metodę rozszerzenia.
Poniższy kod przedstawia użycie narzędzi MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
W poprzednim przykładzie pokazano, dlaczego zwracanie obiektu konstruktora jest ważne. Zwracanie obiektu konstruktora umożliwia deweloperowi aplikacji konfigurowanie zasad, takich jak autoryzacja dla punktu końcowego. W tym przykładzie oprogramowanie pośredniczące kontroli kondycji nie ma bezpośredniej integracji z systemem autoryzacji.
System metadanych został utworzony w odpowiedzi na problemy napotykane przez autorów rozszerzeń przy użyciu oprogramowania pośredniczącego terminalu. Dla każdego oprogramowania pośredniczącego problematyczne jest zaimplementowanie własnej integracji z systemem autoryzacji.
Dopasowywanie adresu URL
- Jest procesem, za pomocą którego routing jest zgodny z żądaniem przychodzącym do punktu końcowego.
- Opiera się na danych w ścieżce i nagłówkach adresu URL.
- Można rozszerzyć, aby uwzględnić dowolne dane w żądaniu.
Po wykonaniu oprogramowania pośredniczącego routingu ustawia Endpoint
ona wartości i kieruje je do funkcji żądania z HttpContext bieżącego żądania:
- Wywołanie metody HttpContext.GetEndpoint pobiera punkt końcowy.
HttpRequest.RouteValues
pobiera kolekcję wartości tras.
Oprogramowanie pośredniczące jest uruchamiane po tym, jak oprogramowanie pośredniczące routingu może sprawdzić punkt końcowy i podjąć działania. Na przykład oprogramowanie pośredniczące autoryzacji może przesłuchić kolekcję metadanych punktu końcowego dla zasad autoryzacji. Po wykonaniu całego oprogramowania pośredniczącego w potoku przetwarzania żądań wywoływany jest delegat wybranego punktu końcowego.
System routingu w routingu punktów końcowych jest odpowiedzialny za wszystkie decyzje dotyczące wysyłania. Ponieważ oprogramowanie pośredniczące stosuje zasady na podstawie wybranego punktu końcowego, ważne jest, aby:
- Każda decyzja, która może mieć wpływ na wysyłanie lub stosowanie zasad zabezpieczeń, jest dokonana wewnątrz systemu routingu.
Ostrzeżenie
W przypadku zgodności z poprzednimi wersjami po wykonaniu delegata punktu końcowego kontroler lub Razor strony właściwości RouteContext.RouteData są ustawione na odpowiednie wartości na podstawie przetwarzania żądań wykonanego do tej pory.
Typ RouteContext
zostanie oznaczony jako przestarzały w przyszłej wersji:
- Przeprowadź migrację
RouteData.Values
doHttpRequest.RouteValues
. - Migrowanie
RouteData.DataTokens
w celu pobrania IDataTokensMetadata z metadanych punktu końcowego.
Dopasowywanie adresów URL działa w konfigurowalnym zestawie faz. W każdej fazie dane wyjściowe są zestawem dopasowań. Zestaw dopasowań można zawęzić dalej przez następną fazę. Implementacja routingu nie gwarantuje kolejności przetwarzania pasujących punktów końcowych. Wszystkie możliwe dopasowania są przetwarzane jednocześnie. Fazy dopasowywania adresów URL są wykonywane w następującej kolejności. ASP.NET Core:
- Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem w MatcherPolicy zestawie wystąpień.
- Używa elementu EndpointSelector , aby podjąć ostateczną decyzję z poprzedniej listy.
Lista punktów końcowych jest priorytetowa zgodnie z:
- RouteEndpoint.Order
- Pierwszeństwo szablonu trasy
Wszystkie pasujące punkty końcowe są przetwarzane w każdej fazie do momentu EndpointSelector osiągnięcia. Jest EndpointSelector
to ostatnia faza. Wybiera punkt końcowy o najwyższym priorytcie z dopasowań jako najlepszy. Jeśli istnieją inne dopasowania z tym samym priorytetem co najlepsze dopasowanie, zgłaszany jest niejednoznaczny wyjątek dopasowania.
Pierwszeństwo trasy jest obliczane na podstawie bardziej szczegółowego szablonu trasy o wyższym priorytecie. Rozważmy na przykład szablony /hello
i /{message}
:
- Oba są zgodne ze ścieżką
/hello
adresu URL . /hello
jest bardziej szczegółowy i dlatego wyższy priorytet.
Ogólnie rzecz biorąc, pierwszeństwo trasy dobrze nadaje się do wyboru najlepszego dopasowania do rodzajów schematów adresów URL używanych w praktyce. Używaj Order tylko wtedy, gdy jest to konieczne, aby uniknąć niejednoznaczności.
Ze względu na rodzaj rozszerzalności zapewnianej przez routing nie jest możliwe, aby system routingu obliczał przed upływem czasu niejednoznaczne trasy. Rozważmy przykład, taki jak szablony /{message:alpha}
tras i /{message:int}
:
- Ograniczenie
alpha
pasuje tylko do znaków alfabetycznych. - Ograniczenie
int
pasuje tylko do liczb. - Te szablony mają taki sam pierwszeństwo trasy, ale nie ma jednego adresu URL, który oba te szablony są zgodne.
- Jeśli system routingu zgłosił błąd niejednoznaczności podczas uruchamiania, zablokuje to prawidłowy przypadek użycia.
Ostrzeżenie
Kolejność operacji wewnątrz UseEndpoints nie ma wpływu na zachowanie routingu z jednym wyjątkiem. MapControllerRoute i MapAreaRoute automatycznie przypisz wartość zamówienia do swoich punktów końcowych na podstawie kolejności, w której są wywoływane. Symuluje to długotrwałe zachowanie kontrolerów bez systemu routingu zapewniające takie same gwarancje jak starsze implementacje routingu.
Routing punktów końcowych w ASP.NET Core:
- Nie ma pojęcia tras.
- Nie zapewnia gwarancji zamawiania. Wszystkie punkty końcowe są przetwarzane jednocześnie.
Pierwszeństwo szablonu trasy i kolejność wyboru punktu końcowego
Pierwszeństwo szablonu trasy to system, który przypisuje każdemu szablonowi trasy wartość na podstawie jego specyfiki. Pierwszeństwo szablonu trasy:
- Pozwala uniknąć konieczności dostosowywania kolejności punktów końcowych w typowych przypadkach.
- Próby dopasowania do zdrowych oczekiwań dotyczących zachowania routingu.
Rozważmy na przykład szablony /Products/List
i /Products/{id}
. Uzasadnione byłoby założenie, że /Products/List
jest to lepsze dopasowanie niż /Products/{id}
dla ścieżki /Products/List
adresu URL . Działa to, ponieważ segment /List
literału jest uznawany za lepszy priorytet niż segment /{id}
parametrów .
Szczegółowe informacje o sposobie działania pierwszeństwa są powiązane z definiowaniem szablonów tras:
- Szablony z większą większa część segmentów są uważane za bardziej szczegółowe.
- Segment z tekstem literału jest uznawany za bardziej szczegółowy niż segment parametrów.
- Segment parametrów z ograniczeniem jest uważany za bardziej szczegółowy niż jeden bez.
- Segment złożony jest uznawany za specyficzny jako segment parametrów z ograniczeniem.
- Parametry catch-all są najmniej specyficzne. Zobacz sekcję Catch-all w sekcji Szablony tras, aby uzyskać ważne informacje na temat tras catch-all.
Pojęcia dotyczące generowania adresów URL
Generowanie adresu URL:
- Jest procesem, za pomocą którego routing może utworzyć ścieżkę adresu URL na podstawie zestawu wartości trasy.
- Umożliwia logiczne rozdzielenie punktów końcowych i adresów URL, które do nich uzyskują dostęp.
Routing punktów końcowych obejmuje LinkGenerator interfejs API. LinkGenerator
jest pojedynczą usługą dostępną z di. Interfejs LinkGenerator
API może być używany poza kontekstem wykonywania żądania. Mvc.IUrlHelper i scenariusze, które opierają się na IUrlHelperelementach , takich jak Pomocnicy tagów, Pomocnicy HTML i Wyniki akcji, używają interfejsu LinkGenerator
API wewnętrznie, aby zapewnić możliwości generowania linków.
Generator linków jest wspierany przez koncepcję schematów adresów i adresów. Schemat adresów to sposób określania punktów końcowych, które powinny być brane pod uwagę podczas generowania linków. Na przykład scenariusze nazw tras i wartości tras, które wielu użytkowników zna z kontrolerów i Razor stron, są implementowane jako schemat adresów.
Generator linków może łączyć się z kontrolerami i Razor stronami za pomocą następujących metod rozszerzeń:
Przeciążenia tych metod akceptują argumenty, które obejmują HttpContext
element . Te metody są funkcjonalnie równoważne url.Action i Url.Page, ale oferują dodatkową elastyczność i opcje.
Metody GetPath*
są najbardziej podobne do Url.Action
metod i Url.Page
, w których generują identyfikator URI zawierający ścieżkę bezwzględną. Metody GetUri*
zawsze generują bezwzględny identyfikator URI zawierający schemat i host. Metody, które akceptują HttpContext
generowanie identyfikatora URI w kontekście wykonywania żądania. Wartości trasy otoczenia , ścieżka podstawowa adresu URL, schemat i host z wykonywanego żądania są używane, chyba że zostaną zastąpione.
LinkGenerator jest wywoływana przy użyciu adresu. Generowanie identyfikatora URI odbywa się w dwóch krokach:
- Adres jest powiązany z listą punktów końcowych pasujących do adresu.
- Każdy punkt końcowy RoutePattern jest oceniany do momentu znalezienia wzorca trasy zgodnego z podanymi wartościami. Wynikowe dane wyjściowe są łączone z innymi częściami identyfikatora URI dostarczonymi do generatora linków i zwracanymi.
Metody udostępniane przez LinkGenerator obsługę standardowych możliwości generowania linków dla dowolnego typu adresu. Najwygodniejszym sposobem korzystania z generatora linków jest użycie metod rozszerzeń, które wykonują operacje dla określonego typu adresu:
Extension, metoda | opis |
---|---|
GetPathByAddress | Generuje identyfikator URI ze ścieżką bezwzględną na podstawie podanych wartości. |
GetUriByAddress | Generuje bezwzględny identyfikator URI na podstawie podanych wartości. |
Ostrzeżenie
Zwróć uwagę na następujące konsekwencje wywoływania LinkGenerator metod:
Użyj
GetUri*
metod rozszerzeń z ostrożnością w konfiguracji aplikacji, która nie weryfikujeHost
nagłówka żądań przychodzących.Host
Jeśli nagłówek żądań przychodzących nie jest weryfikowany, niezaufane dane wejściowe żądania mogą być wysyłane z powrotem do klienta w identyfikatorach URI w widoku lub na stronie. Zalecamy skonfigurowanie serwera przez wszystkie aplikacje produkcyjne w celu zweryfikowania nagłówkaHost
pod kątem znanych prawidłowych wartości.Należy używać LinkGenerator z ostrożnością w oprogramowania pośredniczącego w połączeniu z
Map
lubMapWhen
.Map*
zmienia ścieżkę podstawową wykonywania żądania, co wpływa na dane wyjściowe generowania linków. LinkGenerator Wszystkie interfejsy API umożliwiają określenie ścieżki podstawowej. Określ pustą ścieżkę podstawową, aby cofnąć wpływ na generowanie linkówMap*
.
Przykład oprogramowania pośredniczącego
W poniższym przykładzie oprogramowanie pośredniczące używa interfejsu LinkGenerator API do utworzenia linku do metody akcji zawierającej listę produktów. Użycie generatora linków przez wstrzyknięcie go do klasy i wywołanie GenerateLink
jest dostępne dla dowolnej klasy w aplikacji:
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.");
}
}
Szablony tras
Tokeny w ramach {}
definicji parametrów trasy, które są powiązane, jeśli trasa jest zgodna. W segmencie trasy można zdefiniować więcej niż jeden parametr trasy, ale parametry trasy muszą być rozdzielone wartością literału. Na przykład:
{controller=Home}{action=Index}
nie jest prawidłową trasą, ponieważ nie ma wartości literału między {controller}
i {action}
. Parametry trasy muszą mieć nazwę i mogą mieć określone dodatkowe atrybuty.
Tekst literału inny niż parametry trasy (na przykład {id}
) i separator /
ścieżki musi być zgodny z tekstem w adresie URL. Dopasowywanie tekstu jest bez uwzględniania wielkości liter i oparte na dekodowanej reprezentacji ścieżki adresu URL. Aby dopasować ogranicznik {
parametru trasy literału lub }
, należy uruchomić ogranicznik, powtarzając znak. Na przykład {{
lub }}
.
Gwiazdka *
lub podwójna gwiazdka **
:
- Może służyć jako prefiks do parametru trasy w celu powiązania z rest identyfikatorem URI.
- Są nazywane parametrami catch-all . Na przykład :
blog/{**slug}
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
blog/
i ma dowolną wartość po niej. - Poniższa wartość
blog/
jest przypisywana do wartości trasy slug .
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
Ostrzeżenie
Parametr catch-all może być niepoprawnie zgodny z trasami z powodu błędu w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:
- Trasa typu catch-all, na przykład
{**slug}"
- Trasa catch-all nie pasuje do żądań, które powinna być zgodna.
- Usunięcie innych tras sprawia, że trasa catch-all zacznie działać.
Zobacz Usterki usługi GitHub 18677 i 16579 , na przykład przypadki, w których wystąpiła ta usterka.
Poprawka zgody na tę usterkę jest zawarta w zestawie .NET Core 3.1.301 SDK i nowszych wersjach. Poniższy kod ustawia przełącznik wewnętrzny, który naprawia tę usterkę:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Parametry catch-all mogą być również zgodne z pustym ciągiem.
Parametr catch-all ucieczki odpowiednich znaków, gdy trasa jest używana do generowania adresu URL, w tym znaków separatora /
ścieżki. Na przykład trasa foo/{*path}
z wartościami { path = "my/path" }
tras generuje foo/my%2Fpath
wartość . Zwróć uwagę na unikniętą ukośnik. Aby użyć znaków separatora ścieżki dwukierunkowej, użyj prefiksu parametru **
trasy. Trasa foo/{**path}
z elementem { path = "my/path" }
generuje foo/my/path
wartość .
Wzorce adresów URL, które próbują przechwycić nazwę pliku z opcjonalnym rozszerzeniem pliku, mają dodatkowe uwagi. Rozważmy na przykład szablon files/{filename}.{ext?}
. Gdy wartości obu filename
i ext
istnieją, oba wartości są wypełniane. Jeśli w adresie URL istnieje tylko wartość filename
, trasa jest zgodna, ponieważ końcowy .
jest opcjonalny. Następujące adresy URL pasują do tej trasy:
/files/myFile.txt
/files/myFile
Parametry trasy mogą mieć wartości domyślne wyznaczone przez określenie wartości domyślnej po nazwie parametru oddzielonej znakiem równości (=
). Na przykład {controller=Home}
definiuje Home
jako wartość domyślną dla elementu controller
. Wartość domyślna jest używana, jeśli w adresie URL parametru nie ma żadnej wartości. Parametry trasy są opcjonalne, dołączając znak zapytania (?
) na końcu nazwy parametru. Na przykład id?
. Różnica między opcjonalnymi wartościami a domyślnymi parametrami trasy to:
- Parametr trasy z wartością domyślną zawsze generuje wartość.
- Opcjonalny parametr ma wartość tylko wtedy, gdy wartość jest dostarczana przez adres URL żądania.
Parametry trasy mogą mieć ograniczenia, które muszą być zgodne z wartością trasy powiązaną z adresem URL. Dodanie :
i ograniczenie nazwy po nazwie parametru trasy określa ograniczenie wbudowane w parametrze trasy. Jeśli ograniczenie wymaga argumentów, są one ujęte w nawiasy (...)
po nazwie ograniczenia. Można określić wiele ograniczeń wbudowanych przez dołączenie innej :
nazwy i ograniczenia.
Nazwa ograniczenia i argumenty są przekazywane do IInlineConstraintResolver usługi w celu utworzenia wystąpienia IRouteConstraint do użycia w przetwarzaniu adresów URL. Na przykład szablon blog/{article:minlength(10)}
trasy określa minlength
ograniczenie z argumentem 10
. Aby uzyskać więcej informacji na temat ograniczeń tras i listy ograniczeń udostępnianych przez platformę, zobacz sekcję Ograniczenia trasy.
Parametry trasy mogą również zawierać transformatory parametrów. Transformatory parametrów przekształcają wartość parametru podczas generowania łączy i pasujących akcji i stron do adresów URL. Podobnie jak ograniczenia, transformatory parametrów można dodać w tekście do parametru trasy przez dodanie :
nazwy parametru i transformatora po nazwie parametru trasy. Na przykład szablon blog/{article:slugify}
trasy określa slugify
transformator. Aby uzyskać więcej informacji na temat transformatorów parametrów, zobacz sekcję Transformatory parametrów .
W poniższej tabeli przedstawiono przykładowe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI | Identyfikator URI żądania... |
---|---|---|
hello |
/hello |
Pasuje tylko do pojedynczej ścieżki /hello . |
{Page=Home} |
/ |
Dopasuj i ustawia wartość Page Home . |
{Page=Home} |
/Contact |
Dopasuj i ustawia wartość Page Contact . |
{controller}/{action}/{id?} |
/Products/List |
Mapuje na Products kontroler i List akcję. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapuje na Products kontroler i Details akcję z ustawioną wartościąid 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapuje na kontroler i Index metodęHome . Właściwość id jest ignorowana. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapuje na kontroler i Index metodęProducts . Właściwość id jest ignorowana. |
Użycie szablonu jest zwykle najprostszym podejściem do routingu. Ograniczenia i wartości domyślne można również określić poza szablonem trasy.
Złożone segmenty
Złożone segmenty są przetwarzane przez dopasowanie ograniczników literału od prawej do lewej w sposób niechłanny . Na przykład [Route("/a{b}c{d}")]
jest to złożony segment.
Złożone segmenty działają w określony sposób, aby można było z nich korzystać pomyślnie. W przykładzie w tej sekcji pokazano, dlaczego złożone segmenty działają prawidłowo tylko wtedy, gdy tekst ogranicznika nie pojawia się wewnątrz wartości parametrów. Użycie wyrażenia regularnego, a następnie ręczne wyodrębnienie wartości jest wymagane w przypadku bardziej złożonych przypadków.
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Jest to podsumowanie kroków, które routing wykonuje przy użyciu szablonu /a{b}c{d}
i ścieżki /abcd
adresu URL . Służy |
do wizualizacji sposobu działania algorytmu:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/abcd
jest wyszukiwany z prawej strony i znajduje/ab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/ab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - Brak pozostałego tekstu i brak pozostałego szablonu trasy, więc jest to dopasowanie.
Oto przykład negatywnego przypadku przy użyciu tego samego szablonu /a{b}c{d}
i ścieżki /aabcd
adresu URL . Służy |
do wizualizowania sposobu działania algorytmu. Ten przypadek nie jest zgodny, co zostało wyjaśnione przez ten sam algorytm:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/aabcd
jest wyszukiwany z prawej strony i znajduje/aab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/aab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/a|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - W tym momencie pozostaje tekst
a
, ale algorytm zabrakło szablonu trasy do przeanalizowania, więc nie jest to dopasowanie.
Ponieważ pasujący algorytm nie jest chciwy:
- Pasuje do najmniejszej ilości tekstu możliwego w każdym kroku.
- Każdy przypadek, w którym wartość ogranicznika pojawia się wewnątrz wartości parametrów, powoduje niezgodnie.
Wyrażenia regularne zapewniają znacznie większą kontrolę nad ich zachowaniem dopasowania.
Zachłanne dopasowanie, znane również jako leniwe dopasowanie, pasuje do największego możliwego ciągu. Niechłanny pasuje do najmniejszego możliwego ciągu.
Routing z znakami specjalnymi
Routing ze znakami specjalnymi może prowadzić do nieoczekiwanych wyników. Rozważmy na przykład kontroler z następującą metodą akcji:
[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;
}
Jeśli string id
zawiera następujące zakodowane wartości, mogą wystąpić nieoczekiwane wyniki:
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
Parametry trasy nie zawsze są dekodowane za pomocą adresu URL. Ten problem może zostać rozwiązany w przyszłości. Aby uzyskać więcej informacji, zobacz ten problem z usługą GitHub;
Ograniczenia trasy
Ograniczenia trasy są wykonywane, gdy wystąpiło dopasowanie do przychodzącego adresu URL, a ścieżka adresu URL jest tokenizowana do wartości tras. Ograniczenia tras zazwyczaj sprawdzają wartość trasy skojarzoną za pośrednictwem szablonu trasy i podejmowania prawdziwej lub fałszywej decyzji o tym, czy wartość jest akceptowalna. Niektóre ograniczenia trasy używają danych poza wartością trasy, aby rozważyć, czy żądanie można kierować. Na przykład HttpMethodRouteConstraint może zaakceptować lub odrzucić żądanie na podstawie jego czasownika HTTP. Ograniczenia są używane w żądaniach routingu i generowaniu linków.
Ostrzeżenie
Nie używaj ograniczeń do walidacji danych wejściowych. Jeśli ograniczenia są używane do walidacji danych wejściowych, nieprawidłowe dane wejściowe będą skutkować odpowiedzią 404
Nie znaleziono. Nieprawidłowe dane wejściowe powinny spowodować wygenerowanie nieprawidłowego 400
żądania z odpowiednim komunikatem o błędzie. Ograniczenia trasy służą do uściślania podobnych tras, a nie sprawdzania poprawności danych wejściowych dla określonej trasy.
W poniższej tabeli przedstawiono przykładowe ograniczenia tras i ich oczekiwane zachowanie:
ograniczenie | Przykład | Przykładowe dopasowania | Uwagi |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Pasuje do dowolnej liczby całkowitej |
bool |
{active:bool} |
true , FALSE |
Dopasuj lub true false . Bez uwzględniania wielkości liter |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Pasuje do prawidłowej DateTime wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Pasuje do prawidłowej decimal wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej double wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej float wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Pasuje do prawidłowej Guid wartości |
long |
{ticks:long} |
123456789 , -123456789 |
Pasuje do prawidłowej long wartości |
minlength(value) |
{username:minlength(4)} |
Rick |
Ciąg musi mieć co najmniej 4 znaki |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Ciąg nie może zawierać więcej niż 8 znaków |
length(length) |
{filename:length(12)} |
somefile.txt |
Ciąg musi mieć długość dokładnie 12 znaków |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Ciąg musi mieć co najmniej 8 znaków i nie więcej niż 16 znaków |
min(value) |
{age:min(18)} |
19 |
Wartość całkowita musi być co najmniej 18 |
max(value) |
{age:max(120)} |
91 |
Wartość całkowita nie może być większa niż 120 |
range(min,max) |
{age:range(18,120)} |
91 |
Wartość całkowita musi być co najmniej 18, ale nie większa niż 120 |
alpha |
{name:alpha} |
Rick |
Ciąg musi składać się z co najmniej jednego znaku a -z alfabetycznego i bez uwzględniania wielkości liter. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Ciąg musi być zgodny z wyrażeniem regularnym. Zobacz porady dotyczące definiowania wyrażenia regularnego. |
required |
{name:required} |
Rick |
Służy do wymuszania, że wartość nieparametrowa jest obecna podczas generowania adresu URL |
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wiele ograniczeń rozdzielonych dwukropkami można zastosować do jednego parametru. Na przykład następujące ograniczenie ogranicza parametr do wartości całkowitej 1 lub większej:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Ostrzeżenie
Ograniczenia trasy, które weryfikują adres URL i są konwertowane na typ CLR, zawsze używają niezmiennej kultury. Na przykład konwersja na typ int
CLR lub DateTime
. Te ograniczenia zakładają, że adres URL nie jest lokalizowalny. Ograniczenia trasy dostarczone przez platformę nie modyfikują wartości przechowywanych w wartościach tras. Wszystkie wartości tras analizowane z adresu URL są przechowywane jako ciągi. Na przykład float
ograniczenie próbuje przekonwertować wartość trasy na zmiennoprzecinkowy, ale przekonwertowana wartość jest używana tylko do sprawdzania, czy można ją przekonwertować na zmiennoprzecinkowy.
Wyrażenia regularne w ograniczeniach
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wyrażenia regularne można określić jako ograniczenia wbudowane przy użyciu regex(...)
ograniczenia trasy. Metody w MapControllerRoute rodzinie akceptują również literał obiektów ograniczeń. Jeśli ten formularz jest używany, wartości ciągów są interpretowane jako wyrażenia regularne.
Poniższy kod używa wbudowanego ograniczenia wyrażeń regularnych:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
Poniższy kod używa literału obiektu do określenia ograniczenia wyrażenia regularnego:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
Struktura ASP.NET Core dodaje RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
do konstruktora wyrażeń regularnych. Zobacz RegexOptions opis tych elementów członkowskich.
Wyrażenia regularne używają ograniczników i tokenów podobnych do tych używanych przez routing i język C#. Tokeny wyrażeń regularnych muszą zostać uniknięci. Aby użyć wyrażenia ^\d{3}-\d{2}-\d{4}$
regularnego w ograniczeniu wbudowanym, użyj jednego z następujących elementów:
- Zastąp
\
znaki podane w ciągu jako\\
znaki w pliku źródłowym języka C#, aby uniknąć\
znaku ucieczki ciągu. - Literały ciągu dosłowne.
Aby uniknąć znaków {
ogranicznika parametru routingu , , [
}
, ]
, podwaja znaki w wyrażeniu, na przykład {{
, , }}
, [[
, ]]
. W poniższej tabeli przedstawiono wyrażenie regularne i jego wersję unikniętą:
Regular expression | Wyrażenie regularne o wartości ucieczki |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Wyrażenia regularne używane w routingu często zaczynają się od ^
znaku i pasują do pozycji początkowej ciągu. Wyrażenia często kończą się znakiem $
i pasują do końca ciągu. Znaki ^
i $
zapewniają, że wyrażenie regularne jest zgodne z całą wartością parametru trasy. ^
Bez znaków i $
wyrażenie regularne pasuje do dowolnego podciągu w ciągu, co jest często niepożądane. W poniższej tabeli przedstawiono przykłady i wyjaśniono, dlaczego są one zgodne lub nie są zgodne:
Wyrażenie | String | Match | Komentarz |
---|---|---|---|
[a-z]{2} |
hello | Tak | Dopasowania podciągów |
[a-z]{2} |
123abc456 | Tak | Dopasowania podciągów |
[a-z]{2} |
mz | Tak | Dopasuj wyrażenie |
[a-z]{2} |
MZ | Tak | Nie uwzględnia wielkości liter |
^[a-z]{2}$ |
hello | Nie. | Zobacz ^ i $ powyżej |
^[a-z]{2}$ |
123abc456 | Nie. | Zobacz ^ i $ powyżej |
Aby uzyskać więcej informacji na temat składni wyrażeń regularnych, zobacz .NET Framework Regular Expressions (Wyrażenia regularne programu .NET Framework).
Aby ograniczyć parametr do znanego zestawu możliwych wartości, użyj wyrażenia regularnego. Na przykład {action:regex(^(list|get|create)$)}
pasuje action
tylko do wartości trasy do list
, get
lub create
. Jeśli zostanie przekazany do słownika ograniczeń, ciąg ^(list|get|create)$
jest równoważny. Ograniczenia przekazywane w słowniku ograniczeń, które nie są zgodne z jednym ze znanych ograniczeń, są również traktowane jako wyrażenia regularne. Ograniczenia przekazywane w szablonie, które nie pasują do jednego ze znanych ograniczeń, nie są traktowane jako wyrażenia regularne.
Ograniczenia trasy niestandardowej
Ograniczenia trasy niestandardowej można utworzyć przez zaimplementowanie interfejsu IRouteConstraint . Interfejs IRouteConstraint
zawiera Matchwartość , która zwraca true
wartość, jeśli ograniczenie jest spełnione i false
w przeciwnym razie.
Ograniczenia trasy niestandardowej są rzadko potrzebne. Przed zaimplementowaniem ograniczenia trasy niestandardowej rozważ alternatywy, takie jak powiązanie modelu.
Folder ASP.NET Core Constraints zawiera dobre przykłady tworzenia ograniczeń. Na przykład GuidRouteConstraint.
Aby użyć niestandardowego IRouteConstraint
, typ ograniczenia trasy musi być zarejestrowany w aplikacji ConstraintMap w kontenerze usługi. Jest ConstraintMap
to słownik, który mapuje klucze ograniczeń trasy na IRouteConstraint
implementacje, które weryfikują te ograniczenia. Aplikację ConstraintMap
można zaktualizować w Program.cs
ramach AddRouting wywołania lub konfigurując bezpośrednio za pomocą builder.Services.Configure<RouteOptions>
polecenia RouteOptions . Na przykład:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
Powyższe ograniczenie jest stosowane w następującym kodzie:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
Implementacja elementu NoZeroesRouteConstraint
uniemożliwia 0
korzystanie z parametru trasy:
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);
}
}
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Powyższy kod ma następujące działanie:
- Zapobiega
0
w{id}
segmencie trasy. - Pokazano, aby podać podstawowy przykład implementacji ograniczenia niestandardowego. Nie należy jej używać w aplikacji produkcyjnej.
Poniższy kod jest lepszym podejściem do zapobiegania przetwarzaniu elementu zawierającego id
0
element:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
Powyższy kod ma następujące zalety w porównaniu z podejściem NoZeroesRouteConstraint
:
- Nie wymaga on ograniczenia niestandardowego.
- Zwraca on bardziej opisowy błąd, gdy parametr trasy zawiera
0
wartość .
Transformatory parametrów
Transformatory parametrów:
- Wykonaj polecenie podczas generowania linku przy użyciu polecenia LinkGenerator.
- Zaimplementuj .Microsoft.AspNetCore.Routing.IOutboundParameterTransformer
- Są konfigurowane przy użyciu polecenia ConstraintMap.
- Weź wartość trasy parametru i przekształć go w nową wartość ciągu.
- Wynik użycia przekształconej wartości w wygenerowanym linku.
Na przykład funkcja przekształcania parametrów niestandardowych slugify
we wzorcu blog\{article:slugify}
trasy z poleceniem Url.Action(new { article = "MyTestArticle" })
generuje blog\my-test-article
wartość .
Rozważmy następującą IOutboundParameterTransformer
implementację:
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();
}
}
Aby użyć transformatora parametrów we wzorcu trasy, skonfiguruj go przy użyciu ConstraintMap polecenia w pliku Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
Struktura ASP.NET Core używa funkcji przekształcania parametrów w celu przekształcenia identyfikatora URI, w którym punkt końcowy jest rozpoznawany. Na przykład transformatory parametrów przekształcają wartości tras używane do dopasowania area
wartości , , action
controller
i page
:
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
W przypadku poprzedniego szablonu trasy akcja SubscriptionManagementController.GetAll
jest zgodna z identyfikatorem URI /subscription-management/get-all
. Funkcja przekształcania parametrów nie zmienia wartości tras używanych do generowania łącza. Na przykład Url.Action("GetAll", "SubscriptionManagement")
dane wyjściowe ./subscription-management/get-all
ASP.NET Core udostępnia konwencje interfejsu API dotyczące używania transformatorów parametrów z wygenerowanymi trasami:
- Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention Konwencja MVC stosuje określony parametr transformer do wszystkich tras atrybutów w aplikacji. Transformator parametru przekształca tokeny tras atrybutu podczas ich wymiany. Aby uzyskać więcej informacji, zobacz Używanie funkcji przekształcania parametrów do dostosowywania zamiany tokenu.
- Razor Strony używają konwencji interfejsu PageRouteTransformerConvention API. Ta konwencja stosuje określony transformator parametru do wszystkich automatycznie odnalezionych Razor stron. Funkcja przekształcania parametrów przekształca segmenty Razor nazw folderów i plików tras stron. Aby uzyskać więcej informacji, zobacz Dostosowywanie tras stron przy użyciu funkcji przekształcania parametrów.
Dokumentacja generowania adresów URL
Ta sekcja zawiera odwołanie do algorytmu zaimplementowanego przez generowanie adresów URL. W praktyce większość złożonych przykładów generowania adresów URL używa kontrolerów lub Razor stron. Aby uzyskać dodatkowe informacje, zobacz routing w kontrolerach .
Proces generowania adresu URL rozpoczyna się od wywołania metody LinkGenerator.GetPathByAddress lub podobnej metody. Metoda jest dostarczana z adresem, zestawem wartości tras i opcjonalnie informacjami o bieżącym żądaniu z .HttpContext
Pierwszym krokiem jest użycie adresu w celu rozpoznania zestawu punktów końcowych kandydata przy użyciu elementu zgodnego z typem IEndpointAddressScheme<TAddress> adresu.
Po znalezieniu zestawu kandydatów przez schemat adresów punkty końcowe są uporządkowane i przetwarzane iteracyjnie, dopóki operacja generowania adresu URL nie powiedzie się. Generowanie adresu URL nie sprawdza niejednoznaczności. Zwracany pierwszy wynik jest wynikiem końcowym.
Rozwiązywanie problemów z generowaniem adresu URL przy użyciu rejestrowania
Pierwszym krokiem generowania adresu URL rozwiązywania problemów jest ustawienie poziomu rejestrowania Microsoft.AspNetCore.Routing
na TRACE
. LinkGenerator
rejestruje wiele szczegółów dotyczących jego przetwarzania, które mogą być przydatne do rozwiązywania problemów.
Aby uzyskać szczegółowe informacje na temat generowania adresów URL, zobacz Dokumentację generowania adresów URL.
Adresy
Adresy to koncepcja generowania adresów URL używana do powiązania wywołania z generatorem linków z zestawem kandydatów punktów końcowych.
Adresy to rozszerzalna koncepcja, która domyślnie zawiera dwie implementacje:
- Użyj nazwy punktu końcowego (
string
) jako adresu:- Zapewnia podobne funkcje do nazwy trasy MVC.
- IEndpointNameMetadata Używa typu metadanych.
- Usuwa podany ciąg względem metadanych wszystkich zarejestrowanych punktów końcowych.
- Zgłasza wyjątek podczas uruchamiania, jeśli wiele punktów końcowych używa tej samej nazwy.
- Zalecane do użytku ogólnego przeznaczenia poza kontrolerami i Razor stronami.
- Użyj wartości tras (RouteValuesAddress) jako adresu:
- Zapewnia podobne funkcje do kontrolerów i Razor starszej generacji adresów URL stron.
- Bardzo złożone rozszerzanie i debugowanie.
- Zapewnia implementację używaną przez
IUrlHelper
pomocników tagów, pomocników HTML, wyniki akcji itp.
Rolą schematu adresów jest skojarzenie adresu i pasujących punktów końcowych według dowolnych kryteriów:
- Schemat nazw punktów końcowych wykonuje wyszukiwanie w słowniku podstawowym.
- Schemat wartości tras ma złożony najlepszy podzbiór ustawionego algorytmu.
Wartości otoczenia i jawne wartości
Z bieżącego żądania routing uzyskuje dostęp do wartości tras bieżącego żądania HttpContext.Request.RouteValues
. Wartości skojarzone z bieżącym żądaniem są określane jako wartości otoczenia. W celu jasności dokumentacja odnosi się do wartości tras przekazywanych do metod jako jawnych wartości.
W poniższym przykładzie przedstawiono wartości otoczenia i jawne wartości. Zapewnia ona wartości otoczenia z bieżącego żądania i jawnych wartości:
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);
}
// ...
Powyższy kod ma następujące działanie:
- Zwraca
/Widget/Index/17
- Pobiera LinkGenerator przez DI.
Poniższy kod zawiera tylko jawne wartości i nie ma wartości otoczenia:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
Poprzednia metoda zwraca /Home/Subscribe/17
Poniższy kod w zwracaniu WidgetController
wartości /Widget/Subscribe/17
:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
Poniższy kod udostępnia kontroler z wartości otoczenia w bieżącym żądaniu i jawnych wartościach:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
Powyższy kod:
/Gadget/Edit/17
jest zwracany.- Url pobiera element IUrlHelper.
- Action generuje adres URL ze ścieżką bezwzględną dla metody akcji. Adres URL zawiera określoną
action
nazwę iroute
wartości.
Poniższy kod zawiera wartości otoczenia z bieżącego żądania i jawnych wartości:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
Powyższy kod ustawia wartość url
, /Edit/17
gdy strona edycji Razor zawiera następującą dyrektywę strony:
@page "{id:int}"
Jeśli strona Edycja nie zawiera szablonu "{id:int}"
trasy, url
to /Edit?id=17
.
Zachowanie wzorca MVC IUrlHelper dodaje warstwę złożoności oprócz reguł opisanych tutaj:
IUrlHelper
zawsze udostępnia wartości tras z bieżącego żądania jako wartości otoczenia.- IUrlHelper.Action zawsze kopiuje bieżące
action
wartości icontroller
trasy jako wartości jawne, chyba że zostanie zastąpione przez dewelopera. - IUrlHelper.Page zawsze kopiuje bieżącą
page
wartość trasy jako jawną wartość, chyba że zostanie zastąpiona. IUrlHelper.Page
zawsze zastępuje bieżącąhandler
wartość trasy wartościąnull
jawną, chyba że zostanie zastąpiona.
Użytkownicy są często zaskoczeni szczegółami zachowania wartości otoczenia, ponieważ MVC nie wydaje się przestrzegać własnych reguł. Ze względów historycznych i zgodności niektóre wartości tras, takie jak action
, controller
, page
i handler
mają własne zachowanie specjalne.
Równoważna funkcjonalność zapewniana przez LinkGenerator.GetPathByAction
program i LinkGenerator.GetPathByPage
duplikuje te anomalie w IUrlHelper
celu zapewnienia zgodności.
Proces generowania adresów URL
Po znalezieniu zestawu punktów końcowych kandydata algorytm generowania adresów URL:
- Przetwarza iteracyjne punkty końcowe.
- Zwraca pierwszy pomyślny wynik.
Pierwszym krokiem w tym procesie jest unieważnienie wartości trasy. Unieważnienie wartości trasy to proces, za pomocą którego routing decyduje, które wartości tras z wartości otoczenia powinny być używane i które powinny być ignorowane. Każda wartość otoczenia jest uwzględniana i połączona z jawnymi wartościami lub ignorowana.
Najlepszym sposobem myślenia o roli wartości otoczenia jest to, że próbują zapisać deweloperów aplikacji wpisywanie, w niektórych typowych przypadkach. Tradycyjnie scenariusze, w których wartości otoczenia są przydatne, są związane z MVC:
- Podczas łączenia z inną akcją w tym samym kontrolerze nie trzeba określać nazwy kontrolera.
- Podczas łączenia z innym kontrolerem w tym samym obszarze nie trzeba określać nazwy obszaru.
- Podczas łączenia z tą samą metodą akcji wartości tras nie muszą być określone.
- Podczas łączenia z inną częścią aplikacji nie chcesz przenosić wartości tras, które nie mają znaczenia w tej części aplikacji.
Wywołania do LinkGenerator
lub IUrlHelper
tego zwracania null
są zwykle spowodowane przez niezrozumienie unieważnienia wartości trasy. Rozwiąż problemy z nieprawidłową wartością trasy, określając jawnie więcej wartości trasy, aby sprawdzić, czy rozwiązuje to problem.
Unieważnienie wartości trasy działa zgodnie z założeniem, że schemat adresu URL aplikacji jest hierarchiczny, z hierarchią utworzoną od lewej do prawej. Rozważmy podstawowy szablon {controller}/{action}/{id?}
trasy kontrolera, aby uzyskać intuicyjne zrozumienie sposobu działania tej metody w praktyce. Zmiana wartości unieważnia wszystkie wartości trasy, które są wyświetlane po prawej stronie. Odzwierciedla to założenie dotyczące hierarchii. Jeśli aplikacja ma wartość otoczenia dla id
, a operacja określa inną wartość dla elementu controller
:
id
nie będzie ponownie używany, ponieważ{controller}
znajduje się po lewej{id?}
stronie .
Kilka przykładów demonstrujących tę zasadę:
- Jeśli jawne wartości zawierają wartość parametru
id
, wartość otoczenia dlaid
elementu jest ignorowana. Wartości otoczenia icontroller
action
mogą być używane. - Jeśli jawne wartości zawierają wartość parametru
action
, każda wartość otoczenia dlaaction
elementu jest ignorowana. Można użyć wartościcontroller
otoczenia. Jeśli jawna wartość parametruaction
jest inna niż wartość otoczenia dlaaction
wartości ,id
wartość nie zostanie użyta. Jeśli jawna wartość parametruaction
jest taka sama jak wartość otoczenia dlaaction
,id
można użyć wartości . - Jeśli jawne wartości zawierają wartość parametru
controller
, każda wartość otoczenia dlacontroller
elementu jest ignorowana. Jeśli jawna wartość parametrucontroller
jest inna niż wartość otoczenia dlacontroller
,action
wartości iid
nie będą używane. Jeśli jawna wartość parametrucontroller
jest taka sama jak wartość otoczenia dlacontroller
,action
można użyć wartości iid
.
Ten proces jest jeszcze bardziej skomplikowany przez istnienie tras atrybutów i dedykowanych tras konwencjonalnych. Kontroler konwencjonalnych tras, takich jak {controller}/{action}/{id?}
określanie hierarchii przy użyciu parametrów trasy. W przypadku dedykowanych tras konwencjonalnych i tras atrybutów do kontrolerów i Razor stron:
- Istnieje hierarchia wartości tras.
- Nie są one wyświetlane w szablonie.
W takich przypadkach generowanie adresów URL definiuje koncepcję wymaganych wartości . Punkty końcowe utworzone przez kontrolery i Razor strony mają wymagane wartości określone, które umożliwiają działanie unieważnienia wartości trasy.
Algorytm unieważniania wartości trasy szczegółowo:
- Wymagane nazwy wartości są łączone z parametrami trasy, a następnie przetwarzane od lewej do prawej.
- Dla każdego parametru porównywana jest wartość otoczenia i jawna wartość:
- Jeśli wartość otoczenia i jawna wartość są takie same, proces będzie kontynuowany.
- Jeśli wartość otoczenia jest obecna, a jawna wartość nie jest, wartość otoczenia jest używana podczas generowania adresu URL.
- Jeśli wartość otoczenia nie jest obecna, a jawna wartość to, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
- Jeśli wartość otoczenia i jawna wartość są obecne, a dwie wartości są różne, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
W tym momencie operacja generowania adresu URL jest gotowa do oceny ograniczeń trasy. Zestaw akceptowanych wartości jest połączony z wartościami domyślnymi parametrów, które są dostarczane do ograniczeń. Jeśli wszystkie ograniczenia przechodzą, operacja będzie kontynuowana.
Następnie akceptowane wartości mogą służyć do rozszerzania szablonu trasy. Szablon trasy jest przetwarzany:
- Od lewej do prawej.
- Każdy parametr ma zastąpioną akceptowaną wartość.
- W następujących przypadkach specjalnych:
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr ma wartość domyślną, zostanie użyta wartość domyślna.
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr jest opcjonalny, przetwarzanie będzie kontynuowane.
- Jeśli dowolny parametr trasy z prawej strony brakującego opcjonalnego parametru ma wartość, operacja kończy się niepowodzeniem.
- Ciągłe parametry domyślne i parametry opcjonalne są zwinięte tam, gdzie to możliwe.
Wartości jawnie podane, które nie pasują do segmentu trasy, są dodawane do ciągu zapytania. W poniższej tabeli przedstawiono wynik użycia szablonu {controller}/{action}/{id?}
trasy .
Wartości otoczenia | Jawne wartości | Result |
---|---|---|
controller = "Home" | action = "Informacje" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "Informacje" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Opcjonalna kolejność parametrów trasy
Opcjonalne parametry trasy muszą pochodzić po wszystkich wymaganych parametrach trasy. W poniższym kodzie id
parametry i name
muszą pochodzić po parametrze color
:
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 ?? ""}");
}
}
Problemy z unieważnieniem wartości trasy
Poniższy kod przedstawia przykład schematu generowania adresów URL, który nie jest obsługiwany przez routing:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
W poprzednim kodzie culture
parametr trasy jest używany do lokalizacji. Pragnieniem jest, culture
aby parametr zawsze był akceptowany jako wartość otoczenia. culture
Jednak parametr nie jest akceptowany jako wartość otoczenia ze względu na sposób działania wymaganych wartości:
- W szablonie
"default"
culture
trasy parametr trasy znajduje się po lewej stroniecontroller
elementu , więc zmianycontroller
nie będą unieważniaćculture
elementu . - W szablonie
"blog"
culture
trasy parametr trasy jest uznawany za znajdujący się po prawej stroniecontroller
elementu , który jest wyświetlany w wymaganych wartościach.
Analizowanie ścieżek URL za pomocą polecenia LinkParser
Klasa LinkParser dodaje obsługę analizowania ścieżki adresu URL do zestawu wartości tras. Metoda ParsePathByEndpointName przyjmuje nazwę punktu końcowego i ścieżkę adresu URL i zwraca zestaw wartości tras wyodrębnionych ze ścieżki adresu URL.
W poniższym przykładowym kontrolerze akcja GetProduct
używa szablonu api/Products/{id}
trasy i ma Name GetProduct
wartość :
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
W tej samej klasie AddRelatedProduct
kontrolera akcja oczekuje ścieżki adresu URL , pathToRelatedProduct
która może być podana jako parametr ciągu zapytania:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
W poprzednim przykładzie akcja AddRelatedProduct
wyodrębnia id
wartość trasy ze ścieżki adresu URL. Na przykład ze ścieżką /api/Products/1
relatedProductId
adresu URL wartości jest ustawiona wartość 1
. Takie podejście umożliwia klientom interfejsu API używanie ścieżek url podczas odwoływania się do zasobów bez konieczności znajomości struktury takiego adresu URL.
Konfigurowanie metadanych punktu końcowego
Poniższe linki zawierają informacje na temat konfigurowania metadanych punktu końcowego:
- Włączanie mechanizmu Cors z routingiem punktów końcowych
- Przykład IAuthorizationPolicyProvider przy użyciu atrybutu niestandardowego
[MinimumAgeAuthorize]
- Testowanie uwierzytelniania za pomocą atrybutu [Autoryzuj]
- RequireAuthorization
- Wybieranie schematu z atrybutem [Authorize]
- Stosowanie zasad przy użyciu atrybutu [Autoryzuj]
- Autoryzacja oparta na rolach w programie ASP.NET Core
Dopasowywanie hostów w trasach za pomocą elementu RequireHost
RequireHost stosuje ograniczenie do trasy, która wymaga określonego hosta. Parametr RequireHost
lub [Host] może być:
- Host:
www.domain.com
, pasujewww.domain.com
do dowolnego portu. - Host z symbolem wieloznacznymi:
*.domain.com
, pasujewww.domain.com
do ,subdomain.domain.com
lubwww.subdomain.domain.com
na dowolnym porcie. - Port:
*:5000
, pasuje do portu 5000 z dowolnym hostem. - Host i port:
www.domain.com:5000
lub*.domain.com:5000
, pasuje do hosta i portu.
Można określić wiele parametrów przy użyciu polecenia RequireHost
lub [Host]
. Ograniczenie pasuje do hostów prawidłowych dla dowolnego z parametrów. Na przykład pasuje domain.com
do [Host("domain.com", "*.domain.com")]
, www.domain.com
i subdomain.domain.com
.
Poniższy kod używa RequireHost
metody , aby wymagać określonego hosta na trasie:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
Poniższy kod używa atrybutu [Host]
na kontrolerze, aby wymagać któregokolwiek z określonych hostów:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Po zastosowaniu atrybutu [Host]
zarówno do kontrolera, jak i metody akcji:
- Używany jest atrybut akcji.
- Atrybut kontrolera jest ignorowany.
Grupy tras
Metoda MapGroup rozszerzenia ułatwia organizowanie grup punktów końcowych za pomocą wspólnego prefiksu. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata które dodają metadane punktu końcowego.
Na przykład poniższy kod tworzy dwie podobne grupy punktów końcowych:
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;
}
W tym scenariuszu możesz użyć względnego adresu nagłówka Location
w 201 Created
wyniku:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Pierwsza grupa punktów końcowych będzie pasowała tylko do żądań z prefiksem /public/todos
i jest dostępna bez żadnego uwierzytelniania. Druga grupa punktów końcowych będzie pasowała tylko do żądań poprzedzonych prefiksem /private/todos
i wymaga uwierzytelniania.
QueryPrivateTodos
Fabryka filtrów punktów końcowych to funkcja lokalna, która modyfikuje parametry programu obsługi TodoDb
tras, aby umożliwić dostęp do prywatnych danych zadań do wykonania i przechowywanie ich.
Grupy tras obsługują również grupy zagnieżdżone i złożone wzorce prefiksów z parametrami i ograniczeniami trasy. W poniższym przykładzie program obsługi tras zamapowany na grupę user
może przechwytywać {org}
parametry trasy i {group}
zdefiniowane w prefiksach grup zewnętrznych.
Prefiks może być również pusty. Może to być przydatne w przypadku dodawania metadanych lub filtrów punktu końcowego do grupy punktów końcowych bez zmiany wzorca trasy.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Dodanie filtrów lub metadanych do grupy działa tak samo jak dodawanie ich indywidualnie do każdego punktu końcowego przed dodaniem dodatkowych filtrów lub metadanych, które mogły zostać dodane do grupy wewnętrznej lub określonego punktu końcowego.
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);
});
W powyższym przykładzie zewnętrzny filtr będzie rejestrować żądanie przychodzące przed filtrem wewnętrznym, mimo że został dodany w sekundzie. Ponieważ filtry zostały zastosowane do różnych grup, kolejność, którą zostały dodane względem siebie, nie ma znaczenia. Filtry kolejności są dodawane niezależnie od tego, czy są stosowane do tej samej grupy lub określonego punktu końcowego.
Żądanie, aby zarejestrować /outer/inner/
następujące elementy:
/outer group filter
/inner group filter
MapGet filter
Wskazówki dotyczące wydajności routingu
Gdy aplikacja ma problemy z wydajnością, routing jest często podejrzewany jako problem. Podejrzewa się, że routing polega na tym, że struktury, takie jak kontrolery i Razor strony, zgłaszają ilość czasu spędzonego w strukturze w komunikatach rejestrowania. W przypadku znacznej różnicy między czasem zgłoszonym przez kontrolery a łącznym czasem żądania:
- Deweloperzy eliminują kod aplikacji jako źródło problemu.
- Często zakłada się, że routing jest przyczyną.
Routing jest testowany pod kątem wydajności przy użyciu tysięcy punktów końcowych. Jest mało prawdopodobne, aby typowa aplikacja napotkała problem z wydajnością, ponieważ jest zbyt duża. Najczęstszą główną przyczyną niskiej wydajności routingu jest zwykle nieprawidłowe zachowanie niestandardowego oprogramowania pośredniczącego.
W poniższym przykładzie kodu przedstawiono podstawową technikę zawężania źródła opóźnienia:
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.");
Routing czasowy:
- Przeplataj każde oprogramowanie pośredniczące kopią oprogramowania pośredniczącego chronometrażu pokazanego w poprzednim kodzie.
- Dodaj unikatowy identyfikator, aby skorelować dane chronometrażu z kodem.
Jest to podstawowy sposób zawężenia opóźnienia, gdy jest to znaczące, na przykład więcej niż 10ms
. Odejmowanie Time 2
z Time 1
raportów czasu spędzonego wewnątrz oprogramowania pośredniczącego UseRouting
.
Poniższy kod używa bardziej kompaktowego podejścia do poprzedniego kodu chronometrażu:
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.");
Potencjalnie kosztowne funkcje routingu
Poniższa lista zawiera wgląd w funkcje routingu, które są stosunkowo kosztowne w porównaniu z podstawowymi szablonami tras:
- Wyrażenia regularne: istnieje możliwość zapisywania wyrażeń regularnych, które są złożone lub mają długi czas z niewielką ilością danych wejściowych.
- Segmenty złożone (
{x}-{y}-{z}
):- Są znacznie droższe niż analizowanie zwykłego segmentu ścieżki adresu URL.
- Powoduje przydzielenie wielu podciągów.
- Dostęp do danych synchronicznych: wiele złożonych aplikacji ma dostęp do bazy danych w ramach routingu. Użyj punktów rozszerzalności, takich jak MatcherPolicy i EndpointSelectorContext, które są asynchroniczne.
Wskazówki dotyczące dużych tabel tras
Domyślnie ASP.NET Core używa algorytmu routingu, który wymienia pamięć na czas procesora CPU. Ma to miły wpływ, że czas dopasowywania trasy zależy tylko od długości ścieżki do dopasowania, a nie liczby tras. Jednak takie podejście może być potencjalnie problematyczne w niektórych przypadkach, gdy aplikacja ma dużą liczbę tras (w tysiącach) i istnieje duża ilość zmiennych prefiksów w trasach. Jeśli na przykład trasy mają parametry we wczesnych segmentach trasy, na przykład {parameter}/some/literal
.
Jest mało prawdopodobne, aby aplikacja napotkała sytuację, w której jest to problem, chyba że:
- W aplikacji istnieje duża liczba tras korzystających z tego wzorca.
- W aplikacji istnieje duża liczba tras.
Jak określić, czy aplikacja jest uruchomiona w dużym problemie z tabelą tras
- Istnieją dwa objawy do wyszukania:
- Aplikacja powoli zaczyna się od pierwszego żądania.
- Należy pamiętać, że jest to wymagane, ale nie wystarczające. Istnieje wiele innych problemów niezwiązanych z trasą, które mogą powodować powolne uruchamianie aplikacji. Sprawdź poniższy warunek, aby dokładnie określić, czy aplikacja działa w tej sytuacji.
- Aplikacja zużywa dużo pamięci podczas uruchamiania, a zrzut pamięci pokazuje dużą liczbę
Microsoft.AspNetCore.Routing.Matching.DfaNode
wystąpień.
- Aplikacja powoli zaczyna się od pierwszego żądania.
Jak rozwiązać ten problem
Istnieje kilka technik i optymalizacji, które można zastosować do tras, które w dużej mierze poprawią ten scenariusz:
- Zastosuj ograniczenia trasy do parametrów, na przykład
{parameter:int}
,{parameter:guid}
,{parameter:regex(\\d+)}
itp., jeśli to możliwe.- Dzięki temu algorytm routingu może wewnętrznie zoptymalizować struktury używane do dopasowywania i drastycznie zmniejszyć ilość używanej pamięci.
- W zdecydowanej większości przypadków wystarczy, aby wrócić do akceptowalnego zachowania.
- Zmień trasy, aby przenieść parametry do późniejszych segmentów w szablonie.
- Zmniejsza to liczbę możliwych "ścieżek", aby dopasować punkt końcowy na daną ścieżkę.
- Użyj trasy dynamicznej i dynamicznie wykonaj mapowanie na kontroler/stronę.
- Można to osiągnąć przy użyciu metod
MapDynamicControllerRoute
iMapDynamicPageRoute
.
- Można to osiągnąć przy użyciu metod
Wskazówki dla autorów bibliotek
Ta sekcja zawiera wskazówki dotyczące autorów bibliotek opartych na routingu. Te szczegóły mają na celu zapewnienie, że deweloperzy aplikacji mają dobre środowisko korzystania z bibliotek i struktur rozszerzających routing.
Definiowanie punktów końcowych
Aby utworzyć strukturę korzystającą z routingu do dopasowywania adresów URL, zacznij od zdefiniowania środowiska użytkownika, które opiera się na systemie UseEndpoints.
Kompilacja DO na podstawie funkcji IEndpointRouteBuilder. Dzięki temu użytkownicy mogą tworzyć struktury przy użyciu innych funkcji ASP.NET Core bez pomyłek. Każdy szablon ASP.NET Core zawiera routing. Załóżmy, że routing jest obecny i znany użytkownikom.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
NALEŻY zwrócić zapieczętowany typ betonowy z wywołania, do MapMyFramework(...)
którego implementuje IEndpointConventionBuilderelement . Większość metod struktury Map...
jest zgodne z tym wzorcem. Interfejs IEndpointConventionBuilder
:
- Umożliwia komponowania metadanych.
- Jest celem różnych metod rozszerzeń.
Deklarowanie własnego typu umożliwia dodanie do konstruktora własnych funkcji specyficznych dla platformy. Można zawinąć konstruktora zadeklarowanego przez platformę i przekazać do niego wywołania.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
ROZWAŻ napisanie własnego EndpointDataSourcepliku . EndpointDataSource
jest elementem pierwotnym niskiego poziomu do deklarowania i aktualizowania kolekcji punktów końcowych. EndpointDataSource
to zaawansowany interfejs API używany przez kontrolery i Razor strony.
Testy routingu mają podstawowy przykład nieakcyjnego źródła danych.
ROZWAŻ zaimplementowanie .GetGroupedEndpoints Zapewnia to pełną kontrolę nad uruchamianiem konwencji grupy i ostatnimi metadanymi w zgrupowanych punktach końcowych. Umożliwia to na przykład niestandardowe EndpointDataSource
implementacje uruchamiania filtrów punktów końcowych dodanych do grup.
NIE próbuj domyślnie rejestrować konta EndpointDataSource
. Wymagaj od użytkowników zarejestrowania struktury w programie UseEndpoints. Filozofią routingu jest to, że nic nie jest dołączane domyślnie i UseEndpoints
jest to miejsce do rejestrowania punktów końcowych.
Tworzenie zintegrowanego z routingiem oprogramowania pośredniczącego
ROZWAŻ zdefiniowanie typów metadanych jako interfejsu.
DO umożliwia używanie typów metadanych jako atrybutu w klasach i metodach.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Struktury, takie jak kontrolery i Razor strony, obsługują stosowanie atrybutów metadanych do typów i metod. W przypadku deklarowania typów metadanych:
- Udostępnij je jako atrybuty.
- Większość użytkowników zna stosowanie atrybutów.
Deklarowanie typu metadanych jako interfejsu zwiększa kolejną warstwę elastyczności:
- Interfejsy są komponowalne.
- Deweloperzy mogą zadeklarować własne typy, które łączą wiele zasad.
NIE umożliwia przesłaniania metadanych, jak pokazano w poniższym przykładzie:
[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() { }
}
Najlepszym sposobem przestrzegania tych wytycznych jest unikanie definiowania metadanych znaczników:
- Nie należy po prostu szukać obecności typu metadanych.
- Zdefiniuj właściwość na metadanych i sprawdź właściwość .
Kolekcja metadanych jest uporządkowana i obsługuje zastępowanie według priorytetu. W przypadku kontrolerów metadane metody akcji są najbardziej specyficzne.
DO sprawia, że oprogramowanie pośredniczące jest przydatne z routingiem i bez tego routingu:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Jako przykład tej wskazówki należy wziąć pod uwagę UseAuthorization
oprogramowanie pośredniczące. Oprogramowanie pośredniczące autoryzacji umożliwia przekazanie zasad rezerwowych. Zasady rezerwowe, jeśli zostały określone, mają zastosowanie do obu następujących elementów:
- Punkty końcowe bez określonych zasad.
- Żądania, które nie są zgodne z punktem końcowym.
Dzięki temu oprogramowanie pośredniczące autoryzacji jest przydatne poza kontekstem routingu. Oprogramowanie pośredniczące autoryzacji może służyć do tradycyjnego programowania oprogramowania pośredniczącego.
Diagnostyka debugowania
Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw wartość Logging:LogLevel:Microsoft
Debug
. W środowisku deweloperów ustaw poziom dziennika w pliku appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Dodatkowe zasoby
Routing jest odpowiedzialny za dopasowywanie przychodzących żądań HTTP i wysyłanie tych żądań do punktów końcowych wykonywalnych aplikacji. Punkty końcowe to jednostki aplikacji kodu obsługi żądań wykonywalnych. Punkty końcowe są definiowane w aplikacji i konfigurowane podczas uruchamiania aplikacji. Proces dopasowywania punktu końcowego może wyodrębnić wartości z adresu URL żądania i podać te wartości do przetwarzania żądań. Korzystając z informacji o punkcie końcowym z aplikacji, routing jest również w stanie wygenerować adresy URL mapujące na punkty końcowe.
Aplikacje mogą konfigurować routing przy użyciu:
- Kontrolery
- Razor Pages
- SignalR
- usługi gRPC
- Oprogramowanie pośredniczące z obsługą punktu końcowego, takie jak kontrole kondycji.
- Delegaty i lambdy zarejestrowane przy użyciu routingu.
W tym artykule opisano szczegóły niskiego poziomu dotyczące routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:
- W przypadku kontrolerów zobacz Routing do akcji kontrolera w ASP.NET Core.
- Aby zapoznać Razor się z konwencjami stron, zobacz Razor Strony route and app conventions in ASP.NET Core (Konwencje tras stron i aplikacji w programie ASP.NET Core).
Podstawy routingu
Poniższy kod przedstawia podstawowy przykład routingu:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy przykład zawiera pojedynczy punkt końcowy przy użyciu MapGet metody :
- Po wysłaniu żądania HTTP
GET
do głównego adresu URL/
:- Delegat żądania jest wykonywany.
Hello World!
jest zapisywany w odpowiedzi HTTP.
- Jeśli metoda żądania nie
GET
jest lub główny adres URL nie/
jest , nie jest zgodna trasa i zwracany jest http 404.
Routing używa pary oprogramowania pośredniczącego zarejestrowanego przez UseRouting i UseEndpoints:
UseRouting
Dodaje trasę zgodną z potokiem oprogramowania pośredniczącego. To oprogramowanie pośredniczące analizuje zestaw punktów końcowych zdefiniowanych w aplikacji i wybiera najlepsze dopasowanie na podstawie żądania.UseEndpoints
Dodaje wykonywanie punktu końcowego do potoku oprogramowania pośredniczącego. Uruchamia delegata skojarzonego z wybranym punktem końcowym.
Aplikacje zazwyczaj nie muszą wywoływać ani UseRouting
UseEndpoints
. WebApplicationBuilder Konfiguruje potok oprogramowania pośredniczącego, który opakowuje oprogramowanie pośredniczące dodane za Program.cs
pomocą poleceń UseRouting
i UseEndpoints
. Jednak aplikacje mogą zmieniać kolejność, w jakiej UseRouting
i UseEndpoints
uruchamiać, wywołując te metody jawnie. Na przykład następujący kod wykonuje jawne wywołanie metody UseRouting
:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
Powyższy kod:
- Wywołanie w celu
app.Use
zarejestrowania niestandardowego oprogramowania pośredniczącego uruchamianego na początku potoku. - Wywołanie w celu
UseRouting
skonfigurowania odpowiedniego oprogramowania pośredniczącego trasy do uruchomienia po niestandardowym oprogramowaniem pośredniczącym. - Punkt końcowy zarejestrowany
MapGet
przy użyciu jest uruchamiany na końcu potoku.
Jeśli poprzedni przykład nie zawierał wywołania metody UseRouting
, niestandardowe oprogramowanie pośredniczące zostanie uruchomione po dopasowaniu trasy oprogramowania pośredniczącego.
Punkty końcowe
Metoda MapGet
służy do definiowania punktu końcowego. Punkt końcowy to coś, co może być następujące:
- Wybrane przez dopasowanie adresu URL i metody HTTP.
- Wykonywane przez uruchomienie delegata.
Punkty końcowe, które mogą być dopasowane i wykonywane przez aplikację, są konfigurowane w programie UseEndpoints
. Na przykład metody MapGet, MapPosti podobne łączą delegatów żądań z systemem routingu. Dodatkowe metody mogą służyć do łączenia funkcji platformy ASP.NET Core z systemem routingu:
- MapRazorPages for Razor Pages
- MapControllers dla kontrolerów
- MapHub THub<> forSignalR
- MapGrpcService TService<> dla gRPC
W poniższym przykładzie przedstawiono routing z bardziej zaawansowanym szablonem trasy:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
Ciąg /hello/{name:alpha}
jest szablonem trasy. Szablon trasy służy do konfigurowania sposobu dopasowania punktu końcowego. W takim przypadku szablon jest zgodny z następującymi elementami:
- Adres URL podobny do
/hello/Docs
- Każda ścieżka adresu URL rozpoczynająca się
/hello/
od sekwencji znaków alfabetycznych.:alpha
stosuje ograniczenie trasy, które pasuje tylko do znaków alfabetycznych. Ograniczenia tras zostały wyjaśnione w dalszej części tego artykułu.
Drugi segment ścieżki adresu URL: {name:alpha}
- Jest powiązany z parametrem
name
. - Jest przechwytywany i przechowywany w pliku HttpRequest.RouteValues.
W poniższym przykładzie przedstawiono routing z kontrolą kondycji i autoryzacją:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
W poprzednim przykładzie pokazano, jak:
- Oprogramowanie pośredniczące autoryzacji może być używane z routingiem.
- Punkty końcowe mogą służyć do konfigurowania zachowania autoryzacji.
Wywołanie MapHealthChecks dodaje punkt końcowy kontroli kondycji. Łączenie RequireAuthorization łańcucha do tego wywołania powoduje dołączenie zasad autoryzacji do punktu końcowego.
Wywoływanie UseAuthentication i UseAuthorization dodawanie oprogramowania pośredniczącego uwierzytelniania i autoryzacji. Te oprogramowanie pośredniczące są umieszczane między elementami UseRouting i UseEndpoints
, dzięki czemu mogą:
- Zobacz, który punkt końcowy został wybrany przez
UseRouting
element . - Przed wysłaniem do punktu końcowego zastosuj zasady UseEndpoints autoryzacji.
Metadane punktu końcowego
W poprzednim przykładzie istnieją dwa punkty końcowe, ale tylko punkt końcowy kontroli kondycji ma dołączone zasady autoryzacji. Jeśli żądanie pasuje do punktu końcowego sprawdzania kondycji, /healthz
zostanie wykonane sprawdzanie autoryzacji. Pokazuje to, że punkty końcowe mogą mieć dołączone dodatkowe dane. Te dodatkowe dane są nazywane metadanymi punktu końcowego:
- Metadane mogą być przetwarzane przez oprogramowanie pośredniczące obsługujące routing.
- Metadane mogą być dowolnego typu platformy .NET.
Pojęcia dotyczące routingu
System routingu opiera się na potoku oprogramowania pośredniczącego, dodając zaawansowaną koncepcję punktu końcowego . Punkty końcowe reprezentują jednostki funkcjonalności aplikacji, które różnią się od siebie pod względem routingu, autoryzacji i dowolnej liczby systemów ASP.NET Core.
definicja punktu końcowego platformy ASP.NET Core
Punkt końcowy platformy ASP.NET Core to:
- Plik wykonywalny: ma element RequestDelegate.
- Rozszerzalne: ma kolekcję metadanych .
- Można wybrać: opcjonalnie zawiera informacje o routingu.
- Wyliczenie: kolekcja punktów końcowych może być wyświetlana przez pobranie elementu EndpointDataSource z di.
Poniższy kod pokazuje, jak pobrać i sprawdzić punkt końcowy pasujący do bieżącego żądania:
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.");
Punkt końcowy, jeśli został wybrany, można pobrać z pliku HttpContext
. Jego właściwości można sprawdzić. Obiekty punktu końcowego są niezmienne i nie można ich modyfikować po utworzeniu. Najczęstszym typem RouteEndpointpunktu końcowego jest . RouteEndpoint
zawiera informacje, które umożliwiają wybranie go przez system routingu.
W poprzednim kodzie aplikacja. Użyj konfiguracji wbudowanego oprogramowania pośredniczącego.
Poniższy kod pokazuje, że w zależności od tego, gdzie app.Use
jest wywoływana w potoku, punkt końcowy może nie być:
// 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);
});
Poprzedni przykład dodaje Console.WriteLine
instrukcje, które wyświetlają, czy wybrano punkt końcowy. Aby uzyskać czytelność, przykład przypisuje nazwę wyświetlaną do podanego /
punktu końcowego.
Powyższy przykład obejmuje również wywołania i UseRouting
UseEndpoints
sterowanie dokładnie tym, kiedy oprogramowanie pośredniczące działa w potoku.
Uruchomienie tego kodu z adresem URL wyświetlania /
:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Uruchomienie tego kodu przy użyciu dowolnego innego adresu URL powoduje wyświetlenie:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Te dane wyjściowe pokazują, że:
- Punkt końcowy ma zawsze wartość null przed
UseRouting
wywołaniem. - Jeśli zostanie znalezione dopasowanie, punkt końcowy nie ma wartości null między
UseRouting
i UseEndpoints. - Oprogramowanie
UseEndpoints
pośredniczące jest terminalem po znalezieniu dopasowania. Oprogramowanie pośredniczące terminala jest zdefiniowane w dalszej części tego artykułu. - Oprogramowanie pośredniczące po
UseEndpoints
wykonaniu tylko wtedy, gdy nie zostanie znalezione dopasowanie.
Oprogramowanie UseRouting
pośredniczące używa SetEndpoint metody w celu dołączenia punktu końcowego do bieżącego kontekstu. Można zastąpić UseRouting
oprogramowanie pośredniczące logiką niestandardową i nadal korzystać z zalet korzystania z punktów końcowych. Punkty końcowe są typami pierwotnymi niskiego poziomu, takimi jak oprogramowanie pośredniczące, i nie są powiązane z implementacją routingu. Większość aplikacji nie musi zastępować UseRouting
logiką niestandardową.
Oprogramowanie UseEndpoints
pośredniczące jest przeznaczone do użycia w parze z oprogramowaniem pośredniczącym UseRouting
. Podstawowa logika do wykonania punktu końcowego nie jest skomplikowana. Użyj GetEndpoint polecenia , aby pobrać punkt końcowy, a następnie wywołać jego RequestDelegate właściwość.
Poniższy kod pokazuje, jak oprogramowanie pośredniczące może wpływać na routing lub reagować na nie:
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 { }
W poprzednim przykładzie przedstawiono dwie ważne pojęcia:
- Oprogramowanie pośredniczące może działać przed
UseRouting
zmodyfikowaniem danych, na których działa routing.- Zwykle oprogramowanie pośredniczące, które pojawia się przed zmodyfikowaniem routingu, modyfikuje niektóre właściwości żądania, takie jak UseRewriter, UseHttpMethodOverridelub UsePathBase.
- Oprogramowanie pośredniczące może działać między elementami
UseRouting
i UseEndpoints w celu przetworzenia wyników routingu przed wykonaniem punktu końcowego.- Oprogramowanie pośredniczące uruchamiane między elementami
UseRouting
iUseEndpoints
:- Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
- Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami
UseAuthorization
iUseCors
.
- Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.
- Oprogramowanie pośredniczące uruchamiane między elementami
Powyższy kod przedstawia przykład niestandardowego oprogramowania pośredniczącego obsługującego zasady poszczególnych punktów końcowych. Oprogramowanie pośredniczące zapisuje dziennik inspekcji dostępu do poufnych danych w konsoli. Oprogramowanie pośredniczące można skonfigurować do inspekcji punktu końcowego za RequiresAuditAttribute
pomocą metadanych. W tym przykładzie pokazano wzorzec zgody, w którym są poddawane inspekcji tylko punkty końcowe oznaczone jako poufne. Tę logikę można zdefiniować w odwrotnej kolejności, przeprowadzając inspekcję wszystkich elementów, które nie są oznaczone jako bezpieczne, na przykład. System metadanych punktu końcowego jest elastyczny. Ta logika może być zaprojektowana w dowolny sposób odpowiednio do przypadku użycia.
Powyższy przykładowy kod ma na celu zademonstrowanie podstawowych pojęć dotyczących punktów końcowych. Przykład nie jest przeznaczony do użytku produkcyjnego. Bardziej kompletna wersja oprogramowania pośredniczącego dziennika inspekcji:
- Zaloguj się do pliku lub bazy danych.
- Dołącz szczegóły, takie jak użytkownik, adres IP, nazwa poufnego punktu końcowego i inne.
Metadane RequiresAuditAttribute
zasad inspekcji są definiowane jako Attribute
łatwiejsze w użyciu z platformami opartymi na klasach, takimi jak kontrolery i SignalR. W przypadku używania trasy do kodu:
- Metadane są dołączane do interfejsu API konstruktora.
- Struktury oparte na klasach obejmują wszystkie atrybuty odpowiedniej metody i klasy podczas tworzenia punktów końcowych.
Najlepszym rozwiązaniem dla typów metadanych jest zdefiniowanie ich jako interfejsów lub atrybutów. Interfejsy i atrybuty umożliwiają ponowne użycie kodu. System metadanych jest elastyczny i nie nakłada żadnych ograniczeń.
Porównanie oprogramowania pośredniczącego terminalu z routingiem
W poniższym przykładzie pokazano zarówno oprogramowanie pośredniczące terminala, jak i routing:
// 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.");
Styl oprogramowania pośredniczącego pokazany za pomocą Approach 1:
programu to oprogramowanie pośredniczące terminalu. Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ wykonuje zgodną operację:
- Zgodna operacja w poprzednim przykładzie dotyczy
Path == "/"
oprogramowania pośredniczącego iPath == "/Routing"
routingu. - Gdy dopasowanie zakończy się pomyślnie, wykonuje pewne funkcje i zwraca, zamiast wywoływania oprogramowania pośredniczącego
next
.
Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ kończy wyszukiwanie, wykonuje niektóre funkcje, a następnie zwraca.
Poniższa lista porównuje oprogramowanie pośredniczące terminala z routingiem:
- Obie metody umożliwiają zakończenie potoku przetwarzania:
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
next
- Punkty końcowe są zawsze terminalne.
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
- Oprogramowanie pośredniczące terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
- Punkty końcowe są wykonywane w pozycji UseEndpoints.
- Oprogramowanie pośredniczące terminalu umożliwia dowolnemu kodowi określenie, kiedy oprogramowanie pośredniczące jest zgodne:
- Niestandardowy kod dopasowania trasy może być pełny i trudny do poprawnego zapisu.
- Routing udostępnia proste rozwiązania dla typowych aplikacji. Większość aplikacji nie wymaga niestandardowego kodu dopasowania tras.
- Interfejs punktów końcowych z oprogramowaniem pośredniczącym, takim jak
UseAuthorization
iUseCors
.- Używanie oprogramowania pośredniczącego terminalu z
UseAuthorization
oprogramowaniem pośredniczącym lubUseCors
wymaga ręcznego łączenia się z systemem autoryzacji.
- Używanie oprogramowania pośredniczącego terminalu z
Punkt końcowy definiuje oba:
- Pełnomocnik do przetwarzania żądań.
- Kolekcja dowolnych metadanych. Metadane służą do implementowania zagadnień krzyżowych na podstawie zasad i konfiguracji dołączonych do każdego punktu końcowego.
Oprogramowanie pośredniczące terminalu może być skutecznym narzędziem, ale może wymagać:
- Znaczna ilość kodowania i testowania.
- Ręczna integracja z innymi systemami w celu osiągnięcia żądanego poziomu elastyczności.
Przed napisaniem oprogramowania pośredniczącego terminalu rozważ integrację z routingiem.
Istniejące oprogramowanie pośredniczące terminalu integrujące się z usługą Map lub MapWhen zwykle może zostać przekształcone w punkt końcowy obsługujący routing. MapHealthChecks demonstruje wzorzec oprogramowania routera:
- Napisz metodę rozszerzenia w pliku IEndpointRouteBuilder.
- Utwórz zagnieżdżony potok oprogramowania pośredniczącego przy użyciu polecenia CreateApplicationBuilder.
- Dołącz oprogramowanie pośredniczące do nowego potoku. W tym przypadku . UseHealthChecks
- Build potok oprogramowania pośredniczącego do pliku RequestDelegate.
- Wywołaj
Map
i podaj nowy potok oprogramowania pośredniczącego. - Zwróć obiekt konstruktora dostarczony przez
Map
metodę rozszerzenia.
Poniższy kod przedstawia użycie narzędzi MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
W poprzednim przykładzie pokazano, dlaczego zwracanie obiektu konstruktora jest ważne. Zwracanie obiektu konstruktora umożliwia deweloperowi aplikacji konfigurowanie zasad, takich jak autoryzacja dla punktu końcowego. W tym przykładzie oprogramowanie pośredniczące kontroli kondycji nie ma bezpośredniej integracji z systemem autoryzacji.
System metadanych został utworzony w odpowiedzi na problemy napotykane przez autorów rozszerzeń przy użyciu oprogramowania pośredniczącego terminalu. Dla każdego oprogramowania pośredniczącego problematyczne jest zaimplementowanie własnej integracji z systemem autoryzacji.
Dopasowywanie adresu URL
- Jest procesem, za pomocą którego routing jest zgodny z żądaniem przychodzącym do punktu końcowego.
- Opiera się na danych w ścieżce i nagłówkach adresu URL.
- Można rozszerzyć, aby uwzględnić dowolne dane w żądaniu.
Po wykonaniu oprogramowania pośredniczącego routingu ustawia Endpoint
ona wartości i kieruje je do funkcji żądania z HttpContext bieżącego żądania:
- Wywołanie metody HttpContext.GetEndpoint pobiera punkt końcowy.
HttpRequest.RouteValues
pobiera kolekcję wartości tras.
Oprogramowanie pośredniczące jest uruchamiane po tym, jak oprogramowanie pośredniczące routingu może sprawdzić punkt końcowy i podjąć działania. Na przykład oprogramowanie pośredniczące autoryzacji może przesłuchić kolekcję metadanych punktu końcowego dla zasad autoryzacji. Po wykonaniu całego oprogramowania pośredniczącego w potoku przetwarzania żądań wywoływany jest delegat wybranego punktu końcowego.
System routingu w routingu punktów końcowych jest odpowiedzialny za wszystkie decyzje dotyczące wysyłania. Ponieważ oprogramowanie pośredniczące stosuje zasady na podstawie wybranego punktu końcowego, ważne jest, aby:
- Każda decyzja, która może mieć wpływ na wysyłanie lub stosowanie zasad zabezpieczeń, jest dokonana wewnątrz systemu routingu.
Ostrzeżenie
W przypadku zgodności z poprzednimi wersjami po wykonaniu delegata punktu końcowego kontroler lub Razor strony właściwości RouteContext.RouteData są ustawione na odpowiednie wartości na podstawie przetwarzania żądań wykonanego do tej pory.
Typ RouteContext
zostanie oznaczony jako przestarzały w przyszłej wersji:
- Przeprowadź migrację
RouteData.Values
doHttpRequest.RouteValues
. - Migrowanie
RouteData.DataTokens
w celu pobrania IDataTokensMetadata z metadanych punktu końcowego.
Dopasowywanie adresów URL działa w konfigurowalnym zestawie faz. W każdej fazie dane wyjściowe są zestawem dopasowań. Zestaw dopasowań można zawęzić dalej przez następną fazę. Implementacja routingu nie gwarantuje kolejności przetwarzania pasujących punktów końcowych. Wszystkie możliwe dopasowania są przetwarzane jednocześnie. Fazy dopasowywania adresów URL są wykonywane w następującej kolejności. ASP.NET Core:
- Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem w MatcherPolicy zestawie wystąpień.
- Używa elementu EndpointSelector , aby podjąć ostateczną decyzję z poprzedniej listy.
Lista punktów końcowych jest priorytetowa zgodnie z:
- RouteEndpoint.Order
- Pierwszeństwo szablonu trasy
Wszystkie pasujące punkty końcowe są przetwarzane w każdej fazie do momentu EndpointSelector osiągnięcia. Jest EndpointSelector
to ostatnia faza. Wybiera punkt końcowy o najwyższym priorytcie z dopasowań jako najlepszy. Jeśli istnieją inne dopasowania z tym samym priorytetem co najlepsze dopasowanie, zgłaszany jest niejednoznaczny wyjątek dopasowania.
Pierwszeństwo trasy jest obliczane na podstawie bardziej szczegółowego szablonu trasy o wyższym priorytecie. Rozważmy na przykład szablony /hello
i /{message}
:
- Oba są zgodne ze ścieżką
/hello
adresu URL . /hello
jest bardziej szczegółowy i dlatego wyższy priorytet.
Ogólnie rzecz biorąc, pierwszeństwo trasy dobrze nadaje się do wyboru najlepszego dopasowania do rodzajów schematów adresów URL używanych w praktyce. Używaj Order tylko wtedy, gdy jest to konieczne, aby uniknąć niejednoznaczności.
Ze względu na rodzaj rozszerzalności zapewnianej przez routing nie jest możliwe, aby system routingu obliczał przed upływem czasu niejednoznaczne trasy. Rozważmy przykład, taki jak szablony /{message:alpha}
tras i /{message:int}
:
- Ograniczenie
alpha
pasuje tylko do znaków alfabetycznych. - Ograniczenie
int
pasuje tylko do liczb. - Te szablony mają taki sam pierwszeństwo trasy, ale nie ma jednego adresu URL, który oba te szablony są zgodne.
- Jeśli system routingu zgłosił błąd niejednoznaczności podczas uruchamiania, zablokuje to prawidłowy przypadek użycia.
Ostrzeżenie
Kolejność operacji wewnątrz UseEndpoints nie ma wpływu na zachowanie routingu z jednym wyjątkiem. MapControllerRoute i MapAreaRoute automatycznie przypisz wartość zamówienia do swoich punktów końcowych na podstawie kolejności, w której są wywoływane. Symuluje to długotrwałe zachowanie kontrolerów bez systemu routingu zapewniające takie same gwarancje jak starsze implementacje routingu.
Routing punktów końcowych w ASP.NET Core:
- Nie ma pojęcia tras.
- Nie zapewnia gwarancji zamawiania. Wszystkie punkty końcowe są przetwarzane jednocześnie.
Pierwszeństwo szablonu trasy i kolejność wyboru punktu końcowego
Pierwszeństwo szablonu trasy to system, który przypisuje każdemu szablonowi trasy wartość na podstawie jego specyfiki. Pierwszeństwo szablonu trasy:
- Pozwala uniknąć konieczności dostosowywania kolejności punktów końcowych w typowych przypadkach.
- Próby dopasowania do zdrowych oczekiwań dotyczących zachowania routingu.
Rozważmy na przykład szablony /Products/List
i /Products/{id}
. Uzasadnione byłoby założenie, że /Products/List
jest to lepsze dopasowanie niż /Products/{id}
dla ścieżki /Products/List
adresu URL . Działa to, ponieważ segment /List
literału jest uznawany za lepszy priorytet niż segment /{id}
parametrów .
Szczegółowe informacje o sposobie działania pierwszeństwa są powiązane z definiowaniem szablonów tras:
- Szablony z większą większa część segmentów są uważane za bardziej szczegółowe.
- Segment z tekstem literału jest uznawany za bardziej szczegółowy niż segment parametrów.
- Segment parametrów z ograniczeniem jest uważany za bardziej szczegółowy niż jeden bez.
- Segment złożony jest uznawany za specyficzny jako segment parametrów z ograniczeniem.
- Parametry catch-all są najmniej specyficzne. Zobacz sekcję Catch-all w sekcji Szablony tras, aby uzyskać ważne informacje na temat tras catch-all.
Pojęcia dotyczące generowania adresów URL
Generowanie adresu URL:
- Jest procesem, za pomocą którego routing może utworzyć ścieżkę adresu URL na podstawie zestawu wartości trasy.
- Umożliwia logiczne rozdzielenie punktów końcowych i adresów URL, które do nich uzyskują dostęp.
Routing punktów końcowych obejmuje LinkGenerator interfejs API. LinkGenerator
jest pojedynczą usługą dostępną z di. Interfejs LinkGenerator
API może być używany poza kontekstem wykonywania żądania. Mvc.IUrlHelper i scenariusze, które opierają się na IUrlHelperelementach , takich jak Pomocnicy tagów, Pomocnicy HTML i Wyniki akcji, używają interfejsu LinkGenerator
API wewnętrznie, aby zapewnić możliwości generowania linków.
Generator linków jest wspierany przez koncepcję schematów adresów i adresów. Schemat adresów to sposób określania punktów końcowych, które powinny być brane pod uwagę podczas generowania linków. Na przykład scenariusze nazw tras i wartości tras, które wielu użytkowników zna z kontrolerów i Razor stron, są implementowane jako schemat adresów.
Generator linków może łączyć się z kontrolerami i Razor stronami za pomocą następujących metod rozszerzeń:
Przeciążenia tych metod akceptują argumenty, które obejmują HttpContext
element . Te metody są funkcjonalnie równoważne url.Action i Url.Page, ale oferują dodatkową elastyczność i opcje.
Metody GetPath*
są najbardziej podobne do Url.Action
metod i Url.Page
, w których generują identyfikator URI zawierający ścieżkę bezwzględną. Metody GetUri*
zawsze generują bezwzględny identyfikator URI zawierający schemat i host. Metody, które akceptują HttpContext
generowanie identyfikatora URI w kontekście wykonywania żądania. Wartości trasy otoczenia , ścieżka podstawowa adresu URL, schemat i host z wykonywanego żądania są używane, chyba że zostaną zastąpione.
LinkGenerator jest wywoływana przy użyciu adresu. Generowanie identyfikatora URI odbywa się w dwóch krokach:
- Adres jest powiązany z listą punktów końcowych pasujących do adresu.
- Każdy punkt końcowy RoutePattern jest oceniany do momentu znalezienia wzorca trasy zgodnego z podanymi wartościami. Wynikowe dane wyjściowe są łączone z innymi częściami identyfikatora URI dostarczonymi do generatora linków i zwracanymi.
Metody udostępniane przez LinkGenerator obsługę standardowych możliwości generowania linków dla dowolnego typu adresu. Najwygodniejszym sposobem korzystania z generatora linków jest użycie metod rozszerzeń, które wykonują operacje dla określonego typu adresu:
Extension, metoda | opis |
---|---|
GetPathByAddress | Generuje identyfikator URI ze ścieżką bezwzględną na podstawie podanych wartości. |
GetUriByAddress | Generuje bezwzględny identyfikator URI na podstawie podanych wartości. |
Ostrzeżenie
Zwróć uwagę na następujące konsekwencje wywoływania LinkGenerator metod:
Użyj
GetUri*
metod rozszerzeń z ostrożnością w konfiguracji aplikacji, która nie weryfikujeHost
nagłówka żądań przychodzących.Host
Jeśli nagłówek żądań przychodzących nie jest weryfikowany, niezaufane dane wejściowe żądania mogą być wysyłane z powrotem do klienta w identyfikatorach URI w widoku lub na stronie. Zalecamy skonfigurowanie serwera przez wszystkie aplikacje produkcyjne w celu zweryfikowania nagłówkaHost
pod kątem znanych prawidłowych wartości.Należy używać LinkGenerator z ostrożnością w oprogramowania pośredniczącego w połączeniu z
Map
lubMapWhen
.Map*
zmienia ścieżkę podstawową wykonywania żądania, co wpływa na dane wyjściowe generowania linków. LinkGenerator Wszystkie interfejsy API umożliwiają określenie ścieżki podstawowej. Określ pustą ścieżkę podstawową, aby cofnąć wpływ na generowanie linkówMap*
.
Przykład oprogramowania pośredniczącego
W poniższym przykładzie oprogramowanie pośredniczące używa interfejsu LinkGenerator API do utworzenia linku do metody akcji zawierającej listę produktów. Użycie generatora linków przez wstrzyknięcie go do klasy i wywołanie GenerateLink
jest dostępne dla dowolnej klasy w aplikacji:
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.");
}
}
Szablony tras
Tokeny w ramach {}
definicji parametrów trasy, które są powiązane, jeśli trasa jest zgodna. W segmencie trasy można zdefiniować więcej niż jeden parametr trasy, ale parametry trasy muszą być rozdzielone wartością literału. Na przykład:
{controller=Home}{action=Index}
nie jest prawidłową trasą, ponieważ nie ma wartości literału między {controller}
i {action}
. Parametry trasy muszą mieć nazwę i mogą mieć określone dodatkowe atrybuty.
Tekst literału inny niż parametry trasy (na przykład {id}
) i separator /
ścieżki musi być zgodny z tekstem w adresie URL. Dopasowywanie tekstu jest bez uwzględniania wielkości liter i oparte na dekodowanej reprezentacji ścieżki adresu URL. Aby dopasować ogranicznik {
parametru trasy literału lub }
, należy uruchomić ogranicznik, powtarzając znak. Na przykład {{
lub }}
.
Gwiazdka *
lub podwójna gwiazdka **
:
- Może służyć jako prefiks do parametru trasy w celu powiązania z rest identyfikatorem URI.
- Są nazywane parametrami catch-all . Na przykład :
blog/{**slug}
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
blog/
i ma dowolną wartość po niej. - Poniższa wartość
blog/
jest przypisywana do wartości trasy slug .
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
Ostrzeżenie
Parametr catch-all może być niepoprawnie zgodny z trasami z powodu błędu w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:
- Trasa typu catch-all, na przykład
{**slug}"
- Trasa catch-all nie pasuje do żądań, które powinna być zgodna.
- Usunięcie innych tras sprawia, że trasa catch-all zacznie działać.
Zobacz Usterki usługi GitHub 18677 i 16579 , na przykład przypadki, w których wystąpiła ta usterka.
Poprawka zgody na tę usterkę jest zawarta w zestawie .NET Core 3.1.301 SDK i nowszych wersjach. Poniższy kod ustawia przełącznik wewnętrzny, który naprawia tę usterkę:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Parametry catch-all mogą być również zgodne z pustym ciągiem.
Parametr catch-all ucieczki odpowiednich znaków, gdy trasa jest używana do generowania adresu URL, w tym znaków separatora /
ścieżki. Na przykład trasa foo/{*path}
z wartościami { path = "my/path" }
tras generuje foo/my%2Fpath
wartość . Zwróć uwagę na unikniętą ukośnik. Aby użyć znaków separatora ścieżki dwukierunkowej, użyj prefiksu parametru **
trasy. Trasa foo/{**path}
z elementem { path = "my/path" }
generuje foo/my/path
wartość .
Wzorce adresów URL, które próbują przechwycić nazwę pliku z opcjonalnym rozszerzeniem pliku, mają dodatkowe uwagi. Rozważmy na przykład szablon files/{filename}.{ext?}
. Gdy wartości obu filename
i ext
istnieją, oba wartości są wypełniane. Jeśli w adresie URL istnieje tylko wartość filename
, trasa jest zgodna, ponieważ końcowy .
jest opcjonalny. Następujące adresy URL pasują do tej trasy:
/files/myFile.txt
/files/myFile
Parametry trasy mogą mieć wartości domyślne wyznaczone przez określenie wartości domyślnej po nazwie parametru oddzielonej znakiem równości (=
). Na przykład {controller=Home}
definiuje Home
jako wartość domyślną dla elementu controller
. Wartość domyślna jest używana, jeśli w adresie URL parametru nie ma żadnej wartości. Parametry trasy są opcjonalne, dołączając znak zapytania (?
) na końcu nazwy parametru. Na przykład id?
. Różnica między opcjonalnymi wartościami a domyślnymi parametrami trasy to:
- Parametr trasy z wartością domyślną zawsze generuje wartość.
- Opcjonalny parametr ma wartość tylko wtedy, gdy wartość jest dostarczana przez adres URL żądania.
Parametry trasy mogą mieć ograniczenia, które muszą być zgodne z wartością trasy powiązaną z adresem URL. Dodanie :
i ograniczenie nazwy po nazwie parametru trasy określa ograniczenie wbudowane w parametrze trasy. Jeśli ograniczenie wymaga argumentów, są one ujęte w nawiasy (...)
po nazwie ograniczenia. Można określić wiele ograniczeń wbudowanych przez dołączenie innej :
nazwy i ograniczenia.
Nazwa ograniczenia i argumenty są przekazywane do IInlineConstraintResolver usługi w celu utworzenia wystąpienia IRouteConstraint do użycia w przetwarzaniu adresów URL. Na przykład szablon blog/{article:minlength(10)}
trasy określa minlength
ograniczenie z argumentem 10
. Aby uzyskać więcej informacji na temat ograniczeń tras i listy ograniczeń udostępnianych przez platformę, zobacz sekcję Ograniczenia trasy.
Parametry trasy mogą również zawierać transformatory parametrów. Transformatory parametrów przekształcają wartość parametru podczas generowania łączy i pasujących akcji i stron do adresów URL. Podobnie jak ograniczenia, transformatory parametrów można dodać w tekście do parametru trasy przez dodanie :
nazwy parametru i transformatora po nazwie parametru trasy. Na przykład szablon blog/{article:slugify}
trasy określa slugify
transformator. Aby uzyskać więcej informacji na temat transformatorów parametrów, zobacz sekcję Transformatory parametrów .
W poniższej tabeli przedstawiono przykładowe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI | Identyfikator URI żądania... |
---|---|---|
hello |
/hello |
Pasuje tylko do pojedynczej ścieżki /hello . |
{Page=Home} |
/ |
Dopasuj i ustawia wartość Page Home . |
{Page=Home} |
/Contact |
Dopasuj i ustawia wartość Page Contact . |
{controller}/{action}/{id?} |
/Products/List |
Mapuje na Products kontroler i List akcję. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapuje na Products kontroler i Details akcję z ustawioną wartościąid 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapuje na kontroler i Index metodęHome . Właściwość id jest ignorowana. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapuje na kontroler i Index metodęProducts . Właściwość id jest ignorowana. |
Użycie szablonu jest zwykle najprostszym podejściem do routingu. Ograniczenia i wartości domyślne można również określić poza szablonem trasy.
Złożone segmenty
Złożone segmenty są przetwarzane przez dopasowanie ograniczników literału od prawej do lewej w sposób niechłanny . Na przykład [Route("/a{b}c{d}")]
jest to złożony segment.
Złożone segmenty działają w określony sposób, aby można było z nich korzystać pomyślnie. W przykładzie w tej sekcji pokazano, dlaczego złożone segmenty działają prawidłowo tylko wtedy, gdy tekst ogranicznika nie pojawia się wewnątrz wartości parametrów. Użycie wyrażenia regularnego, a następnie ręczne wyodrębnienie wartości jest wymagane w przypadku bardziej złożonych przypadków.
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Jest to podsumowanie kroków, które routing wykonuje przy użyciu szablonu /a{b}c{d}
i ścieżki /abcd
adresu URL . Służy |
do wizualizacji sposobu działania algorytmu:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/abcd
jest wyszukiwany z prawej strony i znajduje/ab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/ab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - Brak pozostałego tekstu i brak pozostałego szablonu trasy, więc jest to dopasowanie.
Oto przykład negatywnego przypadku przy użyciu tego samego szablonu /a{b}c{d}
i ścieżki /aabcd
adresu URL . Służy |
do wizualizowania sposobu działania algorytmu. Ten przypadek nie jest zgodny, co zostało wyjaśnione przez ten sam algorytm:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/aabcd
jest wyszukiwany z prawej strony i znajduje/aab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/aab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/a|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - W tym momencie pozostaje tekst
a
, ale algorytm zabrakło szablonu trasy do przeanalizowania, więc nie jest to dopasowanie.
Ponieważ pasujący algorytm nie jest chciwy:
- Pasuje do najmniejszej ilości tekstu możliwego w każdym kroku.
- Każdy przypadek, w którym wartość ogranicznika pojawia się wewnątrz wartości parametrów, powoduje niezgodnie.
Wyrażenia regularne zapewniają znacznie większą kontrolę nad ich zachowaniem dopasowania.
Zachłanne dopasowanie, znane również jako leniwe dopasowanie, pasuje do największego możliwego ciągu. Niechłanny pasuje do najmniejszego możliwego ciągu.
Routing z znakami specjalnymi
Routing ze znakami specjalnymi może prowadzić do nieoczekiwanych wyników. Rozważmy na przykład kontroler z następującą metodą akcji:
[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;
}
Jeśli string id
zawiera następujące zakodowane wartości, mogą wystąpić nieoczekiwane wyniki:
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
Parametry trasy nie zawsze są dekodowane za pomocą adresu URL. Ten problem może zostać rozwiązany w przyszłości. Aby uzyskać więcej informacji, zobacz ten problem z usługą GitHub;
Ograniczenia trasy
Ograniczenia trasy są wykonywane, gdy wystąpiło dopasowanie do przychodzącego adresu URL, a ścieżka adresu URL jest tokenizowana do wartości tras. Ograniczenia tras zazwyczaj sprawdzają wartość trasy skojarzoną za pośrednictwem szablonu trasy i podejmowania prawdziwej lub fałszywej decyzji o tym, czy wartość jest akceptowalna. Niektóre ograniczenia trasy używają danych poza wartością trasy, aby rozważyć, czy żądanie można kierować. Na przykład HttpMethodRouteConstraint może zaakceptować lub odrzucić żądanie na podstawie jego czasownika HTTP. Ograniczenia są używane w żądaniach routingu i generowaniu linków.
Ostrzeżenie
Nie używaj ograniczeń do walidacji danych wejściowych. Jeśli ograniczenia są używane do walidacji danych wejściowych, nieprawidłowe dane wejściowe będą skutkować odpowiedzią 404
Nie znaleziono. Nieprawidłowe dane wejściowe powinny spowodować wygenerowanie nieprawidłowego 400
żądania z odpowiednim komunikatem o błędzie. Ograniczenia trasy służą do uściślania podobnych tras, a nie sprawdzania poprawności danych wejściowych dla określonej trasy.
W poniższej tabeli przedstawiono przykładowe ograniczenia tras i ich oczekiwane zachowanie:
ograniczenie | Przykład | Przykładowe dopasowania | Uwagi |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Pasuje do dowolnej liczby całkowitej |
bool |
{active:bool} |
true , FALSE |
Dopasuj lub true false . Bez uwzględniania wielkości liter |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Pasuje do prawidłowej DateTime wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Pasuje do prawidłowej decimal wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej double wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej float wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Pasuje do prawidłowej Guid wartości |
long |
{ticks:long} |
123456789 , -123456789 |
Pasuje do prawidłowej long wartości |
minlength(value) |
{username:minlength(4)} |
Rick |
Ciąg musi mieć co najmniej 4 znaki |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Ciąg nie może zawierać więcej niż 8 znaków |
length(length) |
{filename:length(12)} |
somefile.txt |
Ciąg musi mieć długość dokładnie 12 znaków |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Ciąg musi mieć co najmniej 8 znaków i nie więcej niż 16 znaków |
min(value) |
{age:min(18)} |
19 |
Wartość całkowita musi być co najmniej 18 |
max(value) |
{age:max(120)} |
91 |
Wartość całkowita nie może być większa niż 120 |
range(min,max) |
{age:range(18,120)} |
91 |
Wartość całkowita musi być co najmniej 18, ale nie większa niż 120 |
alpha |
{name:alpha} |
Rick |
Ciąg musi składać się z co najmniej jednego znaku a -z alfabetycznego i bez uwzględniania wielkości liter. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Ciąg musi być zgodny z wyrażeniem regularnym. Zobacz porady dotyczące definiowania wyrażenia regularnego. |
required |
{name:required} |
Rick |
Służy do wymuszania, że wartość nieparametrowa jest obecna podczas generowania adresu URL |
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wiele ograniczeń rozdzielonych dwukropkami można zastosować do jednego parametru. Na przykład następujące ograniczenie ogranicza parametr do wartości całkowitej 1 lub większej:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Ostrzeżenie
Ograniczenia trasy, które weryfikują adres URL i są konwertowane na typ CLR, zawsze używają niezmiennej kultury. Na przykład konwersja na typ int
CLR lub DateTime
. Te ograniczenia zakładają, że adres URL nie jest lokalizowalny. Ograniczenia trasy dostarczone przez platformę nie modyfikują wartości przechowywanych w wartościach tras. Wszystkie wartości tras analizowane z adresu URL są przechowywane jako ciągi. Na przykład float
ograniczenie próbuje przekonwertować wartość trasy na zmiennoprzecinkowy, ale przekonwertowana wartość jest używana tylko do sprawdzania, czy można ją przekonwertować na zmiennoprzecinkowy.
Wyrażenia regularne w ograniczeniach
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wyrażenia regularne można określić jako ograniczenia wbudowane przy użyciu regex(...)
ograniczenia trasy. Metody w MapControllerRoute rodzinie akceptują również literał obiektów ograniczeń. Jeśli ten formularz jest używany, wartości ciągów są interpretowane jako wyrażenia regularne.
Poniższy kod używa wbudowanego ograniczenia wyrażeń regularnych:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
Poniższy kod używa literału obiektu do określenia ograniczenia wyrażenia regularnego:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
Struktura ASP.NET Core dodaje RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
do konstruktora wyrażeń regularnych. Zobacz RegexOptions opis tych elementów członkowskich.
Wyrażenia regularne używają ograniczników i tokenów podobnych do tych używanych przez routing i język C#. Tokeny wyrażeń regularnych muszą zostać uniknięci. Aby użyć wyrażenia ^\d{3}-\d{2}-\d{4}$
regularnego w ograniczeniu wbudowanym, użyj jednego z następujących elementów:
- Zastąp
\
znaki podane w ciągu jako\\
znaki w pliku źródłowym języka C#, aby uniknąć\
znaku ucieczki ciągu. - Literały ciągu dosłowne.
Aby uniknąć znaków {
ogranicznika parametru routingu , , [
}
, ]
, podwaja znaki w wyrażeniu, na przykład {{
, , }}
, [[
, ]]
. W poniższej tabeli przedstawiono wyrażenie regularne i jego wersję unikniętą:
Regular expression | Wyrażenie regularne o wartości ucieczki |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Wyrażenia regularne używane w routingu często zaczynają się od ^
znaku i pasują do pozycji początkowej ciągu. Wyrażenia często kończą się znakiem $
i pasują do końca ciągu. Znaki ^
i $
zapewniają, że wyrażenie regularne jest zgodne z całą wartością parametru trasy. ^
Bez znaków i $
wyrażenie regularne pasuje do dowolnego podciągu w ciągu, co jest często niepożądane. W poniższej tabeli przedstawiono przykłady i wyjaśniono, dlaczego są one zgodne lub nie są zgodne:
Wyrażenie | String | Match | Komentarz |
---|---|---|---|
[a-z]{2} |
hello | Tak | Dopasowania podciągów |
[a-z]{2} |
123abc456 | Tak | Dopasowania podciągów |
[a-z]{2} |
mz | Tak | Dopasuj wyrażenie |
[a-z]{2} |
MZ | Tak | Nie uwzględnia wielkości liter |
^[a-z]{2}$ |
hello | Nie. | Zobacz ^ i $ powyżej |
^[a-z]{2}$ |
123abc456 | Nie. | Zobacz ^ i $ powyżej |
Aby uzyskać więcej informacji na temat składni wyrażeń regularnych, zobacz .NET Framework Regular Expressions (Wyrażenia regularne programu .NET Framework).
Aby ograniczyć parametr do znanego zestawu możliwych wartości, użyj wyrażenia regularnego. Na przykład {action:regex(^(list|get|create)$)}
pasuje action
tylko do wartości trasy do list
, get
lub create
. Jeśli zostanie przekazany do słownika ograniczeń, ciąg ^(list|get|create)$
jest równoważny. Ograniczenia przekazywane w słowniku ograniczeń, które nie są zgodne z jednym ze znanych ograniczeń, są również traktowane jako wyrażenia regularne. Ograniczenia przekazywane w szablonie, które nie pasują do jednego ze znanych ograniczeń, nie są traktowane jako wyrażenia regularne.
Ograniczenia trasy niestandardowej
Ograniczenia trasy niestandardowej można utworzyć przez zaimplementowanie interfejsu IRouteConstraint . Interfejs IRouteConstraint
zawiera Matchwartość , która zwraca true
wartość, jeśli ograniczenie jest spełnione i false
w przeciwnym razie.
Ograniczenia trasy niestandardowej są rzadko potrzebne. Przed zaimplementowaniem ograniczenia trasy niestandardowej rozważ alternatywy, takie jak powiązanie modelu.
Folder ASP.NET Core Constraints zawiera dobre przykłady tworzenia ograniczeń. Na przykład GuidRouteConstraint.
Aby użyć niestandardowego IRouteConstraint
, typ ograniczenia trasy musi być zarejestrowany w aplikacji ConstraintMap w kontenerze usługi. Jest ConstraintMap
to słownik, który mapuje klucze ograniczeń trasy na IRouteConstraint
implementacje, które weryfikują te ograniczenia. Aplikację ConstraintMap
można zaktualizować w Program.cs
ramach AddRouting wywołania lub konfigurując bezpośrednio za pomocą builder.Services.Configure<RouteOptions>
polecenia RouteOptions . Na przykład:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
Powyższe ograniczenie jest stosowane w następującym kodzie:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
Implementacja elementu NoZeroesRouteConstraint
uniemożliwia 0
korzystanie z parametru trasy:
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);
}
}
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Powyższy kod ma następujące działanie:
- Zapobiega
0
w{id}
segmencie trasy. - Pokazano, aby podać podstawowy przykład implementacji ograniczenia niestandardowego. Nie należy jej używać w aplikacji produkcyjnej.
Poniższy kod jest lepszym podejściem do zapobiegania przetwarzaniu elementu zawierającego id
0
element:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
Powyższy kod ma następujące zalety w porównaniu z podejściem NoZeroesRouteConstraint
:
- Nie wymaga on ograniczenia niestandardowego.
- Zwraca on bardziej opisowy błąd, gdy parametr trasy zawiera
0
wartość .
Transformatory parametrów
Transformatory parametrów:
- Wykonaj polecenie podczas generowania linku przy użyciu polecenia LinkGenerator.
- Zaimplementuj .Microsoft.AspNetCore.Routing.IOutboundParameterTransformer
- Są konfigurowane przy użyciu polecenia ConstraintMap.
- Weź wartość trasy parametru i przekształć go w nową wartość ciągu.
- Wynik użycia przekształconej wartości w wygenerowanym linku.
Na przykład funkcja przekształcania parametrów niestandardowych slugify
we wzorcu blog\{article:slugify}
trasy z poleceniem Url.Action(new { article = "MyTestArticle" })
generuje blog\my-test-article
wartość .
Rozważmy następującą IOutboundParameterTransformer
implementację:
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();
}
}
Aby użyć transformatora parametrów we wzorcu trasy, skonfiguruj go przy użyciu ConstraintMap polecenia w pliku Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
Struktura ASP.NET Core używa funkcji przekształcania parametrów w celu przekształcenia identyfikatora URI, w którym punkt końcowy jest rozpoznawany. Na przykład transformatory parametrów przekształcają wartości tras używane do dopasowania area
wartości , , action
controller
i page
:
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
W przypadku poprzedniego szablonu trasy akcja SubscriptionManagementController.GetAll
jest zgodna z identyfikatorem URI /subscription-management/get-all
. Funkcja przekształcania parametrów nie zmienia wartości tras używanych do generowania łącza. Na przykład Url.Action("GetAll", "SubscriptionManagement")
dane wyjściowe ./subscription-management/get-all
ASP.NET Core udostępnia konwencje interfejsu API dotyczące używania transformatorów parametrów z wygenerowanymi trasami:
- Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention Konwencja MVC stosuje określony parametr transformer do wszystkich tras atrybutów w aplikacji. Transformator parametru przekształca tokeny tras atrybutu podczas ich wymiany. Aby uzyskać więcej informacji, zobacz Używanie funkcji przekształcania parametrów do dostosowywania zamiany tokenu.
- Razor Strony używają konwencji interfejsu PageRouteTransformerConvention API. Ta konwencja stosuje określony transformator parametru do wszystkich automatycznie odnalezionych Razor stron. Funkcja przekształcania parametrów przekształca segmenty Razor nazw folderów i plików tras stron. Aby uzyskać więcej informacji, zobacz Dostosowywanie tras stron przy użyciu funkcji przekształcania parametrów.
Dokumentacja generowania adresów URL
Ta sekcja zawiera odwołanie do algorytmu zaimplementowanego przez generowanie adresów URL. W praktyce większość złożonych przykładów generowania adresów URL używa kontrolerów lub Razor stron. Aby uzyskać dodatkowe informacje, zobacz routing w kontrolerach .
Proces generowania adresu URL rozpoczyna się od wywołania metody LinkGenerator.GetPathByAddress lub podobnej metody. Metoda jest dostarczana z adresem, zestawem wartości tras i opcjonalnie informacjami o bieżącym żądaniu z .HttpContext
Pierwszym krokiem jest użycie adresu w celu rozpoznania zestawu punktów końcowych kandydata przy użyciu elementu zgodnego z typem IEndpointAddressScheme<TAddress> adresu.
Po znalezieniu zestawu kandydatów przez schemat adresów punkty końcowe są uporządkowane i przetwarzane iteracyjnie, dopóki operacja generowania adresu URL nie powiedzie się. Generowanie adresu URL nie sprawdza niejednoznaczności. Zwracany pierwszy wynik jest wynikiem końcowym.
Rozwiązywanie problemów z generowaniem adresu URL przy użyciu rejestrowania
Pierwszym krokiem generowania adresu URL rozwiązywania problemów jest ustawienie poziomu rejestrowania Microsoft.AspNetCore.Routing
na TRACE
. LinkGenerator
rejestruje wiele szczegółów dotyczących jego przetwarzania, które mogą być przydatne do rozwiązywania problemów.
Aby uzyskać szczegółowe informacje na temat generowania adresów URL, zobacz Dokumentację generowania adresów URL.
Adresy
Adresy to koncepcja generowania adresów URL używana do powiązania wywołania z generatorem linków z zestawem kandydatów punktów końcowych.
Adresy to rozszerzalna koncepcja, która domyślnie zawiera dwie implementacje:
- Użyj nazwy punktu końcowego (
string
) jako adresu:- Zapewnia podobne funkcje do nazwy trasy MVC.
- IEndpointNameMetadata Używa typu metadanych.
- Usuwa podany ciąg względem metadanych wszystkich zarejestrowanych punktów końcowych.
- Zgłasza wyjątek podczas uruchamiania, jeśli wiele punktów końcowych używa tej samej nazwy.
- Zalecane do użytku ogólnego przeznaczenia poza kontrolerami i Razor stronami.
- Użyj wartości tras (RouteValuesAddress) jako adresu:
- Zapewnia podobne funkcje do kontrolerów i Razor starszej generacji adresów URL stron.
- Bardzo złożone rozszerzanie i debugowanie.
- Zapewnia implementację używaną przez
IUrlHelper
pomocników tagów, pomocników HTML, wyniki akcji itp.
Rolą schematu adresów jest skojarzenie adresu i pasujących punktów końcowych według dowolnych kryteriów:
- Schemat nazw punktów końcowych wykonuje wyszukiwanie w słowniku podstawowym.
- Schemat wartości tras ma złożony najlepszy podzbiór ustawionego algorytmu.
Wartości otoczenia i jawne wartości
Z bieżącego żądania routing uzyskuje dostęp do wartości tras bieżącego żądania HttpContext.Request.RouteValues
. Wartości skojarzone z bieżącym żądaniem są określane jako wartości otoczenia. W celu jasności dokumentacja odnosi się do wartości tras przekazywanych do metod jako jawnych wartości.
W poniższym przykładzie przedstawiono wartości otoczenia i jawne wartości. Zapewnia ona wartości otoczenia z bieżącego żądania i jawnych wartości:
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);
}
// ...
Powyższy kod ma następujące działanie:
- Zwraca
/Widget/Index/17
- Pobiera LinkGenerator przez DI.
Poniższy kod zawiera tylko jawne wartości i nie ma wartości otoczenia:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
Poprzednia metoda zwraca /Home/Subscribe/17
Poniższy kod w zwracaniu WidgetController
wartości /Widget/Subscribe/17
:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
Poniższy kod udostępnia kontroler z wartości otoczenia w bieżącym żądaniu i jawnych wartościach:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
Powyższy kod:
/Gadget/Edit/17
jest zwracany.- Url pobiera element IUrlHelper.
- Action generuje adres URL ze ścieżką bezwzględną dla metody akcji. Adres URL zawiera określoną
action
nazwę iroute
wartości.
Poniższy kod zawiera wartości otoczenia z bieżącego żądania i jawnych wartości:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
Powyższy kod ustawia wartość url
, /Edit/17
gdy strona edycji Razor zawiera następującą dyrektywę strony:
@page "{id:int}"
Jeśli strona Edycja nie zawiera szablonu "{id:int}"
trasy, url
to /Edit?id=17
.
Zachowanie wzorca MVC IUrlHelper dodaje warstwę złożoności oprócz reguł opisanych tutaj:
IUrlHelper
zawsze udostępnia wartości tras z bieżącego żądania jako wartości otoczenia.- IUrlHelper.Action zawsze kopiuje bieżące
action
wartości icontroller
trasy jako wartości jawne, chyba że zostanie zastąpione przez dewelopera. - IUrlHelper.Page zawsze kopiuje bieżącą
page
wartość trasy jako jawną wartość, chyba że zostanie zastąpiona. IUrlHelper.Page
zawsze zastępuje bieżącąhandler
wartość trasy wartościąnull
jawną, chyba że zostanie zastąpiona.
Użytkownicy są często zaskoczeni szczegółami zachowania wartości otoczenia, ponieważ MVC nie wydaje się przestrzegać własnych reguł. Ze względów historycznych i zgodności niektóre wartości tras, takie jak action
, controller
, page
i handler
mają własne zachowanie specjalne.
Równoważna funkcjonalność zapewniana przez LinkGenerator.GetPathByAction
program i LinkGenerator.GetPathByPage
duplikuje te anomalie w IUrlHelper
celu zapewnienia zgodności.
Proces generowania adresów URL
Po znalezieniu zestawu punktów końcowych kandydata algorytm generowania adresów URL:
- Przetwarza iteracyjne punkty końcowe.
- Zwraca pierwszy pomyślny wynik.
Pierwszym krokiem w tym procesie jest unieważnienie wartości trasy. Unieważnienie wartości trasy to proces, za pomocą którego routing decyduje, które wartości tras z wartości otoczenia powinny być używane i które powinny być ignorowane. Każda wartość otoczenia jest uwzględniana i połączona z jawnymi wartościami lub ignorowana.
Najlepszym sposobem myślenia o roli wartości otoczenia jest to, że próbują zapisać deweloperów aplikacji wpisywanie, w niektórych typowych przypadkach. Tradycyjnie scenariusze, w których wartości otoczenia są przydatne, są związane z MVC:
- Podczas łączenia z inną akcją w tym samym kontrolerze nie trzeba określać nazwy kontrolera.
- Podczas łączenia z innym kontrolerem w tym samym obszarze nie trzeba określać nazwy obszaru.
- Podczas łączenia z tą samą metodą akcji wartości tras nie muszą być określone.
- Podczas łączenia z inną częścią aplikacji nie chcesz przenosić wartości tras, które nie mają znaczenia w tej części aplikacji.
Wywołania do LinkGenerator
lub IUrlHelper
tego zwracania null
są zwykle spowodowane przez niezrozumienie unieważnienia wartości trasy. Rozwiąż problemy z nieprawidłową wartością trasy, określając jawnie więcej wartości trasy, aby sprawdzić, czy rozwiązuje to problem.
Unieważnienie wartości trasy działa zgodnie z założeniem, że schemat adresu URL aplikacji jest hierarchiczny, z hierarchią utworzoną od lewej do prawej. Rozważmy podstawowy szablon {controller}/{action}/{id?}
trasy kontrolera, aby uzyskać intuicyjne zrozumienie sposobu działania tej metody w praktyce. Zmiana wartości unieważnia wszystkie wartości trasy, które są wyświetlane po prawej stronie. Odzwierciedla to założenie dotyczące hierarchii. Jeśli aplikacja ma wartość otoczenia dla id
, a operacja określa inną wartość dla elementu controller
:
id
nie będzie ponownie używany, ponieważ{controller}
znajduje się po lewej{id?}
stronie .
Kilka przykładów demonstrujących tę zasadę:
- Jeśli jawne wartości zawierają wartość parametru
id
, wartość otoczenia dlaid
elementu jest ignorowana. Wartości otoczenia icontroller
action
mogą być używane. - Jeśli jawne wartości zawierają wartość parametru
action
, każda wartość otoczenia dlaaction
elementu jest ignorowana. Można użyć wartościcontroller
otoczenia. Jeśli jawna wartość parametruaction
jest inna niż wartość otoczenia dlaaction
wartości ,id
wartość nie zostanie użyta. Jeśli jawna wartość parametruaction
jest taka sama jak wartość otoczenia dlaaction
,id
można użyć wartości . - Jeśli jawne wartości zawierają wartość parametru
controller
, każda wartość otoczenia dlacontroller
elementu jest ignorowana. Jeśli jawna wartość parametrucontroller
jest inna niż wartość otoczenia dlacontroller
,action
wartości iid
nie będą używane. Jeśli jawna wartość parametrucontroller
jest taka sama jak wartość otoczenia dlacontroller
,action
można użyć wartości iid
.
Ten proces jest jeszcze bardziej skomplikowany przez istnienie tras atrybutów i dedykowanych tras konwencjonalnych. Kontroler konwencjonalnych tras, takich jak {controller}/{action}/{id?}
określanie hierarchii przy użyciu parametrów trasy. W przypadku dedykowanych tras konwencjonalnych i tras atrybutów do kontrolerów i Razor stron:
- Istnieje hierarchia wartości tras.
- Nie są one wyświetlane w szablonie.
W takich przypadkach generowanie adresów URL definiuje koncepcję wymaganych wartości . Punkty końcowe utworzone przez kontrolery i Razor strony mają wymagane wartości określone, które umożliwiają działanie unieważnienia wartości trasy.
Algorytm unieważniania wartości trasy szczegółowo:
- Wymagane nazwy wartości są łączone z parametrami trasy, a następnie przetwarzane od lewej do prawej.
- Dla każdego parametru porównywana jest wartość otoczenia i jawna wartość:
- Jeśli wartość otoczenia i jawna wartość są takie same, proces będzie kontynuowany.
- Jeśli wartość otoczenia jest obecna, a jawna wartość nie jest, wartość otoczenia jest używana podczas generowania adresu URL.
- Jeśli wartość otoczenia nie jest obecna, a jawna wartość to, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
- Jeśli wartość otoczenia i jawna wartość są obecne, a dwie wartości są różne, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
W tym momencie operacja generowania adresu URL jest gotowa do oceny ograniczeń trasy. Zestaw akceptowanych wartości jest połączony z wartościami domyślnymi parametrów, które są dostarczane do ograniczeń. Jeśli wszystkie ograniczenia przechodzą, operacja będzie kontynuowana.
Następnie akceptowane wartości mogą służyć do rozszerzania szablonu trasy. Szablon trasy jest przetwarzany:
- Od lewej do prawej.
- Każdy parametr ma zastąpioną akceptowaną wartość.
- W następujących przypadkach specjalnych:
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr ma wartość domyślną, zostanie użyta wartość domyślna.
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr jest opcjonalny, przetwarzanie będzie kontynuowane.
- Jeśli dowolny parametr trasy z prawej strony brakującego opcjonalnego parametru ma wartość, operacja kończy się niepowodzeniem.
- Ciągłe parametry domyślne i parametry opcjonalne są zwinięte tam, gdzie to możliwe.
Wartości jawnie podane, które nie pasują do segmentu trasy, są dodawane do ciągu zapytania. W poniższej tabeli przedstawiono wynik użycia szablonu {controller}/{action}/{id?}
trasy .
Wartości otoczenia | Jawne wartości | Result |
---|---|---|
controller = "Home" | action = "Informacje" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "Informacje" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Problemy z unieważnieniem wartości trasy
Poniższy kod przedstawia przykład schematu generowania adresów URL, który nie jest obsługiwany przez routing:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
W poprzednim kodzie culture
parametr trasy jest używany do lokalizacji. Pragnieniem jest, culture
aby parametr zawsze był akceptowany jako wartość otoczenia. culture
Jednak parametr nie jest akceptowany jako wartość otoczenia ze względu na sposób działania wymaganych wartości:
- W szablonie
"default"
culture
trasy parametr trasy znajduje się po lewej stroniecontroller
elementu , więc zmianycontroller
nie będą unieważniaćculture
elementu . - W szablonie
"blog"
culture
trasy parametr trasy jest uznawany za znajdujący się po prawej stroniecontroller
elementu , który jest wyświetlany w wymaganych wartościach.
Analizowanie ścieżek URL za pomocą polecenia LinkParser
Klasa LinkParser dodaje obsługę analizowania ścieżki adresu URL do zestawu wartości tras. Metoda ParsePathByEndpointName przyjmuje nazwę punktu końcowego i ścieżkę adresu URL i zwraca zestaw wartości tras wyodrębnionych ze ścieżki adresu URL.
W poniższym przykładowym kontrolerze akcja GetProduct
używa szablonu api/Products/{id}
trasy i ma Name GetProduct
wartość :
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
W tej samej klasie AddRelatedProduct
kontrolera akcja oczekuje ścieżki adresu URL , pathToRelatedProduct
która może być podana jako parametr ciągu zapytania:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
W poprzednim przykładzie akcja AddRelatedProduct
wyodrębnia id
wartość trasy ze ścieżki adresu URL. Na przykład ze ścieżką /api/Products/1
relatedProductId
adresu URL wartości jest ustawiona wartość 1
. Takie podejście umożliwia klientom interfejsu API używanie ścieżek url podczas odwoływania się do zasobów bez konieczności znajomości struktury takiego adresu URL.
Konfigurowanie metadanych punktu końcowego
Poniższe linki zawierają informacje na temat konfigurowania metadanych punktu końcowego:
- Włączanie mechanizmu Cors z routingiem punktów końcowych
- Przykład IAuthorizationPolicyProvider przy użyciu atrybutu niestandardowego
[MinimumAgeAuthorize]
- Testowanie uwierzytelniania za pomocą atrybutu [Autoryzuj]
- RequireAuthorization
- Wybieranie schematu z atrybutem [Authorize]
- Stosowanie zasad przy użyciu atrybutu [Autoryzuj]
- Autoryzacja oparta na rolach w programie ASP.NET Core
Dopasowywanie hostów w trasach za pomocą elementu RequireHost
RequireHost stosuje ograniczenie do trasy, która wymaga określonego hosta. Parametr RequireHost
lub [Host] może być:
- Host:
www.domain.com
, pasujewww.domain.com
do dowolnego portu. - Host z symbolem wieloznacznymi:
*.domain.com
, pasujewww.domain.com
do ,subdomain.domain.com
lubwww.subdomain.domain.com
na dowolnym porcie. - Port:
*:5000
, pasuje do portu 5000 z dowolnym hostem. - Host i port:
www.domain.com:5000
lub*.domain.com:5000
, pasuje do hosta i portu.
Można określić wiele parametrów przy użyciu polecenia RequireHost
lub [Host]
. Ograniczenie pasuje do hostów prawidłowych dla dowolnego z parametrów. Na przykład pasuje domain.com
do [Host("domain.com", "*.domain.com")]
, www.domain.com
i subdomain.domain.com
.
Poniższy kod używa RequireHost
metody , aby wymagać określonego hosta na trasie:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
Poniższy kod używa atrybutu [Host]
na kontrolerze, aby wymagać któregokolwiek z określonych hostów:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Po zastosowaniu atrybutu [Host]
zarówno do kontrolera, jak i metody akcji:
- Używany jest atrybut akcji.
- Atrybut kontrolera jest ignorowany.
Wskazówki dotyczące wydajności routingu
Gdy aplikacja ma problemy z wydajnością, routing jest często podejrzewany jako problem. Podejrzewa się, że routing polega na tym, że struktury, takie jak kontrolery i Razor strony, zgłaszają ilość czasu spędzonego w strukturze w komunikatach rejestrowania. W przypadku znacznej różnicy między czasem zgłoszonym przez kontrolery a łącznym czasem żądania:
- Deweloperzy eliminują kod aplikacji jako źródło problemu.
- Często zakłada się, że routing jest przyczyną.
Routing jest testowany pod kątem wydajności przy użyciu tysięcy punktów końcowych. Jest mało prawdopodobne, aby typowa aplikacja napotkała problem z wydajnością, ponieważ jest zbyt duża. Najczęstszą główną przyczyną niskiej wydajności routingu jest zwykle nieprawidłowe zachowanie niestandardowego oprogramowania pośredniczącego.
W poniższym przykładzie kodu przedstawiono podstawową technikę zawężania źródła opóźnienia:
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.");
Routing czasowy:
- Przeplataj każde oprogramowanie pośredniczące kopią oprogramowania pośredniczącego chronometrażu pokazanego w poprzednim kodzie.
- Dodaj unikatowy identyfikator, aby skorelować dane chronometrażu z kodem.
Jest to podstawowy sposób zawężenia opóźnienia, gdy jest to znaczące, na przykład więcej niż 10ms
. Odejmowanie Time 2
z Time 1
raportów czasu spędzonego wewnątrz oprogramowania pośredniczącego UseRouting
.
Poniższy kod używa bardziej kompaktowego podejścia do poprzedniego kodu chronometrażu:
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.");
Potencjalnie kosztowne funkcje routingu
Poniższa lista zawiera wgląd w funkcje routingu, które są stosunkowo kosztowne w porównaniu z podstawowymi szablonami tras:
- Wyrażenia regularne: istnieje możliwość zapisywania wyrażeń regularnych, które są złożone lub mają długi czas z niewielką ilością danych wejściowych.
- Segmenty złożone (
{x}-{y}-{z}
):- Są znacznie droższe niż analizowanie zwykłego segmentu ścieżki adresu URL.
- Powoduje przydzielenie wielu podciągów.
- Dostęp do danych synchronicznych: wiele złożonych aplikacji ma dostęp do bazy danych w ramach routingu. Użyj punktów rozszerzalności, takich jak MatcherPolicy i EndpointSelectorContext, które są asynchroniczne.
Wskazówki dotyczące dużych tabel tras
Domyślnie ASP.NET Core używa algorytmu routingu, który wymienia pamięć na czas procesora CPU. Ma to miły wpływ, że czas dopasowywania trasy zależy tylko od długości ścieżki do dopasowania, a nie liczby tras. Jednak takie podejście może być potencjalnie problematyczne w niektórych przypadkach, gdy aplikacja ma dużą liczbę tras (w tysiącach) i istnieje duża ilość zmiennych prefiksów w trasach. Jeśli na przykład trasy mają parametry we wczesnych segmentach trasy, na przykład {parameter}/some/literal
.
Jest mało prawdopodobne, aby aplikacja napotkała sytuację, w której jest to problem, chyba że:
- W aplikacji istnieje duża liczba tras korzystających z tego wzorca.
- W aplikacji istnieje duża liczba tras.
Jak określić, czy aplikacja jest uruchomiona w dużym problemie z tabelą tras
- Istnieją dwa objawy do wyszukania:
- Aplikacja powoli zaczyna się od pierwszego żądania.
- Należy pamiętać, że jest to wymagane, ale nie wystarczające. Istnieje wiele innych problemów niezwiązanych z trasą, które mogą powodować powolne uruchamianie aplikacji. Sprawdź poniższy warunek, aby dokładnie określić, czy aplikacja działa w tej sytuacji.
- Aplikacja zużywa dużo pamięci podczas uruchamiania, a zrzut pamięci pokazuje dużą liczbę
Microsoft.AspNetCore.Routing.Matching.DfaNode
wystąpień.
- Aplikacja powoli zaczyna się od pierwszego żądania.
Jak rozwiązać ten problem
Istnieje kilka technik i optymalizacji, które można zastosować do tras, które w dużej mierze poprawią ten scenariusz:
- Zastosuj ograniczenia trasy do parametrów, na przykład
{parameter:int}
,{parameter:guid}
,{parameter:regex(\\d+)}
itp., jeśli to możliwe.- Dzięki temu algorytm routingu może wewnętrznie zoptymalizować struktury używane do dopasowywania i drastycznie zmniejszyć ilość używanej pamięci.
- W zdecydowanej większości przypadków wystarczy, aby wrócić do akceptowalnego zachowania.
- Zmień trasy, aby przenieść parametry do późniejszych segmentów w szablonie.
- Zmniejsza to liczbę możliwych "ścieżek", aby dopasować punkt końcowy na daną ścieżkę.
- Użyj trasy dynamicznej i dynamicznie wykonaj mapowanie na kontroler/stronę.
- Można to osiągnąć przy użyciu metod
MapDynamicControllerRoute
iMapDynamicPageRoute
.
- Można to osiągnąć przy użyciu metod
Wskazówki dla autorów bibliotek
Ta sekcja zawiera wskazówki dotyczące autorów bibliotek opartych na routingu. Te szczegóły mają na celu zapewnienie, że deweloperzy aplikacji mają dobre środowisko korzystania z bibliotek i struktur rozszerzających routing.
Definiowanie punktów końcowych
Aby utworzyć strukturę korzystającą z routingu do dopasowywania adresów URL, zacznij od zdefiniowania środowiska użytkownika, które opiera się na systemie UseEndpoints.
Kompilacja DO na podstawie funkcji IEndpointRouteBuilder. Dzięki temu użytkownicy mogą tworzyć struktury przy użyciu innych funkcji ASP.NET Core bez pomyłek. Każdy szablon ASP.NET Core zawiera routing. Załóżmy, że routing jest obecny i znany użytkownikom.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
NALEŻY zwrócić zapieczętowany typ betonowy z wywołania, do MapMyFramework(...)
którego implementuje IEndpointConventionBuilderelement . Większość metod struktury Map...
jest zgodne z tym wzorcem. Interfejs IEndpointConventionBuilder
:
- Umożliwia komponowania metadanych.
- Jest celem różnych metod rozszerzeń.
Deklarowanie własnego typu umożliwia dodanie do konstruktora własnych funkcji specyficznych dla platformy. Można zawinąć konstruktora zadeklarowanego przez platformę i przekazać do niego wywołania.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
ROZWAŻ napisanie własnego EndpointDataSourcepliku . EndpointDataSource
jest elementem pierwotnym niskiego poziomu do deklarowania i aktualizowania kolekcji punktów końcowych. EndpointDataSource
to zaawansowany interfejs API używany przez kontrolery i Razor strony.
Testy routingu mają podstawowy przykład nieakcyjnego źródła danych.
NIE próbuj domyślnie rejestrować konta EndpointDataSource
. Wymagaj od użytkowników zarejestrowania struktury w programie UseEndpoints. Filozofią routingu jest to, że nic nie jest dołączane domyślnie i UseEndpoints
jest to miejsce do rejestrowania punktów końcowych.
Tworzenie zintegrowanego z routingiem oprogramowania pośredniczącego
ROZWAŻ zdefiniowanie typów metadanych jako interfejsu.
DO umożliwia używanie typów metadanych jako atrybutu w klasach i metodach.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Struktury, takie jak kontrolery i Razor strony, obsługują stosowanie atrybutów metadanych do typów i metod. W przypadku deklarowania typów metadanych:
- Udostępnij je jako atrybuty.
- Większość użytkowników zna stosowanie atrybutów.
Deklarowanie typu metadanych jako interfejsu zwiększa kolejną warstwę elastyczności:
- Interfejsy są komponowalne.
- Deweloperzy mogą zadeklarować własne typy, które łączą wiele zasad.
NIE umożliwia przesłaniania metadanych, jak pokazano w poniższym przykładzie:
[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() { }
}
Najlepszym sposobem przestrzegania tych wytycznych jest unikanie definiowania metadanych znaczników:
- Nie należy po prostu szukać obecności typu metadanych.
- Zdefiniuj właściwość na metadanych i sprawdź właściwość .
Kolekcja metadanych jest uporządkowana i obsługuje zastępowanie według priorytetu. W przypadku kontrolerów metadane metody akcji są najbardziej specyficzne.
DO sprawia, że oprogramowanie pośredniczące jest przydatne z routingiem i bez tego routingu:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Jako przykład tej wskazówki należy wziąć pod uwagę UseAuthorization
oprogramowanie pośredniczące. Oprogramowanie pośredniczące autoryzacji umożliwia przekazanie zasad rezerwowych. Zasady rezerwowe, jeśli zostały określone, mają zastosowanie do obu następujących elementów:
- Punkty końcowe bez określonych zasad.
- Żądania, które nie są zgodne z punktem końcowym.
Dzięki temu oprogramowanie pośredniczące autoryzacji jest przydatne poza kontekstem routingu. Oprogramowanie pośredniczące autoryzacji może służyć do tradycyjnego programowania oprogramowania pośredniczącego.
Diagnostyka debugowania
Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw wartość Logging:LogLevel:Microsoft
Debug
. W środowisku deweloperów ustaw poziom dziennika w pliku appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Dodatkowe zasoby
Routing jest odpowiedzialny za dopasowywanie przychodzących żądań HTTP i wysyłanie tych żądań do punktów końcowych wykonywalnych aplikacji. Punkty końcowe to jednostki aplikacji kodu obsługi żądań wykonywalnych. Punkty końcowe są definiowane w aplikacji i konfigurowane podczas uruchamiania aplikacji. Proces dopasowywania punktu końcowego może wyodrębnić wartości z adresu URL żądania i podać te wartości do przetwarzania żądań. Korzystając z informacji o punkcie końcowym z aplikacji, routing jest również w stanie wygenerować adresy URL mapujące na punkty końcowe.
Aplikacje mogą konfigurować routing przy użyciu:
- Kontrolery
- Razor Pages
- SignalR
- usługi gRPC
- Oprogramowanie pośredniczące z obsługą punktu końcowego, takie jak kontrole kondycji.
- Delegaty i lambdy zarejestrowane przy użyciu routingu.
Ten dokument zawiera szczegółowe informacje o niskim poziomie routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:
- W przypadku kontrolerów zobacz Routing do akcji kontrolera w ASP.NET Core.
- Aby zapoznać Razor się z konwencjami stron, zobacz Razor Strony route and app conventions in ASP.NET Core (Konwencje tras stron i aplikacji w programie ASP.NET Core).
System routingu punktów końcowych opisany w tym dokumencie ma zastosowanie do ASP.NET Core 3.0 i nowszych. Aby uzyskać informacje na temat poprzedniego systemu routingu opartego na IRoutersystemie , wybierz wersję ASP.NET Core 2.1 przy użyciu jednej z następujących metod:
- Selektor wersji dla poprzedniej wersji.
- Wybierz routing ASP.NET Core 2.1.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Przykłady pobierania dla tego dokumentu są włączone przez określoną Startup
klasę. Aby uruchomić konkretny przykład, zmodyfikuj Program.cs
, aby wywołać żądaną Startup
klasę.
Podstawy routingu
Wszystkie szablony ASP.NET Core obejmują routing w wygenerowany kod. Routing jest rejestrowany w potoku oprogramowania pośredniczącego w programie Startup.Configure
.
Poniższy kod przedstawia podstawowy przykład routingu:
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!");
});
});
}
Routing używa pary oprogramowania pośredniczącego zarejestrowanego przez UseRouting i UseEndpoints:
UseRouting
Dodaje trasę zgodną z potokiem oprogramowania pośredniczącego. To oprogramowanie pośredniczące analizuje zestaw punktów końcowych zdefiniowanych w aplikacji i wybiera najlepsze dopasowanie na podstawie żądania.UseEndpoints
Dodaje wykonywanie punktu końcowego do potoku oprogramowania pośredniczącego. Uruchamia delegata skojarzonego z wybranym punktem końcowym.
Powyższy przykład zawiera pojedynczą trasę do punktu końcowego kodu przy użyciu metody MapGet :
- Po wysłaniu żądania HTTP
GET
do głównego adresu URL/
:- Wyświetlony delegat żądania wykonuje.
Hello World!
jest zapisywany w odpowiedzi HTTP. Domyślnie główny adres URL/
tohttps://localhost:5001/
.
- Jeśli metoda żądania nie
GET
jest lub główny adres URL nie/
jest , nie jest zgodna trasa i zwracany jest http 404.
Punkt końcowy
Metoda MapGet
służy do definiowania punktu końcowego. Punkt końcowy to coś, co może być następujące:
- Wybrane przez dopasowanie adresu URL i metody HTTP.
- Wykonywane przez uruchomienie delegata.
Punkty końcowe, które mogą być dopasowane i wykonywane przez aplikację, są konfigurowane w programie UseEndpoints
. Na przykład metody MapGet, MapPosti podobne łączą delegatów żądań z systemem routingu. Dodatkowe metody mogą służyć do łączenia funkcji platformy ASP.NET Core z systemem routingu:
- MapRazorPages for Razor Pages
- MapControllers dla kontrolerów
- MapHub THub<> forSignalR
- MapGrpcService TService<> dla gRPC
W poniższym przykładzie przedstawiono routing z bardziej zaawansowanym szablonem trasy:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello/{name:alpha}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
});
Ciąg /hello/{name:alpha}
jest szablonem trasy. Służy do konfigurowania sposobu dopasowania punktu końcowego. W takim przypadku szablon jest zgodny z następującymi elementami:
- Adres URL podobny do
/hello/Ryan
- Każda ścieżka adresu URL rozpoczynająca się
/hello/
od sekwencji znaków alfabetycznych.:alpha
stosuje ograniczenie trasy, które pasuje tylko do znaków alfabetycznych. Ograniczenia tras zostały wyjaśnione w dalszej części tego dokumentu.
Drugi segment ścieżki adresu URL: {name:alpha}
- Jest powiązany z parametrem
name
. - Jest przechwytywany i przechowywany w pliku HttpRequest.RouteValues.
System routingu punktów końcowych opisany w tym dokumencie jest nowy w wersji ASP.NET Core 3.0. Jednak wszystkie wersje ASP.NET Core obsługują ten sam zestaw funkcji szablonu trasy i ograniczeń trasy.
W poniższym przykładzie przedstawiono routing z kontrolą kondycji i autoryzacją:
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!");
});
});
}
Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.
W poprzednim przykładzie pokazano, jak:
- Oprogramowanie pośredniczące autoryzacji może być używane z routingiem.
- Punkty końcowe mogą służyć do konfigurowania zachowania autoryzacji.
Wywołanie MapHealthChecks dodaje punkt końcowy kontroli kondycji. Łączenie RequireAuthorization łańcucha do tego wywołania powoduje dołączenie zasad autoryzacji do punktu końcowego.
Wywoływanie UseAuthentication i UseAuthorization dodawanie oprogramowania pośredniczącego uwierzytelniania i autoryzacji. Te oprogramowanie pośredniczące są umieszczane między elementami UseRouting i UseEndpoints
, dzięki czemu mogą:
- Zobacz, który punkt końcowy został wybrany przez
UseRouting
element . - Przed wysłaniem do punktu końcowego zastosuj zasady UseEndpoints autoryzacji.
Metadane punktu końcowego
W poprzednim przykładzie istnieją dwa punkty końcowe, ale tylko punkt końcowy kontroli kondycji ma dołączone zasady autoryzacji. Jeśli żądanie pasuje do punktu końcowego sprawdzania kondycji, /healthz
zostanie wykonane sprawdzanie autoryzacji. Pokazuje to, że punkty końcowe mogą mieć dołączone dodatkowe dane. Te dodatkowe dane są nazywane metadanymi punktu końcowego:
- Metadane mogą być przetwarzane przez oprogramowanie pośredniczące obsługujące routing.
- Metadane mogą być dowolnego typu platformy .NET.
Pojęcia dotyczące routingu
System routingu opiera się na potoku oprogramowania pośredniczącego, dodając zaawansowaną koncepcję punktu końcowego . Punkty końcowe reprezentują jednostki funkcjonalności aplikacji, które różnią się od siebie pod względem routingu, autoryzacji i dowolnej liczby systemów ASP.NET Core.
definicja punktu końcowego platformy ASP.NET Core
Punkt końcowy platformy ASP.NET Core to:
- Plik wykonywalny: ma element RequestDelegate.
- Rozszerzalne: ma kolekcję metadanych .
- Można wybrać: opcjonalnie zawiera informacje o routingu.
- Wyliczenie: kolekcja punktów końcowych może być wyświetlana przez pobranie elementu EndpointDataSource z di.
Poniższy kod pokazuje, jak pobrać i sprawdzić punkt końcowy pasujący do bieżącego żądania:
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!");
});
});
}
Punkt końcowy, jeśli został wybrany, można pobrać z pliku HttpContext
. Jego właściwości można sprawdzić. Obiekty punktu końcowego są niezmienne i nie można ich modyfikować po utworzeniu. Najczęstszym typem RouteEndpointpunktu końcowego jest . RouteEndpoint
zawiera informacje, które umożliwiają wybranie go przez system routingu.
W poprzednim kodzie aplikacja. Użyj konfiguracji oprogramowania pośredniczącego wbudowanego.
Poniższy kod pokazuje, że w zależności od tego, gdzie app.Use
jest wywoływana w potoku, punkt końcowy może nie być:
// 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);
});
W powyższym przykładzie dodano Console.WriteLine
instrukcje, które wyświetlają, czy wybrano punkt końcowy. Aby uzyskać czytelność, przykład przypisuje nazwę wyświetlaną do podanego /
punktu końcowego.
Uruchomienie tego kodu z adresem URL wyświetlania /
:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Uruchomienie tego kodu przy użyciu dowolnego innego adresu URL powoduje wyświetlenie:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Te dane wyjściowe pokazują, że:
- Punkt końcowy ma zawsze wartość null przed
UseRouting
wywołaniem. - Jeśli zostanie znalezione dopasowanie, punkt końcowy nie ma wartości null między
UseRouting
i UseEndpoints. - Oprogramowanie
UseEndpoints
pośredniczące jest terminalem po znalezieniu dopasowania. Oprogramowanie pośredniczące terminalu jest zdefiniowane w dalszej części tego dokumentu. - Oprogramowanie pośredniczące po
UseEndpoints
wykonaniu tylko wtedy, gdy nie zostanie znalezione dopasowanie.
Oprogramowanie UseRouting
pośredniczące używa metody SetEndpoint , aby dołączyć punkt końcowy do bieżącego kontekstu. Można zastąpić UseRouting
oprogramowanie pośredniczące logiką niestandardową i nadal korzystać z zalet korzystania z punktów końcowych. Punkty końcowe są typami pierwotnymi niskiego poziomu, takimi jak oprogramowanie pośredniczące, i nie są powiązane z implementacją routingu. Większość aplikacji nie musi zastępować UseRouting
logiką niestandardową.
Oprogramowanie UseEndpoints
pośredniczące jest przeznaczone do użycia w parze z oprogramowaniem pośredniczącym UseRouting
. Podstawowa logika do wykonania punktu końcowego nie jest skomplikowana. Użyj GetEndpoint polecenia , aby pobrać punkt końcowy, a następnie wywołać jego RequestDelegate właściwość.
Poniższy kod pokazuje, jak oprogramowanie pośredniczące może wpływać na routing lub reagować na nie:
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; }
}
W poprzednim przykładzie przedstawiono dwie ważne pojęcia:
- Oprogramowanie pośredniczące może działać przed
UseRouting
zmodyfikowaniem danych, na których działa routing.- Zwykle oprogramowanie pośredniczące, które pojawia się przed zmodyfikowaniem routingu, modyfikuje niektóre właściwości żądania, takie jak UseRewriter, UseHttpMethodOverridelub UsePathBase.
- Oprogramowanie pośredniczące może działać między elementami
UseRouting
i UseEndpoints w celu przetworzenia wyników routingu przed wykonaniem punktu końcowego.- Oprogramowanie pośredniczące uruchamiane między elementami
UseRouting
iUseEndpoints
:- Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
- Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami
UseAuthorization
iUseCors
.
- Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.
- Oprogramowanie pośredniczące uruchamiane między elementami
Powyższy kod przedstawia przykład niestandardowego oprogramowania pośredniczącego obsługującego zasady poszczególnych punktów końcowych. Oprogramowanie pośredniczące zapisuje dziennik inspekcji dostępu do poufnych danych w konsoli. Oprogramowanie pośredniczące można skonfigurować do inspekcji punktu końcowego za AuditPolicyAttribute
pomocą metadanych. W tym przykładzie pokazano wzorzec zgody, w którym są poddawane inspekcji tylko punkty końcowe oznaczone jako poufne. Tę logikę można zdefiniować w odwrotnej kolejności, przeprowadzając inspekcję wszystkich elementów, które nie są oznaczone jako bezpieczne, na przykład. System metadanych punktu końcowego jest elastyczny. Ta logika może być zaprojektowana w dowolny sposób odpowiednio do przypadku użycia.
Powyższy przykładowy kod ma na celu zademonstrowanie podstawowych pojęć dotyczących punktów końcowych. Przykład nie jest przeznaczony do użytku produkcyjnego. Bardziej kompletna wersja oprogramowania pośredniczącego dziennika inspekcji:
- Zaloguj się do pliku lub bazy danych.
- Dołącz szczegóły, takie jak użytkownik, adres IP, nazwa poufnego punktu końcowego i inne.
Metadane AuditPolicyAttribute
zasad inspekcji są definiowane jako Attribute
łatwiejsze w użyciu z platformami opartymi na klasach, takimi jak kontrolery i SignalR. W przypadku używania trasy do kodu:
- Metadane są dołączane do interfejsu API konstruktora.
- Struktury oparte na klasach obejmują wszystkie atrybuty odpowiedniej metody i klasy podczas tworzenia punktów końcowych.
Najlepszym rozwiązaniem dla typów metadanych jest zdefiniowanie ich jako interfejsów lub atrybutów. Interfejsy i atrybuty umożliwiają ponowne użycie kodu. System metadanych jest elastyczny i nie nakłada żadnych ograniczeń.
Porównywanie oprogramowania pośredniczącego terminalu i routingu
Poniższy przykładowy kod kontrastuje przy użyciu oprogramowania pośredniczącego z użyciem routingu:
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!");
});
});
}
Styl oprogramowania pośredniczącego pokazany za pomocą Approach 1:
programu to oprogramowanie pośredniczące terminalu. Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ wykonuje zgodną operację:
- Zgodna operacja w poprzednim przykładzie dotyczy
Path == "/"
oprogramowania pośredniczącego iPath == "/Movie"
routingu. - Gdy dopasowanie zakończy się pomyślnie, wykonuje pewne funkcje i zwraca, zamiast wywoływania oprogramowania pośredniczącego
next
.
Jest to nazywane oprogramowaniem pośredniczącym terminalu, ponieważ kończy wyszukiwanie, wykonuje niektóre funkcje, a następnie zwraca.
Porównanie oprogramowania pośredniczącego terminalu i routingu:
- Obie metody umożliwiają zakończenie potoku przetwarzania:
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
next
- Punkty końcowe są zawsze terminalne.
- Oprogramowanie pośredniczące kończy potok, zwracając element, a nie wywołując .
- Oprogramowanie pośredniczące terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
- Punkty końcowe są wykonywane w pozycji UseEndpoints.
- Oprogramowanie pośredniczące terminalu umożliwia dowolnemu kodowi określenie, kiedy oprogramowanie pośredniczące jest zgodne:
- Niestandardowy kod dopasowania trasy może być pełny i trudny do poprawnego zapisu.
- Routing udostępnia proste rozwiązania dla typowych aplikacji. Większość aplikacji nie wymaga niestandardowego kodu dopasowania tras.
- Interfejs punktów końcowych z oprogramowaniem pośredniczącym, takim jak
UseAuthorization
iUseCors
.- Używanie oprogramowania pośredniczącego terminalu z
UseAuthorization
oprogramowaniem pośredniczącym lubUseCors
wymaga ręcznego łączenia się z systemem autoryzacji.
- Używanie oprogramowania pośredniczącego terminalu z
Punkt końcowy definiuje oba:
- Pełnomocnik do przetwarzania żądań.
- Kolekcja dowolnych metadanych. Metadane służą do implementowania zagadnień krzyżowych na podstawie zasad i konfiguracji dołączonych do każdego punktu końcowego.
Oprogramowanie pośredniczące terminalu może być skutecznym narzędziem, ale może wymagać:
- Znaczna ilość kodowania i testowania.
- Ręczna integracja z innymi systemami w celu osiągnięcia żądanego poziomu elastyczności.
Przed napisaniem oprogramowania pośredniczącego terminalu rozważ integrację z routingiem.
Istniejące oprogramowanie pośredniczące terminalu integrujące się z usługą Map lub MapWhen zwykle może zostać przekształcone w punkt końcowy obsługujący routing. MapHealthChecks demonstruje wzorzec oprogramowania routera:
- Napisz metodę rozszerzenia w pliku IEndpointRouteBuilder.
- Utwórz zagnieżdżony potok oprogramowania pośredniczącego przy użyciu polecenia CreateApplicationBuilder.
- Dołącz oprogramowanie pośredniczące do nowego potoku. W tym przypadku . UseHealthChecks
- Build potok oprogramowania pośredniczącego do pliku RequestDelegate.
- Wywołaj
Map
i podaj nowy potok oprogramowania pośredniczącego. - Zwróć obiekt konstruktora dostarczony przez
Map
metodę rozszerzenia.
Poniższy kod przedstawia użycie narzędzi MapHealthChecks:
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!");
});
});
}
W poprzednim przykładzie pokazano, dlaczego zwracanie obiektu konstruktora jest ważne. Zwracanie obiektu konstruktora umożliwia deweloperowi aplikacji konfigurowanie zasad, takich jak autoryzacja dla punktu końcowego. W tym przykładzie oprogramowanie pośredniczące kontroli kondycji nie ma bezpośredniej integracji z systemem autoryzacji.
System metadanych został utworzony w odpowiedzi na problemy napotykane przez autorów rozszerzeń przy użyciu oprogramowania pośredniczącego terminalu. Dla każdego oprogramowania pośredniczącego problematyczne jest zaimplementowanie własnej integracji z systemem autoryzacji.
Dopasowywanie adresu URL
- Jest procesem, za pomocą którego routing jest zgodny z żądaniem przychodzącym do punktu końcowego.
- Opiera się na danych w ścieżce i nagłówkach adresu URL.
- Można rozszerzyć, aby uwzględnić dowolne dane w żądaniu.
Po wykonaniu oprogramowania pośredniczącego routingu ustawia Endpoint
ona wartości i kieruje je do funkcji żądania z HttpContext bieżącego żądania:
- Wywołanie metody HttpContext.GetEndpoint pobiera punkt końcowy.
HttpRequest.RouteValues
pobiera kolekcję wartości tras.
Oprogramowanie pośredniczące jest uruchamiane po tym, jak oprogramowanie pośredniczące routingu może sprawdzić punkt końcowy i podjąć działania. Na przykład oprogramowanie pośredniczące autoryzacji może przesłuchić kolekcję metadanych punktu końcowego dla zasad autoryzacji. Po wykonaniu całego oprogramowania pośredniczącego w potoku przetwarzania żądań wywoływany jest delegat wybranego punktu końcowego.
System routingu w routingu punktów końcowych jest odpowiedzialny za wszystkie decyzje dotyczące wysyłania. Ponieważ oprogramowanie pośredniczące stosuje zasady na podstawie wybranego punktu końcowego, ważne jest, aby:
- Każda decyzja, która może mieć wpływ na wysyłanie lub stosowanie zasad zabezpieczeń, jest dokonana wewnątrz systemu routingu.
Ostrzeżenie
W przypadku zgodności z poprzednimi wersjami po wykonaniu delegata punktu końcowego kontroler lub Razor strony właściwości RouteContext.RouteData są ustawione na odpowiednie wartości na podstawie przetwarzania żądań wykonywanego do tej pory.
Typ RouteContext
zostanie oznaczony jako przestarzały w przyszłej wersji:
- Przeprowadź migrację
RouteData.Values
doHttpRequest.RouteValues
. - Przeprowadź migrację
RouteData.DataTokens
, aby pobrać dane IDataTokensMetadata z metadanych punktu końcowego.
Dopasowywanie adresów URL działa w konfigurowalnym zestawie faz. W każdej fazie dane wyjściowe są zestawem dopasowań. Zestaw dopasowań można zawęzić dalej przez następną fazę. Implementacja routingu nie gwarantuje kolejności przetwarzania pasujących punktów końcowych. Wszystkie możliwe dopasowania są przetwarzane jednocześnie. Fazy dopasowywania adresów URL są wykonywane w następującej kolejności. ASP.NET Core:
- Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
- Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem zestawu wystąpień MatcherPolicy .
- Używa elementu EndpointSelector do podjęcia ostatecznej decyzji z poprzedniej listy.
Lista punktów końcowych jest priorytetowa zgodnie z:
- RouteEndpoint.Order
- Pierwszeństwo szablonu trasy
Wszystkie pasujące punkty końcowe są przetwarzane w każdej fazie do momentu EndpointSelector osiągnięcia. Jest EndpointSelector
to ostatnia faza. Wybiera punkt końcowy o najwyższym priorytcie z dopasowań jako najlepszy. Jeśli istnieją inne dopasowania z tym samym priorytetem co najlepsze dopasowanie, zgłaszany jest niejednoznaczny wyjątek dopasowania.
Pierwszeństwo trasy jest obliczane na podstawie bardziej szczegółowego szablonu trasy o wyższym priorytecie. Rozważmy na przykład szablony /hello
i /{message}
:
- Oba są zgodne ze ścieżką
/hello
adresu URL . /hello
jest bardziej szczegółowy i dlatego wyższy priorytet.
Ogólnie rzecz biorąc, pierwszeństwo trasy dobrze nadaje się do wyboru najlepszego dopasowania do rodzajów schematów adresów URL używanych w praktyce. Używaj Order tylko wtedy, gdy jest to konieczne, aby uniknąć niejednoznaczności.
Ze względu na rodzaj rozszerzalności zapewnianej przez routing nie jest możliwe, aby system routingu obliczał przed upływem czasu niejednoznaczne trasy. Rozważmy przykład, taki jak szablony /{message:alpha}
tras i /{message:int}
:
- Ograniczenie
alpha
pasuje tylko do znaków alfabetycznych. - Ograniczenie
int
pasuje tylko do liczb. - Te szablony mają taki sam pierwszeństwo trasy, ale nie ma jednego adresu URL, który oba te szablony są zgodne.
- Jeśli system routingu zgłosił błąd niejednoznaczności podczas uruchamiania, zablokuje to prawidłowy przypadek użycia.
Ostrzeżenie
Kolejność operacji wewnątrz UseEndpoints nie ma wpływu na zachowanie routingu z jednym wyjątkiem. MapControllerRoute i MapAreaRoute automatycznie przypisz wartość zamówienia do swoich punktów końcowych na podstawie kolejności, w której są wywoływane. Symuluje to długotrwałe zachowanie kontrolerów bez systemu routingu zapewniające takie same gwarancje jak starsze implementacje routingu.
W starszej implementacji routingu można zaimplementować rozszerzalność routingu, która ma zależność od kolejności przetwarzania tras. Routing punktów końcowych w programie ASP.NET Core 3.0 lub nowszym:
- Nie ma pojęcia tras.
- Nie zapewnia gwarancji zamawiania. Wszystkie punkty końcowe są przetwarzane jednocześnie.
Pierwszeństwo szablonu trasy i kolejność wyboru punktu końcowego
Pierwszeństwo szablonu trasy to system, który przypisuje każdemu szablonowi trasy wartość na podstawie jego specyfiki. Pierwszeństwo szablonu trasy:
- Pozwala uniknąć konieczności dostosowywania kolejności punktów końcowych w typowych przypadkach.
- Próby dopasowania do zdrowych oczekiwań dotyczących zachowania routingu.
Rozważmy na przykład szablony /Products/List
i /Products/{id}
. Uzasadnione byłoby założenie, że /Products/List
jest to lepsze dopasowanie niż /Products/{id}
dla ścieżki /Products/List
adresu URL . Działa to, ponieważ segment /List
literału jest uznawany za lepszy priorytet niż segment /{id}
parametrów .
Szczegółowe informacje o sposobie działania pierwszeństwa są powiązane z definiowaniem szablonów tras:
- Szablony z większą większa część segmentów są uważane za bardziej szczegółowe.
- Segment z tekstem literału jest uznawany za bardziej szczegółowy niż segment parametrów.
- Segment parametrów z ograniczeniem jest uważany za bardziej szczegółowy niż jeden bez.
- Segment złożony jest uznawany za specyficzny jako segment parametrów z ograniczeniem.
- Parametry catch-all są najmniej specyficzne. Zobacz catch-all w dokumentacji szablonu trasy, aby uzyskać ważne informacje na temat tras catch-all.
Zobacz kod źródłowy w witrynie GitHub , aby uzyskać odwołanie do dokładnych wartości.
Pojęcia dotyczące generowania adresów URL
Generowanie adresu URL:
- Jest procesem, za pomocą którego routing może utworzyć ścieżkę adresu URL na podstawie zestawu wartości trasy.
- Umożliwia logiczne rozdzielenie punktów końcowych i adresów URL, które do nich uzyskują dostęp.
Routing punktów końcowych obejmuje LinkGenerator interfejs API. LinkGenerator
jest pojedynczą usługą dostępną z di. Interfejs LinkGenerator
API może być używany poza kontekstem wykonywania żądania. Mvc.IUrlHelper i scenariusze, które opierają się na IUrlHelperelementach , takich jak Pomocnicy tagów, Pomocnicy HTML i Wyniki akcji, używają interfejsu LinkGenerator
API wewnętrznie, aby zapewnić możliwości generowania linków.
Generator linków jest wspierany przez koncepcję schematów adresów i adresów. Schemat adresów to sposób określania punktów końcowych, które powinny być brane pod uwagę podczas generowania linków. Na przykład scenariusze nazw tras i wartości tras, które wielu użytkowników zna z kontrolerów i Razor stron, są implementowane jako schemat adresów.
Generator linków może łączyć się z kontrolerami i Razor stronami za pomocą następujących metod rozszerzeń:
Przeciążenia tych metod akceptują argumenty, które obejmują HttpContext
element . Te metody są funkcjonalnie równoważne url.Action i Url.Page, ale oferują dodatkową elastyczność i opcje.
Metody GetPath*
są najbardziej podobne do Url.Action
metod i Url.Page
, w których generują identyfikator URI zawierający ścieżkę bezwzględną. Metody GetUri*
zawsze generują bezwzględny identyfikator URI zawierający schemat i host. Metody, które akceptują HttpContext
generowanie identyfikatora URI w kontekście wykonywania żądania. Wartości trasy otoczenia , ścieżka podstawowa adresu URL, schemat i host z wykonywanego żądania są używane, chyba że zostaną zastąpione.
LinkGenerator jest wywoływana przy użyciu adresu. Generowanie identyfikatora URI odbywa się w dwóch krokach:
- Adres jest powiązany z listą punktów końcowych pasujących do adresu.
- Każdy punkt końcowy RoutePattern jest oceniany do momentu znalezienia wzorca trasy zgodnego z podanymi wartościami. Wynikowe dane wyjściowe są łączone z innymi częściami identyfikatora URI dostarczonymi do generatora linków i zwracanymi.
Metody udostępniane przez LinkGenerator obsługę standardowych możliwości generowania linków dla dowolnego typu adresu. Najwygodniejszym sposobem korzystania z generatora linków jest użycie metod rozszerzeń, które wykonują operacje dla określonego typu adresu:
Extension, metoda | opis |
---|---|
GetPathByAddress | Generuje identyfikator URI ze ścieżką bezwzględną na podstawie podanych wartości. |
GetUriByAddress | Generuje bezwzględny identyfikator URI na podstawie podanych wartości. |
Ostrzeżenie
Zwróć uwagę na następujące konsekwencje wywoływania LinkGenerator metod:
Użyj
GetUri*
metod rozszerzeń z ostrożnością w konfiguracji aplikacji, która nie weryfikujeHost
nagłówka żądań przychodzących.Host
Jeśli nagłówek żądań przychodzących nie jest weryfikowany, niezaufane dane wejściowe żądania mogą być wysyłane z powrotem do klienta w identyfikatorach URI w widoku lub na stronie. Zalecamy skonfigurowanie serwera przez wszystkie aplikacje produkcyjne w celu zweryfikowania nagłówkaHost
pod kątem znanych prawidłowych wartości.Należy używać LinkGenerator z ostrożnością w oprogramowania pośredniczącego w połączeniu z
Map
lubMapWhen
.Map*
zmienia ścieżkę podstawową wykonywania żądania, co wpływa na dane wyjściowe generowania linków. LinkGenerator Wszystkie interfejsy API umożliwiają określenie ścieżki podstawowej. Określ pustą ścieżkę podstawową, aby cofnąć wpływ na generowanie linkówMap*
.
Przykład oprogramowania pośredniczącego
W poniższym przykładzie oprogramowanie pośredniczące używa interfejsu LinkGenerator API do utworzenia linku do metody akcji zawierającej listę produktów. Użycie generatora linków przez wstrzyknięcie go do klasy i wywołanie GenerateLink
jest dostępne dla dowolnej klasy w aplikacji:
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.");
}
}
Dokumentacja szablonu trasy
Tokeny w ramach {}
definicji parametrów trasy, które są powiązane, jeśli trasa jest zgodna. W segmencie trasy można zdefiniować więcej niż jeden parametr trasy, ale parametry trasy muszą być rozdzielone wartością literału. Na przykład nie jest prawidłową trasą, {controller=Home}{action=Index}
ponieważ nie ma wartości literału między {controller}
i {action}
. Parametry trasy muszą mieć nazwę i mogą mieć określone dodatkowe atrybuty.
Tekst literału inny niż parametry trasy (na przykład {id}
) i separator /
ścieżki musi być zgodny z tekstem w adresie URL. Dopasowywanie tekstu jest bez uwzględniania wielkości liter i oparte na dekodowanej reprezentacji ścieżki adresu URL. Aby dopasować ogranicznik {
parametru trasy literału lub }
, należy uruchomić ogranicznik, powtarzając znak. Na przykład {{
lub }}
.
Gwiazdka *
lub podwójna gwiazdka **
:
- Może służyć jako prefiks do parametru trasy w celu powiązania z rest identyfikatorem URI.
- Są nazywane parametrami catch-all . Na przykład :
blog/{**slug}
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
/blog
i ma dowolną wartość po niej. - Poniższa wartość
/blog
jest przypisywana do wartości trasy slug .
- Pasuje do dowolnego identyfikatora URI rozpoczynającego się od
Ostrzeżenie
Parametr catch-all może być niepoprawnie zgodny z trasami z powodu błędu w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:
- Trasa typu catch-all, na przykład
{**slug}"
- Trasa catch-all nie pasuje do żądań, które powinna być zgodna.
- Usunięcie innych tras sprawia, że trasa catch-all zacznie działać.
Zobacz Usterki usługi GitHub 18677 i 16579 , na przykład przypadki, w których wystąpiła ta usterka.
Poprawka zgody na tę usterkę jest zawarta w zestawie .NET Core 3.1.301 SDK i nowszych wersjach. Poniższy kod ustawia przełącznik wewnętrzny, który naprawia tę usterkę:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Parametry catch-all mogą być również zgodne z pustym ciągiem.
Parametr catch-all ucieczki odpowiednich znaków, gdy trasa jest używana do generowania adresu URL, w tym znaków separatora /
ścieżki. Na przykład trasa foo/{*path}
z wartościami { path = "my/path" }
tras generuje foo/my%2Fpath
wartość . Zwróć uwagę na unikniętą ukośnik. Aby użyć znaków separatora ścieżki dwukierunkowej, użyj prefiksu parametru **
trasy. Trasa foo/{**path}
z elementem { path = "my/path" }
generuje foo/my/path
wartość .
Wzorce adresów URL, które próbują przechwycić nazwę pliku z opcjonalnym rozszerzeniem pliku, mają dodatkowe uwagi. Rozważmy na przykład szablon files/{filename}.{ext?}
. Gdy wartości obu filename
i ext
istnieją, oba wartości są wypełniane. Jeśli w adresie URL istnieje tylko wartość filename
, trasa jest zgodna, ponieważ końcowy .
jest opcjonalny. Następujące adresy URL pasują do tej trasy:
/files/myFile.txt
/files/myFile
Parametry trasy mogą mieć wartości domyślne wyznaczone przez określenie wartości domyślnej po nazwie parametru oddzielonej znakiem równości (=
). Na przykład {controller=Home}
definiuje Home
jako wartość domyślną dla elementu controller
. Wartość domyślna jest używana, jeśli w adresie URL parametru nie ma żadnej wartości. Parametry trasy są opcjonalne, dołączając znak zapytania (?
) na końcu nazwy parametru. Na przykład id?
. Różnica między opcjonalnymi wartościami a domyślnymi parametrami trasy to:
- Parametr trasy z wartością domyślną zawsze generuje wartość.
- Opcjonalny parametr ma wartość tylko wtedy, gdy wartość jest dostarczana przez adres URL żądania.
Parametry trasy mogą mieć ograniczenia, które muszą być zgodne z wartością trasy powiązaną z adresem URL. Dodanie :
i ograniczenie nazwy po nazwie parametru trasy określa ograniczenie wbudowane w parametrze trasy. Jeśli ograniczenie wymaga argumentów, są one ujęte w nawiasy (...)
po nazwie ograniczenia. Można określić wiele ograniczeń wbudowanych przez dołączenie innej :
nazwy i ograniczenia.
Nazwa ograniczenia i argumenty są przekazywane do IInlineConstraintResolver usługi w celu utworzenia wystąpienia IRouteConstraint do użycia w przetwarzaniu adresów URL. Na przykład szablon blog/{article:minlength(10)}
trasy określa minlength
ograniczenie z argumentem 10
. Aby uzyskać więcej informacji na temat ograniczeń tras i listy ograniczeń udostępnianych przez platformę, zobacz sekcję Dokumentacja ograniczeń trasy.
Parametry trasy mogą również zawierać transformatory parametrów. Transformatory parametrów przekształcają wartość parametru podczas generowania łączy i pasujących akcji i stron do adresów URL. Podobnie jak ograniczenia, transformatory parametrów można dodać w tekście do parametru trasy przez dodanie :
nazwy parametru i transformatora po nazwie parametru trasy. Na przykład szablon blog/{article:slugify}
trasy określa slugify
transformator. Aby uzyskać więcej informacji na temat transformatorów parametrów, zobacz sekcję Dokumentacja przekształcania parametrów.
W poniższej tabeli przedstawiono przykładowe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI | Identyfikator URI żądania... |
---|---|---|
hello |
/hello |
Pasuje tylko do pojedynczej ścieżki /hello . |
{Page=Home} |
/ |
Dopasuj i ustawia wartość Page Home . |
{Page=Home} |
/Contact |
Dopasuj i ustawia wartość Page Contact . |
{controller}/{action}/{id?} |
/Products/List |
Mapuje na Products kontroler i List akcję. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapuje na Products kontroler i Details akcję z ustawioną wartościąid 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapuje na kontroler i Index metodęHome . Właściwość id jest ignorowana. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapuje na kontroler i Index metodęProducts . Właściwość id jest ignorowana. |
Użycie szablonu jest zwykle najprostszym podejściem do routingu. Ograniczenia i wartości domyślne można również określić poza szablonem trasy.
Złożone segmenty
Złożone segmenty są przetwarzane przez dopasowanie ograniczników literału od prawej do lewej w sposób niechłanny . Na przykład [Route("/a{b}c{d}")]
jest to złożony segment.
Złożone segmenty działają w określony sposób, aby można było z nich korzystać pomyślnie. W przykładzie w tej sekcji pokazano, dlaczego złożone segmenty działają prawidłowo tylko wtedy, gdy tekst ogranicznika nie pojawia się wewnątrz wartości parametrów. Użycie wyrażenia regularnego, a następnie ręczne wyodrębnienie wartości jest wymagane w przypadku bardziej złożonych przypadków.
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Jest to podsumowanie kroków, które routing wykonuje przy użyciu szablonu /a{b}c{d}
i ścieżki /abcd
adresu URL . Służy |
do wizualizacji sposobu działania algorytmu:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/abcd
jest wyszukiwany z prawej strony i znajduje/ab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/ab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - Brak pozostałego tekstu i brak pozostałego szablonu trasy, więc jest to dopasowanie.
Oto przykład negatywnego przypadku przy użyciu tego samego szablonu /a{b}c{d}
i ścieżki /aabcd
adresu URL . Służy |
do wizualizowania sposobu działania algorytmu. Ten przypadek nie jest zgodny, co zostało wyjaśnione przez ten sam algorytm:
- Pierwszy literał, od prawej do lewej, to
c
. Dlatego/aabcd
jest wyszukiwany z prawej strony i znajduje/aab|c|d
. - Wszystko po prawej stronie (
d
) jest teraz dopasowane do parametru{d}
trasy . - Następny literał, od prawej do lewej, to
a
. Dlatego/aab|c|d
jest wyszukiwany, zaczynając od miejsca, w którym odeszliśmy, a następniea
znajduje się/a|a|b|c|d
. - Wartość z prawej strony (
b
) jest teraz zgodna z parametrem{b}
trasy . - W tym momencie pozostaje tekst
a
, ale algorytm zabrakło szablonu trasy do przeanalizowania, więc nie jest to dopasowanie.
Ponieważ pasujący algorytm nie jest chciwy:
- Pasuje do najmniejszej ilości tekstu możliwego w każdym kroku.
- Każdy przypadek, w którym wartość ogranicznika pojawia się wewnątrz wartości parametrów, powoduje niezgodnie.
Wyrażenia regularne zapewniają znacznie większą kontrolę nad ich zachowaniem dopasowania.
Zachłanne dopasowanie, znane również jako leniwe dopasowanie, pasuje do największego możliwego ciągu. Niechłanny pasuje do najmniejszego możliwego ciągu.
Dokumentacja ograniczeń trasy
Ograniczenia trasy są wykonywane, gdy wystąpiło dopasowanie do przychodzącego adresu URL, a ścieżka adresu URL jest tokenizowana do wartości tras. Ograniczenia tras zazwyczaj sprawdzają wartość trasy skojarzoną za pośrednictwem szablonu trasy i podejmowania prawdziwej lub fałszywej decyzji o tym, czy wartość jest akceptowalna. Niektóre ograniczenia trasy używają danych poza wartością trasy, aby rozważyć, czy żądanie można kierować. Na przykład HttpMethodRouteConstraint może zaakceptować lub odrzucić żądanie na podstawie jego czasownika HTTP. Ograniczenia są używane w żądaniach routingu i generowaniu linków.
Ostrzeżenie
Nie używaj ograniczeń do walidacji danych wejściowych. Jeśli ograniczenia są używane do walidacji danych wejściowych, nieprawidłowe dane wejściowe będą skutkować odpowiedzią 404
Nie znaleziono. Nieprawidłowe dane wejściowe powinny spowodować wygenerowanie nieprawidłowego 400
żądania z odpowiednim komunikatem o błędzie. Ograniczenia trasy służą do uściślania podobnych tras, a nie sprawdzania poprawności danych wejściowych dla określonej trasy.
W poniższej tabeli przedstawiono przykładowe ograniczenia tras i ich oczekiwane zachowanie:
ograniczenie | Przykład | Przykładowe dopasowania | Uwagi |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Pasuje do dowolnej liczby całkowitej |
bool |
{active:bool} |
true , FALSE |
Dopasuj lub true false . Bez uwzględniania wielkości liter |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Pasuje do prawidłowej DateTime wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Pasuje do prawidłowej decimal wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej double wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Pasuje do prawidłowej float wartości w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Pasuje do prawidłowej Guid wartości |
long |
{ticks:long} |
123456789 , -123456789 |
Pasuje do prawidłowej long wartości |
minlength(value) |
{username:minlength(4)} |
Rick |
Ciąg musi mieć co najmniej 4 znaki |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
Ciąg nie może zawierać więcej niż 8 znaków |
length(length) |
{filename:length(12)} |
somefile.txt |
Ciąg musi mieć długość dokładnie 12 znaków |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
Ciąg musi mieć co najmniej 8 znaków i nie więcej niż 16 znaków |
min(value) |
{age:min(18)} |
19 |
Wartość całkowita musi być co najmniej 18 |
max(value) |
{age:max(120)} |
91 |
Wartość całkowita nie może być większa niż 120 |
range(min,max) |
{age:range(18,120)} |
91 |
Wartość całkowita musi być co najmniej 18, ale nie większa niż 120 |
alpha |
{name:alpha} |
Rick |
Ciąg musi składać się z co najmniej jednego znaku a -z alfabetycznego i bez uwzględniania wielkości liter. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
Ciąg musi być zgodny z wyrażeniem regularnym. Zobacz porady dotyczące definiowania wyrażenia regularnego. |
required |
{name:required} |
Rick |
Służy do wymuszania, że wartość nieparametrowa jest obecna podczas generowania adresu URL |
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wiele ograniczeń rozdzielonych dwukropkami można zastosować do jednego parametru. Na przykład następujące ograniczenie ogranicza parametr do wartości całkowitej 1 lub większej:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Ostrzeżenie
Ograniczenia trasy, które weryfikują adres URL i są konwertowane na typ CLR, zawsze używają niezmiennej kultury. Na przykład konwersja na typ int
CLR lub DateTime
. Te ograniczenia zakładają, że adres URL nie jest lokalizowalny. Ograniczenia trasy dostarczone przez platformę nie modyfikują wartości przechowywanych w wartościach tras. Wszystkie wartości tras analizowane z adresu URL są przechowywane jako ciągi. Na przykład float
ograniczenie próbuje przekonwertować wartość trasy na zmiennoprzecinkowy, ale przekonwertowana wartość jest używana tylko do sprawdzania, czy można ją przekonwertować na zmiennoprzecinkowy.
Wyrażenia regularne w ograniczeniach
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Wyrażenia regularne można określić jako ograniczenia wbudowane przy użyciu regex(...)
ograniczenia trasy. Metody w MapControllerRoute rodzinie akceptują również literał obiektów ograniczeń. Jeśli ten formularz jest używany, wartości ciągów są interpretowane jako wyrażenia regularne.
Poniższy kod używa wbudowanego ograniczenia wyrażeń regularnych:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
context =>
{
return context.Response.WriteAsync("inline-constraint match");
});
});
Poniższy kod używa literału obiektu do określenia ograniczenia wyrażenia regularnego:
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", });
});
Struktura ASP.NET Core dodaje RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
do konstruktora wyrażeń regularnych. Zobacz RegexOptions opis tych elementów członkowskich.
Wyrażenia regularne używają ograniczników i tokenów podobnych do tych używanych przez routing i język C#. Tokeny wyrażeń regularnych muszą zostać uniknięci. Aby użyć wyrażenia ^\d{3}-\d{2}-\d{4}$
regularnego w ograniczeniu wbudowanym, użyj jednego z następujących elementów:
- Zastąp
\
znaki podane w ciągu jako\\
znaki w pliku źródłowym języka C#, aby uniknąć\
znaku ucieczki ciągu. - Literały ciągu dosłowne.
Aby uniknąć znaków {
ogranicznika parametru routingu , , [
}
, ]
, podwaja znaki w wyrażeniu, na przykład {{
, , }}
, [[
, ]]
. W poniższej tabeli przedstawiono wyrażenie regularne i jego wersję unikniętą:
Regular expression | Wyrażenie regularne o wartości ucieczki |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Wyrażenia regularne używane w routingu często zaczynają się od ^
znaku i pasują do pozycji początkowej ciągu. Wyrażenia często kończą się znakiem $
i pasują do końca ciągu. Znaki ^
i $
zapewniają, że wyrażenie regularne jest zgodne z całą wartością parametru trasy. ^
Bez znaków i $
wyrażenie regularne pasuje do dowolnego podciągu w ciągu, co jest często niepożądane. W poniższej tabeli przedstawiono przykłady i wyjaśniono, dlaczego są one zgodne lub nie są zgodne:
Wyrażenie | String | Match | Komentarz |
---|---|---|---|
[a-z]{2} |
hello | Tak | Dopasowania podciągów |
[a-z]{2} |
123abc456 | Tak | Dopasowania podciągów |
[a-z]{2} |
mz | Tak | Dopasuj wyrażenie |
[a-z]{2} |
MZ | Tak | Nie uwzględnia wielkości liter |
^[a-z]{2}$ |
hello | Nie. | Zobacz ^ i $ powyżej |
^[a-z]{2}$ |
123abc456 | Nie. | Zobacz ^ i $ powyżej |
Aby uzyskać więcej informacji na temat składni wyrażeń regularnych, zobacz .NET Framework Regular Expressions (Wyrażenia regularne programu .NET Framework).
Aby ograniczyć parametr do znanego zestawu możliwych wartości, użyj wyrażenia regularnego. Na przykład {action:regex(^(list|get|create)$)}
pasuje action
tylko do wartości trasy do list
, get
lub create
. Jeśli zostanie przekazany do słownika ograniczeń, ciąg ^(list|get|create)$
jest równoważny. Ograniczenia przekazywane w słowniku ograniczeń, które nie są zgodne z jednym ze znanych ograniczeń, są również traktowane jako wyrażenia regularne. Ograniczenia przekazywane w szablonie, które nie pasują do jednego ze znanych ograniczeń, nie są traktowane jako wyrażenia regularne.
Ograniczenia trasy niestandardowej
Ograniczenia trasy niestandardowej można utworzyć przez zaimplementowanie interfejsu IRouteConstraint . Interfejs IRouteConstraint
zawiera Matchwartość , która zwraca true
wartość, jeśli ograniczenie jest spełnione i false
w przeciwnym razie.
Ograniczenia trasy niestandardowej są rzadko potrzebne. Przed zaimplementowaniem ograniczenia trasy niestandardowej rozważ alternatywy, takie jak powiązanie modelu.
Folder ASP.NET Core Constraints zawiera dobre przykłady tworzenia ograniczeń. Na przykład GuidRouteConstraint.
Aby użyć niestandardowego IRouteConstraint
, typ ograniczenia trasy musi być zarejestrowany w aplikacji ConstraintMap w kontenerze usługi. Jest ConstraintMap
to słownik, który mapuje klucze ograniczeń trasy na IRouteConstraint
implementacje, które weryfikują te ograniczenia. Aplikacje ConstraintMap
można zaktualizować w Startup.ConfigureServices
ramach usług. Dodaj wywołanie usługiRouting lub przez skonfigurowanie bezpośrednio za RouteOptions pomocą services.Configure<RouteOptions>
polecenia . Na przykład:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});
}
Powyższe ograniczenie jest stosowane w następującym kodzie:
[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 jest dostarczany przez pakiet NuGet Rick.Docs.Samples.RouteInfo i wyświetla informacje o trasie.
Implementacja elementu uniemożliwia 0
zastosowanie do parametru MyCustomConstraint
trasy:
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;
}
}
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressions
atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Powyższy kod ma następujące działanie:
- Zapobiega
0
w{id}
segmencie trasy. - Pokazano, aby podać podstawowy przykład implementacji ograniczenia niestandardowego. Nie należy jej używać w aplikacji produkcyjnej.
Poniższy kod jest lepszym podejściem do zapobiegania przetwarzaniu elementu zawierającego id
0
element:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return ControllerContext.MyDisplayRouteInfo(id);
}
Powyższy kod ma następujące zalety w porównaniu z podejściem MyCustomConstraint
:
- Nie wymaga on ograniczenia niestandardowego.
- Zwraca on bardziej opisowy błąd, gdy parametr trasy zawiera
0
wartość .
Dokumentacja przekształcania parametrów
Transformatory parametrów:
- Wykonaj polecenie podczas generowania linku przy użyciu polecenia LinkGenerator.
- Zaimplementuj .Microsoft.AspNetCore.Routing.IOutboundParameterTransformer
- Są konfigurowane przy użyciu polecenia ConstraintMap.
- Weź wartość trasy parametru i przekształć go w nową wartość ciągu.
- Wynik użycia przekształconej wartości w wygenerowanym linku.
Na przykład funkcja przekształcania parametrów niestandardowych slugify
we wzorcu blog\{article:slugify}
trasy z poleceniem Url.Action(new { article = "MyTestArticle" })
generuje blog\my-test-article
wartość .
Rozważmy następującą IOutboundParameterTransformer
implementację:
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();
}
}
Aby użyć transformatora parametrów we wzorcu trasy, skonfiguruj go przy użyciu ConstraintMap polecenia w pliku Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
}
Struktura ASP.NET Core używa funkcji przekształcania parametrów w celu przekształcenia identyfikatora URI, w którym punkt końcowy jest rozpoznawany. Na przykład transformatory parametrów przekształcają wartości tras używane do dopasowania area
wartości , , action
controller
i page
.
routes.MapControllerRoute(
name: "default",
template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
W przypadku poprzedniego szablonu trasy akcja SubscriptionManagementController.GetAll
jest zgodna z identyfikatorem URI /subscription-management/get-all
. Funkcja przekształcania parametrów nie zmienia wartości tras używanych do generowania łącza. Na przykład Url.Action("GetAll", "SubscriptionManagement")
dane wyjściowe ./subscription-management/get-all
ASP.NET Core udostępnia konwencje interfejsu API dotyczące używania transformatorów parametrów z wygenerowanymi trasami:
- Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention Konwencja MVC stosuje określony parametr transformer do wszystkich tras atrybutów w aplikacji. Transformator parametru przekształca tokeny tras atrybutu podczas ich wymiany. Aby uzyskać więcej informacji, zobacz Używanie funkcji przekształcania parametrów do dostosowywania zamiany tokenu.
- Razor Strony używają konwencji interfejsu PageRouteTransformerConvention API. Ta konwencja stosuje określony transformator parametru do wszystkich automatycznie odnalezionych Razor stron. Funkcja przekształcania parametrów przekształca segmenty Razor nazw folderów i plików tras stron. Aby uzyskać więcej informacji, zobacz Dostosowywanie tras stron przy użyciu funkcji przekształcania parametrów.
Dokumentacja generowania adresów URL
Ta sekcja zawiera odwołanie do algorytmu zaimplementowanego przez generowanie adresów URL. W praktyce większość złożonych przykładów generowania adresów URL używa kontrolerów lub Razor stron. Aby uzyskać dodatkowe informacje, zobacz routing w kontrolerach .
Proces generowania adresu URL rozpoczyna się od wywołania metody LinkGenerator.GetPathByAddress lub podobnej metody. Metoda jest dostarczana z adresem, zestawem wartości tras i opcjonalnie informacjami o bieżącym żądaniu z .HttpContext
Pierwszym krokiem jest użycie adresu w celu rozpoznania zestawu punktów końcowych kandydata przy użyciu elementu zgodnego z typem IEndpointAddressScheme<TAddress>
adresu.
Po znalezieniu zestawu kandydatów przez schemat adresów punkty końcowe są uporządkowane i przetwarzane iteracyjnie, dopóki operacja generowania adresu URL nie powiedzie się. Generowanie adresu URL nie sprawdza niejednoznaczności. Zwracany pierwszy wynik jest wynikiem końcowym.
Rozwiązywanie problemów z generowaniem adresu URL przy użyciu rejestrowania
Pierwszym krokiem generowania adresu URL rozwiązywania problemów jest ustawienie poziomu rejestrowania Microsoft.AspNetCore.Routing
na TRACE
. LinkGenerator
rejestruje wiele szczegółów dotyczących jego przetwarzania, które mogą być przydatne do rozwiązywania problemów.
Aby uzyskać szczegółowe informacje na temat generowania adresów URL, zobacz Dokumentację generowania adresów URL.
Adresy
Adresy to koncepcja generowania adresów URL używana do powiązania wywołania z generatorem linków z zestawem kandydatów punktów końcowych.
Adresy to rozszerzalna koncepcja, która domyślnie zawiera dwie implementacje:
- Użyj nazwy punktu końcowego (
string
) jako adresu:- Zapewnia podobne funkcje do nazwy trasy MVC.
- IEndpointNameMetadata Używa typu metadanych.
- Usuwa podany ciąg względem metadanych wszystkich zarejestrowanych punktów końcowych.
- Zgłasza wyjątek podczas uruchamiania, jeśli wiele punktów końcowych używa tej samej nazwy.
- Zalecane do użytku ogólnego przeznaczenia poza kontrolerami i Razor stronami.
- Użyj wartości tras (RouteValuesAddress) jako adresu:
- Zapewnia podobne funkcje do kontrolerów i Razor starszej generacji adresów URL stron.
- Bardzo złożone rozszerzanie i debugowanie.
- Zapewnia implementację używaną przez
IUrlHelper
pomocników tagów, pomocników HTML, wyniki akcji itp.
Rolą schematu adresów jest skojarzenie adresu i pasujących punktów końcowych według dowolnych kryteriów:
- Schemat nazw punktów końcowych wykonuje wyszukiwanie w słowniku podstawowym.
- Schemat wartości tras ma złożony najlepszy podzbiór ustawionego algorytmu.
Wartości otoczenia i jawne wartości
Z bieżącego żądania routing uzyskuje dostęp do wartości tras bieżącego żądania HttpContext.Request.RouteValues
. Wartości skojarzone z bieżącym żądaniem są określane jako wartości otoczenia. W celu jasności dokumentacja odnosi się do wartości tras przekazywanych do metod jako jawnych wartości.
W poniższym przykładzie przedstawiono wartości otoczenia i jawne wartości. Zapewnia ona wartości otoczenia z bieżącego żądania i jawnych wartości: : { 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);
}
Powyższy kod ma następujące działanie:
- Zwraca
/Widget/Index/17
- Pobiera LinkGenerator przez DI.
Poniższy kod nie zawiera wartości otoczenia i jawnych wartości: : { controller = "Home", action = "Subscribe", id = 17, }
public IActionResult Index2()
{
var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
new { id = 17, });
return Content(url);
}
Poprzednia metoda zwraca /Home/Subscribe/17
Poniższy kod w zwracaniu WidgetController
wartości /Widget/Subscribe/17
:
var url = _linkGenerator.GetPathByAction("Subscribe", null,
new { id = 17, });
Poniższy kod udostępnia kontrolerowi wartości otoczenia w bieżącym żądaniu i jawnych wartościach: : { action = "Edit", id = 17, }
public class GadgetController : Controller
{
public IActionResult Index()
{
var url = Url.Action("Edit", new { id = 17, });
return Content(url);
}
Powyższy kod:
/Gadget/Edit/17
jest zwracany.- Url pobiera element IUrlHelper.
- Action generuje adres URL ze ścieżką bezwzględną dla metody akcji. Adres URL zawiera określoną
action
nazwę iroute
wartości.
Poniższy kod zawiera wartości otoczenia z bieżącego żądania i jawnych wartości: : { page = "./Edit, id = 17, }
public class IndexModel : PageModel
{
public void OnGet()
{
var url = Url.Page("./Edit", new { id = 17, });
ViewData["URL"] = url;
}
}
Powyższy kod ustawia wartość url
, /Edit/17
gdy strona edycji Razor zawiera następującą dyrektywę strony:
@page "{id:int}"
Jeśli strona Edycja nie zawiera szablonu "{id:int}"
trasy, url
to /Edit?id=17
.
Zachowanie wzorca MVC IUrlHelper dodaje warstwę złożoności oprócz reguł opisanych tutaj:
IUrlHelper
zawsze udostępnia wartości tras z bieżącego żądania jako wartości otoczenia.- IUrlHelper.Action zawsze kopiuje bieżące
action
wartości icontroller
trasy jako wartości jawne, chyba że zostanie zastąpione przez dewelopera. - IUrlHelper.Page zawsze kopiuje bieżącą
page
wartość trasy jako jawną wartość, chyba że zostanie zastąpiona. IUrlHelper.Page
zawsze zastępuje bieżącąhandler
wartość trasy wartościąnull
jawną, chyba że zostanie zastąpiona.
Użytkownicy są często zaskoczeni szczegółami zachowania wartości otoczenia, ponieważ MVC nie wydaje się przestrzegać własnych reguł. Ze względów historycznych i zgodności niektóre wartości tras, takie jak action
, controller
, page
i handler
mają własne zachowanie specjalne.
Równoważna funkcjonalność zapewniana przez LinkGenerator.GetPathByAction
program i LinkGenerator.GetPathByPage
duplikuje te anomalie w IUrlHelper
celu zapewnienia zgodności.
Proces generowania adresów URL
Po znalezieniu zestawu punktów końcowych kandydata algorytm generowania adresów URL:
- Przetwarza iteracyjne punkty końcowe.
- Zwraca pierwszy pomyślny wynik.
Pierwszym krokiem w tym procesie jest unieważnienie wartości trasy. Unieważnienie wartości trasy to proces, za pomocą którego routing decyduje, które wartości tras z wartości otoczenia powinny być używane i które powinny być ignorowane. Każda wartość otoczenia jest uwzględniana i połączona z jawnymi wartościami lub ignorowana.
Najlepszym sposobem myślenia o roli wartości otoczenia jest to, że próbują zapisać deweloperów aplikacji wpisywanie, w niektórych typowych przypadkach. Tradycyjnie scenariusze, w których wartości otoczenia są przydatne, są związane z MVC:
- Podczas łączenia z inną akcją w tym samym kontrolerze nie trzeba określać nazwy kontrolera.
- Podczas łączenia z innym kontrolerem w tym samym obszarze nie trzeba określać nazwy obszaru.
- Podczas łączenia z tą samą metodą akcji wartości tras nie muszą być określone.
- Podczas łączenia z inną częścią aplikacji nie chcesz przenosić wartości tras, które nie mają znaczenia w tej części aplikacji.
Wywołania do LinkGenerator
lub IUrlHelper
tego zwracania null
są zwykle spowodowane przez niezrozumienie unieważnienia wartości trasy. Rozwiąż problemy z nieprawidłową wartością trasy, określając jawnie więcej wartości trasy, aby sprawdzić, czy rozwiązuje to problem.
Unieważnienie wartości trasy działa zgodnie z założeniem, że schemat adresu URL aplikacji jest hierarchiczny, z hierarchią utworzoną od lewej do prawej. Rozważmy podstawowy szablon {controller}/{action}/{id?}
trasy kontrolera, aby uzyskać intuicyjne zrozumienie sposobu działania tej metody w praktyce. Zmiana wartości unieważnia wszystkie wartości trasy, które są wyświetlane po prawej stronie. Odzwierciedla to założenie dotyczące hierarchii. Jeśli aplikacja ma wartość otoczenia dla id
, a operacja określa inną wartość dla elementu controller
:
id
nie będzie ponownie używany, ponieważ{controller}
znajduje się po lewej{id?}
stronie .
Kilka przykładów demonstrujących tę zasadę:
- Jeśli jawne wartości zawierają wartość parametru
id
, wartość otoczenia dlaid
elementu jest ignorowana. Wartości otoczenia icontroller
action
mogą być używane. - Jeśli jawne wartości zawierają wartość parametru
action
, każda wartość otoczenia dlaaction
elementu jest ignorowana. Można użyć wartościcontroller
otoczenia. Jeśli jawna wartość parametruaction
jest inna niż wartość otoczenia dlaaction
wartości ,id
wartość nie zostanie użyta. Jeśli jawna wartość parametruaction
jest taka sama jak wartość otoczenia dlaaction
,id
można użyć wartości . - Jeśli jawne wartości zawierają wartość parametru
controller
, każda wartość otoczenia dlacontroller
elementu jest ignorowana. Jeśli jawna wartość parametrucontroller
jest inna niż wartość otoczenia dlacontroller
,action
wartości iid
nie będą używane. Jeśli jawna wartość parametrucontroller
jest taka sama jak wartość otoczenia dlacontroller
,action
można użyć wartości iid
.
Ten proces jest jeszcze bardziej skomplikowany przez istnienie tras atrybutów i dedykowanych tras konwencjonalnych. Kontroler konwencjonalnych tras, takich jak {controller}/{action}/{id?}
określanie hierarchii przy użyciu parametrów trasy. W przypadku dedykowanych tras konwencjonalnych i tras atrybutów do kontrolerów i Razor stron:
- Istnieje hierarchia wartości tras.
- Nie są one wyświetlane w szablonie.
W takich przypadkach generowanie adresów URL definiuje koncepcję wymaganych wartości . Punkty końcowe utworzone przez kontrolery i Razor strony mają wymagane wartości określone, które umożliwiają działanie unieważnienia wartości trasy.
Algorytm unieważniania wartości trasy szczegółowo:
- Wymagane nazwy wartości są łączone z parametrami trasy, a następnie przetwarzane od lewej do prawej.
- Dla każdego parametru porównywana jest wartość otoczenia i jawna wartość:
- Jeśli wartość otoczenia i jawna wartość są takie same, proces będzie kontynuowany.
- Jeśli wartość otoczenia jest obecna, a jawna wartość nie jest, wartość otoczenia jest używana podczas generowania adresu URL.
- Jeśli wartość otoczenia nie jest obecna, a jawna wartość to, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
- Jeśli wartość otoczenia i jawna wartość są obecne, a dwie wartości są różne, odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
W tym momencie operacja generowania adresu URL jest gotowa do oceny ograniczeń trasy. Zestaw akceptowanych wartości jest połączony z wartościami domyślnymi parametrów, które są dostarczane do ograniczeń. Jeśli wszystkie ograniczenia przechodzą, operacja będzie kontynuowana.
Następnie akceptowane wartości mogą służyć do rozszerzania szablonu trasy. Szablon trasy jest przetwarzany:
- Od lewej do prawej.
- Każdy parametr ma zastąpioną akceptowaną wartość.
- W następujących przypadkach specjalnych:
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr ma wartość domyślną, zostanie użyta wartość domyślna.
- Jeśli w akceptowanych wartościach brakuje wartości, a parametr jest opcjonalny, przetwarzanie będzie kontynuowane.
- Jeśli dowolny parametr trasy z prawej strony brakującego opcjonalnego parametru ma wartość, operacja kończy się niepowodzeniem.
- Ciągłe parametry domyślne i parametry opcjonalne są zwinięte tam, gdzie to możliwe.
Wartości jawnie podane, które nie pasują do segmentu trasy, są dodawane do ciągu zapytania. W poniższej tabeli przedstawiono wynik użycia szablonu {controller}/{action}/{id?}
trasy .
Wartości otoczenia | Jawne wartości | Result |
---|---|---|
controller = "Home" | action = "Informacje" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "Informacje" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Problemy z unieważnieniem wartości trasy
Od wersji ASP.NET Core 3.0 niektóre schematy generowania adresów URL używane we wcześniejszych wersjach ASP.NET Core nie działają dobrze z generowaniem adresów URL. Zespół ASP.NET Core planuje dodać funkcje w celu zaspokojenia tych potrzeb w przyszłej wersji. Na razie najlepszym rozwiązaniem jest użycie starszego routingu.
Poniższy kod przedstawia przykład schematu generowania adresów URL, który nie jest obsługiwany przez routing.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("blog", "{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost", });
});
W poprzednim kodzie culture
parametr trasy jest używany do lokalizacji. Pragnieniem jest, culture
aby parametr zawsze był akceptowany jako wartość otoczenia. culture
Jednak parametr nie jest akceptowany jako wartość otoczenia ze względu na sposób działania wymaganych wartości:
- W szablonie
"default"
culture
trasy parametr trasy znajduje się po lewej stroniecontroller
elementu , więc zmianycontroller
nie będą unieważniaćculture
elementu . - W szablonie
"blog"
culture
trasy parametr trasy jest uznawany za znajdujący się po prawej stroniecontroller
elementu , który jest wyświetlany w wymaganych wartościach.
Konfigurowanie metadanych punktu końcowego
Poniższe linki zawierają informacje na temat konfigurowania metadanych punktu końcowego:
- Włączanie mechanizmu Cors z routingiem punktów końcowych
- Przykład IAuthorizationPolicyProvider przy użyciu atrybutu niestandardowego
[MinimumAgeAuthorize]
- Testowanie uwierzytelniania za pomocą atrybutu [Autoryzuj]
- RequireAuthorization
- Wybieranie schematu z atrybutem [Authorize]
- Stosowanie zasad przy użyciu atrybutu [Autoryzuj]
- Autoryzacja oparta na rolach w programie ASP.NET Core
Dopasowywanie hostów w trasach za pomocą elementu RequireHost
RequireHost stosuje ograniczenie do trasy, która wymaga określonego hosta. Parametr RequireHost
lub [Host] może być:
- Host:
www.domain.com
, pasujewww.domain.com
do dowolnego portu. - Host z symbolem wieloznacznymi:
*.domain.com
, pasujewww.domain.com
do ,subdomain.domain.com
lubwww.subdomain.domain.com
na dowolnym porcie. - Port:
*:5000
, pasuje do portu 5000 z dowolnym hostem. - Host i port:
www.domain.com:5000
lub*.domain.com:5000
, pasuje do hosta i portu.
Można określić wiele parametrów przy użyciu polecenia RequireHost
lub [Host]
. Ograniczenie pasuje do hostów prawidłowych dla dowolnego z parametrów. Na przykład pasuje domain.com
do [Host("domain.com", "*.domain.com")]
, www.domain.com
i subdomain.domain.com
.
Poniższy kod używa RequireHost
metody , aby wymagać określonego hosta na trasie:
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");
});
}
Poniższy kod używa atrybutu [Host]
na kontrolerze, aby wymagać któregokolwiek z określonych hostów:
[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();
}
}
Po zastosowaniu atrybutu [Host]
zarówno do kontrolera, jak i metody akcji:
- Używany jest atrybut akcji.
- Atrybut kontrolera jest ignorowany.
Wskazówki dotyczące wydajności routingu
Większość routingu została zaktualizowana w ASP.NET Core 3.0 w celu zwiększenia wydajności.
Gdy aplikacja ma problemy z wydajnością, routing jest często podejrzewany jako problem. Podejrzewa się, że routing polega na tym, że struktury, takie jak kontrolery i Razor strony, zgłaszają ilość czasu spędzonego w strukturze w komunikatach rejestrowania. W przypadku znacznej różnicy między czasem zgłoszonym przez kontrolery a łącznym czasem żądania:
- Deweloperzy eliminują kod aplikacji jako źródło problemu.
- Często zakłada się, że routing jest przyczyną.
Routing jest testowany pod kątem wydajności przy użyciu tysięcy punktów końcowych. Jest mało prawdopodobne, aby typowa aplikacja napotkała problem z wydajnością, ponieważ jest zbyt duża. Najczęstszą główną przyczyną niskiej wydajności routingu jest zwykle nieprawidłowe zachowanie niestandardowego oprogramowania pośredniczącego.
W poniższym przykładzie kodu przedstawiono podstawową technikę zawężania źródła opóźnienia:
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.");
});
});
}
Routing czasowy:
- Przeplataj każde oprogramowanie pośredniczące kopią oprogramowania pośredniczącego chronometrażu pokazanego w poprzednim kodzie.
- Dodaj unikatowy identyfikator, aby skorelować dane chronometrażu z kodem.
Jest to podstawowy sposób zawężenia opóźnienia, gdy jest to znaczące, na przykład więcej niż 10ms
. Odejmowanie Time 2
z Time 1
raportów czasu spędzonego wewnątrz oprogramowania pośredniczącego UseRouting
.
Poniższy kod używa bardziej kompaktowego podejścia do poprzedniego kodu chronometrażu:
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.");
});
});
}
Potencjalnie kosztowne funkcje routingu
Poniższa lista zawiera wgląd w funkcje routingu, które są stosunkowo kosztowne w porównaniu z podstawowymi szablonami tras:
- Wyrażenia regularne: istnieje możliwość zapisywania wyrażeń regularnych, które są złożone lub mają długi czas z niewielką ilością danych wejściowych.
- Segmenty złożone (
{x}-{y}-{z}
):- Są znacznie droższe niż analizowanie zwykłego segmentu ścieżki adresu URL.
- Powoduje przydzielenie wielu podciągów.
- Złożona logika segmentu nie została zaktualizowana w aktualizacji wydajności routingu ASP.NET Core 3.0.
- Dostęp do danych synchronicznych: wiele złożonych aplikacji ma dostęp do bazy danych w ramach routingu. ASP.NET Core 2.2 i starsze routingu mogą nie zapewniać odpowiednich punktów rozszerzalności do obsługi routingu dostępu do bazy danych. Na przykład IRouteConstraint, i IActionConstraint są synchroniczne. Punkty rozszerzalności, takie jak MatcherPolicy i EndpointSelectorContext , są asynchroniczne.
Wskazówki dla autorów bibliotek
Ta sekcja zawiera wskazówki dotyczące autorów bibliotek opartych na routingu. Te szczegóły mają na celu zapewnienie, że deweloperzy aplikacji mają dobre środowisko korzystania z bibliotek i struktur rozszerzających routing.
Definiowanie punktów końcowych
Aby utworzyć strukturę korzystającą z routingu do dopasowywania adresów URL, zacznij od zdefiniowania środowiska użytkownika, które opiera się na systemie UseEndpoints.
Kompilacja DO na podstawie funkcji IEndpointRouteBuilder. Dzięki temu użytkownicy mogą tworzyć struktury przy użyciu innych funkcji ASP.NET Core bez pomyłek. Każdy szablon ASP.NET Core zawiera routing. Załóżmy, że routing jest obecny i znany użytkownikom.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...);
endpoints.MapHealthChecks("/healthz");
});
NALEŻY zwrócić zapieczętowany typ betonowy z wywołania, do MapMyFramework(...)
którego implementuje IEndpointConventionBuilderelement . Większość metod struktury Map...
jest zgodne z tym wzorcem. Interfejs IEndpointConventionBuilder
:
- Umożliwia komponowanie metadanych.
- Jest celem różnych metod rozszerzeń.
Deklarowanie własnego typu umożliwia dodanie do konstruktora własnych funkcji specyficznych dla platformy. Można zawinąć konstruktora zadeklarowanego przez platformę i przekazać do niego wywołania.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
endpoints.MapHealthChecks("/healthz");
});
ROZWAŻ napisanie własnego EndpointDataSourcepliku . EndpointDataSource
jest elementem pierwotnym niskiego poziomu do deklarowania i aktualizowania kolekcji punktów końcowych. EndpointDataSource
to zaawansowany interfejs API używany przez kontrolery i Razor strony.
Testy routingu mają podstawowy przykład nieakcyjnego źródła danych.
NIE próbuj domyślnie rejestrować konta EndpointDataSource
. Wymagaj od użytkowników zarejestrowania struktury w programie UseEndpoints. Filozofią routingu jest to, że nic nie jest dołączane domyślnie i UseEndpoints
jest to miejsce do rejestrowania punktów końcowych.
Tworzenie zintegrowanego z routingiem oprogramowania pośredniczącego
ROZWAŻ zdefiniowanie typów metadanych jako interfejsu.
DO umożliwia używanie typów metadanych jako atrybutu w klasach i metodach.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Struktury, takie jak kontrolery i Razor strony, obsługują stosowanie atrybutów metadanych do typów i metod. W przypadku deklarowania typów metadanych:
- Udostępnij je jako atrybuty.
- Większość użytkowników zna stosowanie atrybutów.
Deklarowanie typu metadanych jako interfejsu zwiększa kolejną warstwę elastyczności:
- Interfejsy są komponowalne.
- Deweloperzy mogą zadeklarować własne typy, które łączą wiele zasad.
NIE umożliwia przesłaniania metadanych, jak pokazano w poniższym przykładzie:
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() { }
}
Najlepszym sposobem przestrzegania tych wytycznych jest unikanie definiowania metadanych znaczników:
- Nie należy po prostu szukać obecności typu metadanych.
- Zdefiniuj właściwość na metadanych i sprawdź właściwość .
Kolekcja metadanych jest uporządkowana i obsługuje zastępowanie według priorytetu. W przypadku kontrolerów metadane metody akcji są najbardziej specyficzne.
DO sprawia, że oprogramowanie pośredniczące jest przydatne z routingiem i bez tego routingu.
app.UseRouting();
app.UseAuthorization(new AuthorizationPolicy() { ... });
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization();
});
Jako przykład tej wskazówki należy wziąć pod uwagę UseAuthorization
oprogramowanie pośredniczące. Oprogramowanie pośredniczące autoryzacji umożliwia przekazanie zasad rezerwowych. Zasady rezerwowe, jeśli zostały określone, mają zastosowanie do obu następujących elementów:
- Punkty końcowe bez określonych zasad.
- Żądania, które nie są zgodne z punktem końcowym.
Dzięki temu oprogramowanie pośredniczące autoryzacji jest przydatne poza kontekstem routingu. Oprogramowanie pośredniczące autoryzacji może służyć do tradycyjnego programowania oprogramowania pośredniczącego.
Diagnostyka debugowania
Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw wartość Logging:LogLevel:Microsoft
Debug
. W środowisku deweloperów ustaw poziom dziennika w pliku appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}