Udostępnij za pośrednictwem


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.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy (Zasady obsługi platformy .NET Core). Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

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:

W tym artykule opisano szczegóły niskiego poziomu dotyczące routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:

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:

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}

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 UseRoutingelement .
  • 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, /healthzzostanie 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:

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.
  • 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 i UseEndpoints:
      • Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
      • Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami UseAuthorization i UseCors.
    • Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.

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 i Path == "/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 terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
  • 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 i UseCors.
    • Używanie oprogramowania pośredniczącego terminalu z UseAuthorization oprogramowaniem pośredniczącym lub UseCors wymaga ręcznego łączenia się z systemem autoryzacji.

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 do HttpRequest.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:

  1. Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
  2. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
  3. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem w MatcherPolicy zestawie wystąpień.
  4. Używa elementu EndpointSelector , aby podjąć ostateczną decyzję z poprzedniej listy.

Lista punktów końcowych jest priorytetowa zgodnie z:

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ą /helloadresu 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/Listadresu 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ą HttpContextelement . 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:

  1. Adres jest powiązany z listą punktów końcowych pasujących do adresu.
  2. 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 weryfikuje Host 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łówka Host 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 lub MapWhen. 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ów Map* .

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 .

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%2Fpathwartość . 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/pathwartość .

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 /abcdadresu 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ępnie a 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 /aabcdadresu 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ępnie a 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, getlub 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 0wartość .

Transformatory parametrów

Transformatory parametrów:

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-articlewartość .

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 areawartości , , actioncontrolleri 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:

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 IUrlHelperpomocnikó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:

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ę i route 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 i controller 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, pagei 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 dla id elementu jest ignorowana. Wartości otoczenia i controller action mogą być używane.
  • Jeśli jawne wartości zawierają wartość parametru action, każda wartość otoczenia dla action elementu jest ignorowana. Można użyć wartości controller otoczenia. Jeśli jawna wartość parametru action jest inna niż wartość otoczenia dla actionwartości , id wartość nie zostanie użyta. Jeśli jawna wartość parametru action jest taka sama jak wartość otoczenia dla action, id można użyć wartości .
  • Jeśli jawne wartości zawierają wartość parametru controller, każda wartość otoczenia dla controller elementu jest ignorowana. Jeśli jawna wartość parametru controller jest inna niż wartość otoczenia dla controller, action wartości i id nie będą używane. Jeśli jawna wartość parametru controller jest taka sama jak wartość otoczenia dla controller, action można użyć wartości i id .

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 stronie controllerelementu , więc zmiany controller nie będą unieważniać cultureelementu .
  • W szablonie "blog" culture trasy parametr trasy jest uznawany za znajdujący się po prawej stronie controllerelementu , 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 GetProductwartość :

[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 , pathToRelatedProductktó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/1relatedProductId 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:

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, pasuje www.domain.com do dowolnego portu.
  • Host z symbolem wieloznacznymi: *.domain.com, pasuje www.domain.comdo , subdomain.domain.comlub www.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.comdo [Host("domain.com", "*.domain.com")] , www.domain.comi 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:

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

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 i MapDynamicPageRoute.

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:

W tym artykule opisano szczegóły niskiego poziomu dotyczące routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:

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:

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}

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 UseRoutingelement .
  • 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, /healthzzostanie 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:

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.
  • 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 i UseEndpoints:
      • Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
      • Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami UseAuthorization i UseCors.
    • Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.

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 i Path == "/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 terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
  • 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 i UseCors.
    • Używanie oprogramowania pośredniczącego terminalu z UseAuthorization oprogramowaniem pośredniczącym lub UseCors wymaga ręcznego łączenia się z systemem autoryzacji.

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 do HttpRequest.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:

  1. Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
  2. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
  3. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem w MatcherPolicy zestawie wystąpień.
  4. Używa elementu EndpointSelector , aby podjąć ostateczną decyzję z poprzedniej listy.

Lista punktów końcowych jest priorytetowa zgodnie z:

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ą /helloadresu 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/Listadresu 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ą HttpContextelement . 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:

  1. Adres jest powiązany z listą punktów końcowych pasujących do adresu.
  2. 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 weryfikuje Host 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łówka Host 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 lub MapWhen. 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ów Map* .

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 .

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%2Fpathwartość . 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/pathwartość .

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 /abcdadresu 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ępnie a 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 /aabcdadresu 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ępnie a 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, getlub 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 0wartość .

Transformatory parametrów

Transformatory parametrów:

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-articlewartość .

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 areawartości , , actioncontrolleri 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:

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 IUrlHelperpomocnikó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:

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ę i route 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 i controller 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, pagei 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 dla id elementu jest ignorowana. Wartości otoczenia i controller action mogą być używane.
  • Jeśli jawne wartości zawierają wartość parametru action, każda wartość otoczenia dla action elementu jest ignorowana. Można użyć wartości controller otoczenia. Jeśli jawna wartość parametru action jest inna niż wartość otoczenia dla actionwartości , id wartość nie zostanie użyta. Jeśli jawna wartość parametru action jest taka sama jak wartość otoczenia dla action, id można użyć wartości .
  • Jeśli jawne wartości zawierają wartość parametru controller, każda wartość otoczenia dla controller elementu jest ignorowana. Jeśli jawna wartość parametru controller jest inna niż wartość otoczenia dla controller, action wartości i id nie będą używane. Jeśli jawna wartość parametru controller jest taka sama jak wartość otoczenia dla controller, action można użyć wartości i id .

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 stronie controllerelementu , więc zmiany controller nie będą unieważniać cultureelementu .
  • W szablonie "blog" culture trasy parametr trasy jest uznawany za znajdujący się po prawej stronie controllerelementu , 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 GetProductwartość :

[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 , pathToRelatedProductktó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/1relatedProductId 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:

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, pasuje www.domain.com do dowolnego portu.
  • Host z symbolem wieloznacznymi: *.domain.com, pasuje www.domain.comdo , subdomain.domain.comlub www.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.comdo [Host("domain.com", "*.domain.com")] , www.domain.comi 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ń.

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 i MapDynamicPageRoute.

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:

W tym artykule opisano szczegóły niskiego poziomu dotyczące routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:

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:

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}

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 UseRoutingelement .
  • 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, /healthzzostanie 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:

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.
  • 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 i UseEndpoints:
      • Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
      • Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami UseAuthorization i UseCors.
    • Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.

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 i Path == "/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 terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
  • 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 i UseCors.
    • Używanie oprogramowania pośredniczącego terminalu z UseAuthorization oprogramowaniem pośredniczącym lub UseCors wymaga ręcznego łączenia się z systemem autoryzacji.

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 do HttpRequest.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:

  1. Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
  2. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
  3. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem w MatcherPolicy zestawie wystąpień.
  4. Używa elementu EndpointSelector , aby podjąć ostateczną decyzję z poprzedniej listy.

Lista punktów końcowych jest priorytetowa zgodnie z:

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ą /helloadresu 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/Listadresu 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ą HttpContextelement . 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:

  1. Adres jest powiązany z listą punktów końcowych pasujących do adresu.
  2. 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 weryfikuje Host 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łówka Host 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 lub MapWhen. 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ów Map* .

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 .

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%2Fpathwartość . 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/pathwartość .

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 /abcdadresu 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ępnie a 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 /aabcdadresu 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ępnie a 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, getlub 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 0wartość .

Transformatory parametrów

Transformatory parametrów:

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-articlewartość .

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 areawartości , , actioncontrolleri 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:

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 IUrlHelperpomocnikó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:

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ę i route 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 i controller 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, pagei 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 dla id elementu jest ignorowana. Wartości otoczenia i controller action mogą być używane.
  • Jeśli jawne wartości zawierają wartość parametru action, każda wartość otoczenia dla action elementu jest ignorowana. Można użyć wartości controller otoczenia. Jeśli jawna wartość parametru action jest inna niż wartość otoczenia dla actionwartości , id wartość nie zostanie użyta. Jeśli jawna wartość parametru action jest taka sama jak wartość otoczenia dla action, id można użyć wartości .
  • Jeśli jawne wartości zawierają wartość parametru controller, każda wartość otoczenia dla controller elementu jest ignorowana. Jeśli jawna wartość parametru controller jest inna niż wartość otoczenia dla controller, action wartości i id nie będą używane. Jeśli jawna wartość parametru controller jest taka sama jak wartość otoczenia dla controller, action można użyć wartości i id .

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 stronie controllerelementu , więc zmiany controller nie będą unieważniać cultureelementu .
  • W szablonie "blog" culture trasy parametr trasy jest uznawany za znajdujący się po prawej stronie controllerelementu , 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 GetProductwartość :

[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 , pathToRelatedProductktó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/1relatedProductId 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:

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, pasuje www.domain.com do dowolnego portu.
  • Host z symbolem wieloznacznymi: *.domain.com, pasuje www.domain.comdo , subdomain.domain.comlub www.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.comdo [Host("domain.com", "*.domain.com")] , www.domain.comi 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ń.

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 i MapDynamicPageRoute.

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:

Ten dokument zawiera szczegółowe informacje o niskim poziomie routingu ASP.NET Core. Aby uzyskać informacje na temat konfigurowania routingu:

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:

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 / to https://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:

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}

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 UseRoutingelement .
  • 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, /healthzzostanie 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:

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.
  • 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 i UseEndpoints:
      • Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
      • Często podejmuje decyzje dotyczące zabezpieczeń, zgodnie z poleceniami UseAuthorization i UseCors.
    • Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla poszczególnych punktów końcowych.

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 i Path == "/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 terminala umożliwia pozycjonowanie oprogramowania pośredniczącego w dowolnym miejscu w potoku:
  • 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 i UseCors.
    • Używanie oprogramowania pośredniczącego terminalu z UseAuthorization oprogramowaniem pośredniczącym lub UseCors wymaga ręcznego łączenia się z systemem autoryzacji.

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 do HttpRequest.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:

  1. Przetwarza ścieżkę adresu URL względem zestawu punktów końcowych i ich szablonów tras, zbierając wszystkie dopasowania.
  2. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem z zastosowanymi ograniczeniami trasy.
  3. Pobiera poprzednią listę i usuwa dopasowania, które kończą się niepowodzeniem zestawu wystąpień MatcherPolicy .
  4. Używa elementu EndpointSelector do podjęcia ostatecznej decyzji z poprzedniej listy.

Lista punktów końcowych jest priorytetowa zgodnie z:

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ą /helloadresu 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/Listadresu 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ą HttpContextelement . 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:

  1. Adres jest powiązany z listą punktów końcowych pasujących do adresu.
  2. 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 weryfikuje Host 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łówka Host 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 lub MapWhen. 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ów Map* .

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 .

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%2Fpathwartość . 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/pathwartość .

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 /abcdadresu 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ępnie a 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 /aabcdadresu 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ępnie a 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, getlub 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 0wartość .

Dokumentacja przekształcania parametrów

Transformatory parametrów:

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-articlewartość .

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 areawartości , , actioncontrolleri 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:

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 IUrlHelperpomocnikó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:

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ę i route 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 i controller 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, pagei 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 dla id elementu jest ignorowana. Wartości otoczenia i controller action mogą być używane.
  • Jeśli jawne wartości zawierają wartość parametru action, każda wartość otoczenia dla action elementu jest ignorowana. Można użyć wartości controller otoczenia. Jeśli jawna wartość parametru action jest inna niż wartość otoczenia dla actionwartości , id wartość nie zostanie użyta. Jeśli jawna wartość parametru action jest taka sama jak wartość otoczenia dla action, id można użyć wartości .
  • Jeśli jawne wartości zawierają wartość parametru controller, każda wartość otoczenia dla controller elementu jest ignorowana. Jeśli jawna wartość parametru controller jest inna niż wartość otoczenia dla controller, action wartości i id nie będą używane. Jeśli jawna wartość parametru controller jest taka sama jak wartość otoczenia dla controller, action można użyć wartości i id .

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 stronie controllerelementu , więc zmiany controller nie będą unieważniać cultureelementu .
  • W szablonie "blog" culture trasy parametr trasy jest uznawany za znajdujący się po prawej stronie controllerelementu , 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:

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, pasuje www.domain.com do dowolnego portu.
  • Host z symbolem wieloznacznymi: *.domain.com, pasuje www.domain.comdo , subdomain.domain.comlub www.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.comdo [Host("domain.com", "*.domain.com")] , www.domain.comi 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"
    }
  }
}