Routing w ASP.NET Core

A w tym przypadku są to: Gdynie Nowak,Niemka Larkinai Ricka Andersona

Routing jest odpowiedzialny za dopasowywanie przychodzących żądań HTTP i wysyłanie tych żądań do wykonywalnych punktów końcowych aplikacji. Punkty końcowe to jednostki kodu wykonywalnego obsługi żądań aplikacji. 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 może również generować adresy URL mapowe na punkty końcowe.

Aplikacje mogą konfigurować routing przy użyciu:

Ten artykuł zawiera szczegółowe informacje na temat ASP.NET Core routingu. Aby uzyskać informacje na temat konfigurowania routingu:

Podstawowe informacje o routingu

Poniższy kod przedstawia podstawowy przykład routingu:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Poprzedni przykład obejmuje pojedynczy punkt końcowy korzystający z metody MapGet:

  • Gdy żądanie HTTP GET jest wysyłane do głównego adresu / URL:
    • Delegat żądania jest wykonywany.
    • Hello World! Jest zapisywany w odpowiedzi HTTP.
  • Jeśli metoda żądania nie jest lub główny adres URL nie ma adresu , żadna trasa nie pasuje i zwracany jest GET/ błąd HTTP 404.

Routing używa pary oprogramowania pośredniczącego zarejestrowanego przez system UseRouting i UseEndpoints :

  • UseRouting Dodaje dopasowywanie tras do potoku oprogramowania pośredniczącego. To oprogramowanie pośredniczące wyszukuje 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łać wywołania UseRouting ani UseEndpoints . WebApplicationBuilder Konfiguruje potok oprogramowania pośredniczącego, który opakuje oprogramowanie pośredniczące dodane w WebApplicationBuilder pomocą UseRouting i UseEndpoints . Jednak aplikacje mogą zmieniać kolejność, w jakiej są UseRouting uruchamiane, UseEndpoints wywołując te metody jawnie. Na przykład poniższy kod wykonuje jawne wywołanie do UseRouting :

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Powyższy kod ma następujące działanie:

  • Wywołanie app.Use rejestrowania niestandardowego oprogramowania pośredniczącego uruchamianego na początku potoku.
  • Wywołanie klasy konfiguruje pasujące oprogramowanie pośredniczące do UseRouting uruchamiania UseRouting oprogramowania pośredniczącego.
  • Punkt końcowy zarejestrowany za MapGet pomocą przebiegów na końcu potoku.

Jeśli poprzedni przykład nie zawierał wywołania do , niestandardowe oprogramowanie pośredniczące będzie uruchamiane po pasujących UseRouting trasach oprogramowania UseRouting pośredniczącego.

Punkty końcowe

Metoda MapGet służy do definiowania punktu MapGet. Punkt końcowy to coś, co może być:

  • 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 pliku UseEndpoints . Na przykład metody MapGet , i podobne MapPostMapGet delegatów żądań z systemem routingu. Dodatkowe metody mogą służyć do łączenia funkcji ASP.NET Core platformą z systemem routingu:

W poniższym przykładzie pokazano routing przy użyciu bardziej zaawansowanego szablonu trasy:

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

Ciąg jest /hello/{name:alpha}/hello/{name:alpha}. Szablon trasy służy do konfigurowania sposobu dopasowania punktu końcowego. W tym przypadku szablon jest taki jak:

  • Adres URL, taki jak /hello/Docs
  • Dowolna ścieżka URL, która rozpoczyna się /hello/ od ciągu , po której następuje sekwencja znaków alfabetycznych. :alpha stosuje ograniczenie trasy, które pasuje tylko do znaków alfabetycznych. Ograniczenia trasy zostały wyjaśnione w dalszej części tego artykułu.

Drugi segment ścieżki URL, {name:alpha} :

W poniższym przykładzie pokazano 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. Połączenie RequireAuthorization łańcuchowe z tym wywołaniem dołącza zasady autoryzacji do punktu końcowego.

Wywoływanie UseAuthenticationUseAuthorization i dodaje oprogramowanie pośredniczące uwierzytelniania i autoryzacji. Te oprogramowanie pośredniczące znajduje się między UseRouting i , dzięki czemu UseEndpoints mogą:

  • Zobacz, który punkt końcowy został wybrany przez parametr UseRouting .
  • Zastosuj zasady autoryzacji przed UseEndpoints wysłaniem ich do punktu końcowego.

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 kontroli kondycji, /healthz wykonywane jest 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 z routingiem.
  • Metadane mogą mieć dowolny typ .NET.

Pojęcia dotyczące routingu

System routingu opiera się na potoku oprogramowania pośredniczącego przez dodanie zaawansowanej koncepcji 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 ASP.NET Core systemów aplikacji.

ASP.NET Core definicji punktu końcowego

Punkt ASP.NET Core to:

Poniższy kod pokazuje, jak pobrać i sprawdzić punkt końcowy zgodny z bieżącym żądaniem:

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 HttpContext . Jej właściwości można sprawdzić. Obiekty punktu końcowego są niezmienne i nie można ich modyfikować po utworzeniu. Najpopularniejszym typem punktu końcowego jest RouteEndpoint . RouteEndpoint Zawiera informacje, które umożliwiają jego wybraną przez system routingu.

W poprzednim kodzie jest to aplikacja. Użyj konfiguruje wbudowane oprogramowanie pośredniczące.

Poniższy kod pokazuje, że w zależności od tego, gdzie jest wywoływana w app.Use potoku, może nie być punktu końcowego:

app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

W poprzednim przykładzie Console.WriteLine dodano instrukcje, które wyświetlają, czy wybrano punkt końcowy. Dla przejrzystości przykład przypisuje nazwę wyświetlaną do podanego punktu / końcowego.

Poprzedni przykład zawiera również wywołania do i w celu dokładnego kontrolowania, kiedy UseRoutingUseEndpoints to oprogramowanie pośredniczące jest uruchamiane w potoku.

Uruchomienie tego kodu z adresem URL powoduje / wyświetlenie:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

Uruchomienie tego kodu z dowolnym innym adresem 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, zanim UseRouting zostanie wywołana.
  • Jeśli zostanie znalezione dopasowanie, punkt końcowy ma wartość niezerową między UseRouting i UseEndpoints .
  • Oprogramowanie UseEndpoints pośredniczące jest UseEndpoints gdy zostanie znalezione dopasowanie. Oprogramowanie pośredniczące terminalu jest zdefiniowane w dalszej części tego artykułu.
  • Oprogramowanie pośredniczące po UseEndpoints wykonaniu jest wykonywane tylko wtedy, gdy nie zostanie znalezione dopasowanie.

Oprogramowanie UseRouting pośredniczące używa metody UseRouting aby dołączyć punkt końcowy do bieżącego kontekstu. Oprogramowanie pośredniczące można zastąpić niestandardową logiką i nadal korzystać UseRouting z zalet korzystania z punktów końcowych. Punkty końcowe są prymitywem niskiego poziomu, podobnie jak oprogramowanie pośredniczące, i nie są powiązane z implementacją routingu. Większość aplikacji nie musi być zastępowana UseRouting logiką niestandardową.

Oprogramowanie pośredniczące jest przeznaczone do współpracy z oprogramowaniem UseEndpointsUseRouting pośredniczącem. Podstawowa logika do wykonywania punktu końcowego nie jest skomplikowana. Użyj GetEndpoint metody , aby pobrać punkt końcowy, a następnie wywołaj 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 pokazano dwa ważne pojęcia:

  • Oprogramowanie pośredniczące może być uruchamiane przed UseRouting programem w celu zmodyfikowania danych, na których działa routing.
  • Oprogramowanie pośredniczące może działać między UseRouting i UseEndpoints w celu przetwarzania wyników routingu przed wykonaniem punktu końcowego.
    • Oprogramowanie pośredniczące, które działa między UseRouting i UseEndpoints :
      • Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
      • Często podejmuje decyzje dotyczące zabezpieczeń, tak jak to robią UseAuthorization i UseCors .
    • Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla każdego punktu końcowego.

Powyższy kod przedstawia przykład niestandardowego oprogramowania pośredniczącego, które obsługuje zasady dla niestandardowych punktów końcowych. Oprogramowanie pośredniczące zapisuje dziennik inspekcji dostępu do poufnych danych w konsoli programu . Oprogramowanie pośredniczące można skonfigurować do inspekcji punktu końcowego z metadanymi. W tym przykładzie pokazano wzorzec zgody, w którym inspekcje mają tylko punkty końcowe oznaczone jako poufne. Można zdefiniować tę logikę w odwrotnej kolejności, na przykład inspekcji wszystkich danych, które nie są oznaczone jako bezpieczne. System metadanych punktu końcowego jest elastyczny. Tę logikę można zaprojektować w dowolny sposób, który odpowiada przypadku użycia.

Powyższy przykładowy kod ma na celu zademonstrowanie podstawowych pojęć związanych z punktami końcowymi. Przykład nie jest przeznaczony do użytku produkcyjnego. Bardziej kompletna wersja oprogramowania pośredniczącego dziennika inspekcji będzie:

  • 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 zasad inspekcji są definiowane jako element ułatwiający korzystanie z platform opartych na RequiresAuditAttributeAttribute klasach, takich jak kontrolery i SignalR . W przypadku używania trasy do kodu:

  • Metadane są dołączane do interfejsu API konstruktora.
  • Struktury oparte na klasach zawierają wszystkie atrybuty odpowiedniej metody i klasy podczas tworzenia punktów końcowych.

Najlepsze rozwiązania dotyczące typów metadanych to 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 terminalu, 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.");

Wyświetlanym stylem oprogramowania pośredniczącego Approach 1: jest Approach 1:. Jest ono nazywane terminalowym oprogramowaniem pośredniczącem, ponieważ robi operację dopasowania:

  • Operacja dopasowywania w poprzednim przykładzie jest dla Path == "/" oprogramowania pośredniczącego i Path == "/Routing" routingu.
  • Gdy dopasowanie powiedzie się, wykonuje pewne funkcje i zwraca, zamiast wywołania next oprogramowania pośredniczącego.

Jest ono nazywane terminalowym oprogramowaniem pośredniczącem, ponieważ kończy wyszukiwanie, wykonuje pewne funkcje, a następnie zwraca.

Na poniższej liście porównano oprogramowanie pośredniczące terminalu z routingiem:

  • Oba podejścia umożliwiają zakończenie potoku przetwarzania:
    • Oprogramowanie pośredniczące kończy potok, zwracając zamiast wywołania next .
    • Punkty końcowe są zawsze terminalne.
  • Oprogramowanie pośredniczące terminalu umożliwia ustawianie 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 do niej pasuje:
    • Kod dopasowania tras niestandardowych może być pełny i trudny do poprawnego napisania.
    • Routing zapewnia proste rozwiązania dla typowych aplikacji. Większość aplikacji nie wymaga kodu dopasowania tras niestandardowych.
  • Interfejs punktów końcowych z oprogramowaniem pośredniczącem, takim UseAuthorization jak i UseCors .
    • Korzystanie z terminalowego oprogramowania pośredniczącego z UseAuthorization lub UseCors wymaga ręcznej współpracy z systemem autoryzacji.

Punkt końcowy definiuje obie te definicje:

  • Delegat do przetwarzania żądań.
  • Kolekcja dowolnych metadanych. Metadane są używane do implementowanie poszczególnych kwestii 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.

Rozważ integrację z routingiem przed napisaniem oprogramowania pośredniczącego terminalu.

Istniejące oprogramowanie pośredniczące terminalu, które integruje się z usługą Map lub zwykle można je zamienić w punkt końcowy z uwzględnieniem routingu. MapHealthChecks demonstruje wzorzec oprogramowania router-ware:

  • Napisz metodę rozszerzenia w pliku IEndpointRouteBuilder .
  • Utwórz potok zagnieżdżonych oprogramowania pośredniczącego przy użyciu narzędzia CreateApplicationBuilder .
  • Dołącz oprogramowanie pośredniczące do nowego potoku. W tym przypadku jest to UseHealthChecks .
  • Build potok oprogramowania pośredniczącego RequestDelegate do .
  • Wywołaj Map i podaj nowy potok oprogramowania pośredniczącego.
  • Zwróć obiekt konstruktora dostarczony Map przez z metody rozszerzenia.

Poniższy kod pokazuje użycie funkcji MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

W poprzednim przykładzie pokazano, dlaczego zwracanie obiektu konstruktora jest ważne. Zwrócenie obiektu konstruktora umożliwia deweloperowi aplikacji skonfigurowanie 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 rozszerzalności przy użyciu oprogramowania pośredniczącego terminalu. Wdrożenie własnej integracji z systemem autoryzacji dla każdego oprogramowania pośredniczącego jest problematyczne.

Dopasowywanie adresów URL

  • Czy proces, za pomocą którego routing pasuje do żądania przychodzącego do punktu końcowego,
  • Jest oparta na danych w ścieżce adresu URL i nagłówkach.
  • Można ją rozszerzyć w celu uwzględnienia dowolnych danych w żądaniu.

Gdy jest wykonywane oprogramowanie pośredniczące routingu, ustawia i przekieruje wartości do funkcji żądania w żądaniu Endpoint z EndpointHttpContext bieżącego żądania:

Oprogramowanie pośredniczące uruchomione po pośredniczącego routingu może sprawdzić punkt końcowy i podjąć działania. Na przykład oprogramowanie pośredniczące autoryzacji może interrogate kolekcji 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 podjęta w systemie routingu.

Ostrzeżenie

Aby zapewnić zgodność z poprzednimi wersjami, podczas wykonywania delegata punktu końcowego kontrolera lub strony właściwości RazorRazor są ustawiane na odpowiednie wartości na podstawie wykonanego do tej pory przetwarzania żądania.

Typ RouteContext zostanie oznaczony jako przestarzały w przyszłej wersji:

  • RouteData.ValuesPrzemigruj do HttpRequest.RouteValues .
  • RouteData.DataTokensPrzemigruj, RouteData.DataTokens z metadanych punktu końcowego.

Dopasowywanie adresów URL działa w konfigurowalnym zestawie faz. W każdej fazie dane wyjściowe są zestawem dopasowania. Zestaw dopasowania można jeszcze bardziej zawęzić do następnej fazy. 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 występują 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 nie powiodły się po zastosowaniu ograniczeń trasy.
  3. Przyjmuje poprzednią listę i usuwa dopasowania, które nie powiodą się w zestawie wystąpień matcherPolicy.
  4. Używa endpointselector do podjęcia ostatecznej decyzji z powyższej listy.

Priorytet listy punktów końcowych określa się według:

Wszystkie zgodne punkty końcowe są przetwarzane w każdej fazie, dopóki nie EndpointSelector zostanie osiągnięty. Jest EndpointSelector to faza końcowa. Wybiera punkt końcowy o najwyższym priorytecie spośród dopasowań jako najlepsze dopasowanie. Jeśli istnieją inne dopasowania o takim samym priorytecie jak 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 te adresy są zgodne ze ścieżką adresu URL /hello .
  • /hello jest bardziej szczegółowe i dlatego ma wyższy priorytet.

Ogólnie rzecz biorąc, pierwszeństwo trasy dobrze pasuje do rodzajów schematów adresów URL używanych w praktyce. Należy Order używać tylko wtedy, gdy jest to konieczne, aby uniknąć niejednoznaczności.

Ze względu na rodzaje rozszerzalności zapewniane przez routing nie jest możliwe, aby system routingu obliczał z wyprzedzeniem niejednoznaczne trasy. Rozważmy przykład, taki jak szablony tras /{message:alpha} i /{message:int} :

  • Ograniczenie alpha dopasowuje tylko znaki alfabetyczne.
  • Ograniczenie int dopasowuje tylko liczby.
  • Te szablony mają takie samo pierwszeństwo trasy, ale nie ma jednego adresu URL, z który są zgodne.
  • Jeśli system routingu zgłosił błąd niejednoznaczności podczas uruchamiania, zablokowałby ten 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 automatycznie przypisują wartość zamówienia do punktów końcowych na podstawie MapAreaRoute kolejności ich wywoływania. Symuluje to długotrwałe zachowanie kontrolerów bez systemu routingu zapewniającego takie same gwarancje jak starsze implementacje routingu.

Routing punktów końcowych w ASP.NET Core:

  • Nie ma koncepcji 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 tego, jaka jest jego specyfika. Pierwszeństwo szablonu trasy:

  • Pozwala uniknąć konieczności dostosowania kolejności punktów końcowych w typowych przypadkach.
  • Próbuje dopasować typowe oczekiwania dotyczące zachowania routingu.

Rozważmy na przykład szablony /Products/List i /Products/{id} . Rozsądnie byłoby założyć, że jest /Products/List to lepsze dopasowanie niż dla ścieżki adresu URL /Products/{id}/Products/List . Działa to, ponieważ segment literału jest uznawany za lepszy /List priorytet niż segment parametrów /{id} .

Szczegóły dotyczące sposobu działania pierwszeństwa są powiązane ze zdefiniowanymi szablonami tras:

  • Szablony z większą liczby segmentów są uznawane za bardziej szczegółowe.
  • Segment z tekstem literału jest uznawany za bardziej konkretny niż segment parametrów.
  • Segment parametrów z ograniczeniem jest uznawany za bardziej konkretny niż jeden bez.
  • Segment złożony jest traktowany jako specyficzny jako segment parametrów z ograniczeniem.
  • Parametry catch-all są najmniej specyficzne. Zobacz 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, w którym 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 mają do nich dostęp.

Routing punktów końcowych obejmuje interfejs LinkGenerator API. LinkGenerator jest pojedynczą usługą dostępną z LinkGenerator. Interfejsu LinkGenerator API można używać poza kontekstem wykonywanego żądania. Mvc.IUrlHelper i scenariusze, które polegają na , takich jak Pomocnicy tagów, Pomocnicy HTML i Wyniki akcji , używają interfejsu API wewnętrznie, aby zapewnić możliwości generowania linków.

Generator linków jest pozyskany przez koncepcję schematów adresów i adresów. Schemat adresów to sposób określania punktów końcowych, które należy wziąć pod uwagę podczas generowania linków. Na przykład scenariusze nazwy trasy i wartości trasy są znane wielu użytkownikom z kontrolerów, a strony są Razor implementowane jako schemat adresów.

Generator linków może łączyć się z kontrolerami i Razor stronami za pośrednictwem następujących metod rozszerzeń:

Przeciążenia tych metod akceptują argumenty, które obejmują HttpContext . Te metody są funkcjonalnie równoważne funkcji Url.Actioni Url.Page, ale oferują dodatkową elastyczność i opcje.

Metody są najbardziej podobne do metod i , ponieważ generują one wartość GetPath*Url.ActionUrl.Page URI zawierającą ścieżkę bezwzględną. Metody GetUri* zawsze generują bezwzględny URI zawierający schemat i hosta. Metody akceptujące generowanie HttpContext URI w kontekście wykonującego żądania. Wartości trasy otoczenia, ścieżka podstawowa adresu URL, schemat i host z żądania wykonującego są używane, chyba że zostaną zastąpione.

LinkGenerator Element jest wywoływany z adresem . Generowanie URI odbywa się w dwóch krokach:

  1. Adres jest powiązany z listą punktów końcowych, które pasują do adresu.
  2. Każdy punkt końcowy jest oceniany do momentu, aż zostanie znaleziony wzorzec trasy, który odpowiada RoutePattern dostarczonym wartościom. Wynikowe dane wyjściowe są łączone z innymi częściami URI dostarczonymi do generatora linków i zwracane.

Metody udostępniane przez LinkGenerator usługę obsługują standardowe możliwości generowania linków dla dowolnego typu adresu. Najbardziej wygodnym sposobem korzystania z generatora linków są metody rozszerzenia, które wykonują operacje dla określonego typu adresu:

Extension, metoda Opis
GetPathByAddress Generuje URI ze ścieżką bezwzględną na podstawie podanych wartości.
GetUriByAddress Generuje bezwzględny URI na podstawie podanych wartości.

Ostrzeżenie

Zwróć uwagę na następujące implikacje metod LinkGenerator wywoływania:

  • Metody rozszerzeń należy stosować ostrożnie w konfiguracji aplikacji, która nie weryfikuje GetUri*Host nagłówka żądań przychodzących. Jeśli nagłówek żądań przychodzących nie zostanie zweryfikowany, niezaufane dane wejściowe żądania mogą być wysyłane z powrotem do klienta w interfejsach URI w Host widoku lub na stronie. Zalecamy, aby wszystkie aplikacje produkcyjne skonfigurły swój serwer tak, aby sprawdzał Host poprawność nagłówka pod względem znanych prawidłowych wartości.

  • Należy LinkGenerator zachować ostrożność w oprogramowanie pośredniczące w połączeniu z lub MapMapWhen . Map* Zmienia ścieżkę podstawową wykonującego żądania, co ma wpływ na dane wyjściowe generowania łącza. Wszystkie LinkGenerator interfejsy API umożliwiają określenie ścieżki podstawowej. Określ pustą ścieżkę podstawową, aby cofnąć Map* wpływ na generowanie linków.

Przykład oprogramowania pośredniczącego

W poniższym przykładzie oprogramowanie pośredniczące używa interfejsu API do utworzenia linku do metody akcji, która LinkGenerator wyświetla listę produktów ze sklepu. Użycie generatora linków przez wstrzyknięcie go do klasy i wywołanie jest GenerateLink 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 funkcji definiują parametry trasy, które są powiązane, jeśli trasa jest do siebie dopasowana. W segmencie trasy można zdefiniować więcej niż jeden parametr trasy, ale parametry trasy muszą być oddzielone wartością literału. Na przykład nie jest prawidłową trasą, ponieważ nie ma wartości {controller=Home}{action=Index} 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 ) i separator ścieżki muszą być zgodne {id}/ z tekstem w adresie URL. W dopasowywaniu tekstu nie jest roz rozwrażliwa na litera i opiera się na zdekodowanych reprezentacjach ścieżki adresu URL. Aby dopasować ogranicznik parametru trasy literału { lub } , ogranicznik ucieczki przez powtórzenie znaku. Na przykład {{ lub }} .

Gwiazdka * lub podwójna gwiazdka: **

  • Może służyć jako prefiks do parametru trasy w celu powiązania z pozostałą część identyfikatora URI.
  • Są nazywane parametrami catch-all. Na blog/{**slug} przykład:
    • Pasuje do dowolnego URI, który rozpoczyna się od /blog i ma dowolną wartość po nim.
    • Następująca wartość /blog jest przypisywana do /blog

Ostrzeżenie

Parametr catch-all może niepoprawnie dopasować trasy z powodu usterki w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:

  • Trasa catch-all, na przykład {**slug}"
  • Trasa catch-all nie pasuje do żądań, które powinny być zgodne.
  • Usunięcie innych tras sprawia, że trasa catch-all zaczyna działać.

Zobacz GitHub błędów 18677 i 16579, aby uzyskać przykładowe przypadki, w których ta usterka dotyczy.

Poprawka dla tej usterki jest zawarta w zestawie SDK platformy .NET Core 3.1.301 i nowszych. 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 również mogą być zgodne z pustym ciągiem.

Parametr catch-all unika odpowiednie znaki, gdy trasa jest używana do generowania adresu URL, w tym znaków separatora / ścieżki. Na przykład trasa z foo/{*path} wartościami trasy { path = "my/path" } generuje wartość foo/my%2Fpath . Zwróć uwagę na ukośnik ucieczki. Aby znaki separatora ścieżki rundy, użyj ** prefiksu parametru trasy. Trasa z foo/{**path} generuje { path = "my/path" }foo/my/path .

Wzorce adresów URL, które próbują przechwycić nazwę pliku z opcjonalnym rozszerzeniem pliku, mają dodatkowe zagadnienia. Rozważmy na przykład szablon files/{filename}.{ext?} . Gdy wartości dla wartości filename i ext istnieją, obie wartości są wypełniane. Jeśli w adresie URL istnieje tylko wartość filename , trasa jest taka, ponieważ końcowe wartości . są opcjonalne. Następujące adresy URL są zgodne z tą trasą:

  • /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 jako wartość Home domyślną dla 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 wartościami opcjonalnymi i domyślnymi parametrami trasy jest:

  • 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 : nazwy ograniczenia i po nazwie parametru trasy określa wbudowane ograniczenie parametru trasy. Jeśli ograniczenie wymaga argumentów, są one ujęte w nawiasy (...) po nazwie ograniczenia. Wiele ograniczeń w tekście można określić, dołączając inną nazwę ograniczenia i .

Nazwa ograniczenia i argumenty są przekazywane do usługi w celu utworzenia wystąpienia klasy do IInlineConstraintResolverIRouteConstraint użycia w przetwarzaniu adresu URL. Na przykład szablon trasy określa blog/{article:minlength(10)} ograniczenie minlength za pomocą argumentu 10 . Aby uzyskać więcej informacji na temat ograniczeń trasy i listy ograniczeń dostarczanych przez platformę, zobacz sekcję Ograniczenia trasy.

Parametry trasy mogą również mieć transformatory parametrów. Transformatory parametrów przekształcają wartość parametru podczas generowania linków i dopasowywania akcji i stron do adresów URL. Podobnie jak ograniczenia, transformatory parametrów mogą być dodawane w tekście do parametru trasy przez dodanie nazwy transformatora i : po nazwie parametru trasy. Na przykład szablon trasy określa blog/{article:slugify}slugify transformatora. 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 URI URI żądania...
hello /hello Pasuje tylko do pojedynczej ścieżki /hello .
{Page=Home} / Dopasowuje i ustawia Page na Home .
{Page=Home} /Contact Dopasowuje i ustawia Page na Contact .
{controller}/{action}/{id?} /Products/List Mapy do kontrolera Products i List akcji.
{controller}/{action}/{id?} /Products/Details/123 Mapy do kontrolera Products i akcji z Detailsid ustawioną na 123.
{controller=Home}/{action=Index}/{id?} / Mapy do kontrolera Home i Index metody. Parametr id jest ignorowany.
{controller=Home}/{action=Index}/{id?} /Products Mapy do kontrolera Products i Index metody. Parametr id jest ignorowany.

Użycie szablonu jest zazwyczaj najprostszym podejściem do routingu. Ograniczenia i wartości domyślne można również określić poza szablonem trasy.

Segmenty złożone

Złożone segmenty są przetwarzane przez dopasowanie ograniczników literału od prawej do lewej w sposób niezachłanny. Na przykład [Route("/a{b}c{d}")] jest to segment złożony. Złożone segmenty działają w określony sposób, który musi być zrozumiały, aby można było z nich korzystać pomyślnie. W przykładzie w tej sekcji pokazano, dlaczego złożone segmenty działają dobrze tylko wtedy, gdy tekst ogranicznika nie pojawia się wewnątrz wartości parametrów. W przypadku bardziej złożonych przypadków wymagane jest użycie wyrażenia regularnego, a następnie ręczne wyodrębnienie wartości.

Ostrzeżenie

W przypadku System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions przekierowywu limitu czasu.

To jest podsumowanie kroków, które routing wykonuje za pomocą szablonu /a{b}c{d} i ścieżki adresu URL /abcd . Funkcja | służy do wizualizacji sposobu działania algorytmu:

  • Pierwszy literał, od prawej do lewej, to c . Tak /abcd więc jest wyszukiwany od prawej strony i znajduje /ab|c|d .
  • Wszystko po prawej stronie ( d ) jest teraz dopasowane do parametru trasy {d} .
  • Następny literał, od prawej do lewej, to a . Tak /ab|c|d więc wyszukiwanie rozpoczyna się od miejsca, w którym zostało to pozostawione, a następnie jest a wyszukiwane /|a|b|c|d .
  • Wartość po prawej stronie ( b ) jest teraz do dopasowana do parametru trasy {b} .
  • Nie ma pozostałego tekstu i nie ma pozostałego szablonu trasy, więc jest to dopasowanie.

Oto przykład przypadku negatywnego przy użyciu tego samego szablonu /a{b}c{d} i ścieżki adresu URL /aabcd . Funkcja | służy do wizualizacji sposobu działania algorytmu. Ten przypadek nie jest dopasowaniem, co jest wyjaśnione za pomocą tego samego algorytmu:

  • Pierwszy literał, od prawej do lewej, to c . Tak /aabcd więc jest wyszukiwany od prawej strony i znajduje /aab|c|d .
  • Wszystko po prawej stronie ( d ) jest teraz dopasowane do parametru trasy {d} .
  • Następny literał, od prawej do lewej, to a . Tak /aab|c|d więc wyszukiwanie rozpoczyna się od miejsca, w którym zostało to pozostawione, a następnie jest a wyszukiwane /a|a|b|c|d .
  • Wartość po prawej stronie ( b ) jest teraz do dopasowana do parametru trasy {b} .
  • W tym momencie jest pozostały tekst , ale w algorytmie zabrakło szablonu trasy do analizy, więc nie a jest to dopasowanie.

Ponieważ algorytm dopasowywania jest niezachłanny:

  • Odpowiada on najmniejszej możliwej ilości tekstu w każdym kroku.
  • W każdym przypadku, gdy wartość ogranicznika pojawia się wewnątrz wartości parametrów, wyniki nie są zgodne.

Wyrażenia regularne zapewniają znacznie większą kontrolę nad ich zachowaniem dopasowania.

Zachłanne dopasowywanie, czyli dopasowywanie z opóźnieniem,pasuje do największego możliwego ciągu. Niezachłanny dopasowuje najmniejszą możliwą wartość ciągu.

Ograniczenia trasy

Ograniczenia trasy są wykonywane, gdy wystąpiło dopasowanie do przychodzącego adresu URL, a ścieżka adresu URL jest tokenizowana na wartości trasy. Ograniczenia trasy zazwyczaj sprawdzają wartość trasy skojarzoną za pośrednictwem szablonu trasy i decyzje dotyczące tego, czy wartość jest dopuszczalna, czy jest to prawda, czy fałsz. Niektóre ograniczenia trasy używają danych spoza wartości trasy, aby rozważyć, czy żądanie może być kierowane. Na przykład może HttpMethodRouteConstraint akceptować lub odrzucać żądanie na podstawie jego czasownika HTTP. Ograniczenia są używane podczas generowania żądań routingu i linków.

Ostrzeżenie

Nie używaj ograniczeń do weryfikacji danych wejściowych. Jeśli ograniczenia są używane do weryfikacji danych wejściowych, nieprawidłowe dane wejściowe skutkują odpowiedzią 404 Nie znaleziono. Nieprawidłowe dane wejściowe powinny dawać 400 nieprawidłowe żądanie z odpowiednim komunikatem o błędzie. Ograniczenia trasy są używane do ujednoznania podobnych tras, a nie do weryfikowania 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 Dopasowuje dowolną liczbę całkowitą
bool {active:bool} true, FALSE Dopasowuje true lub false . Bez uwzględniania wielkości liter
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Dopasowuje DateTime prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
decimal {price:decimal} 49.99, -1,000.01 Dopasowuje decimal prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
double {weight:double} 1.234, -1,001.01e8 Dopasowuje double prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
float {weight:float} 1.234, -1,001.01e8 Dopasowuje float prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Dopasowuje prawidłową Guid wartość
long {ticks:long} 123456789, -123456789 Dopasowuje prawidłową long wartość
minlength(value) {username:minlength(4)} Rick Ciąg musi zawierać 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ć 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 może zawierać 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 może być większa niż 120
alpha {name:alpha} Rick Ciąg musi składać się z co najmniej jednego znaków alfabetycznych i bez uwzględniania a-z liter.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 Ciąg musi być zgodne z wyrażeniem regularnym. Zobacz porady dotyczące definiowania wyrażenia regularnego.
required {name:required} Rick Służy do wymuszania, że wartość bez parametru jest obecna podczas generowania adresu URL

Ostrzeżenie

W przypadku System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions limitu czasu.

Ograniczenia rozdzielane wieloma dwukropkami można zastosować do pojedynczego 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 CLR int lub DateTime . Te ograniczenia zakładają, że adres URL nie jest do lokalizacji. Ograniczenia tras udostępniane przez platformę nie modyfikują wartości przechowywanych w wartościach tras. Wszystkie wartości trasy analizowane z adresu URL są przechowywane jako ciągi. Na przykład ograniczenie próbuje przekonwertować wartość trasy na wartość zmiennoprzecinkową, ale przekonwertowana wartość jest używana tylko do sprawdzenia, czy można ją przekonwertować na wartość float zmiennoprzecinkową.

Wyrażenia regularne w ograniczeniach

Ostrzeżenie

W przypadku System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions 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ągu są interpretowane jako wyrażenia regularne.

W poniższym kodzie użyto wbudowanego ograniczenia wyrażenia regularnego:

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

Ta ASP.NET Core dodaje RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant do konstruktora wyrażenia regularnego. Zobacz RegexOptions , aby uzyskać opis tych elementów członkowskich.

Wyrażenia regularne używają ograniczników i tokenów podobnych do używanych przez routing i język C#. Tokeny wyrażeń regularnych muszą być znakami ucieczki. Aby użyć wyrażenia ^\d{3}-\d{2}-\d{4}$ regularnego w ograniczeniu w tekście, użyj jednej z następujących czynności:

  • 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łownego.

Aby uniknąć znaków ogranicznika parametrów routingu , , , , , podwaja znaki w wyrażeniu, na przykład {} , , , []{{}}[[]] . W poniższej tabeli przedstawiono wyrażenie regularne i jego wersję z ucieczką:

Wyrażenie regularne Wyrażenie regularne ze ucieczką
^\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ątku ciągu. Wyrażenia często kończą się $ znakiem i pasują do końca ciągu. Znaki ^ i zapewniają zgodność wyrażenia $ regularnego z wartością parametru całej trasy. Bez znaków i wyrażenie regularne dopasowuje dowolny podciąg 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 Ciąg Dopasowanie Komentarz
[a-z]{2} hello Tak Dopasowania podciągów
[a-z]{2} 123abc456 Tak Dopasowania podciągów
[a-z]{2} Mz Tak Dopasowuje wyrażenie
[a-z]{2} MZ Tak Wielkość liter nie jest zróżnicowa
^[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 wyrażenia regularne.

Aby ograniczyć parametr do znanego zestawu możliwych wartości, użyj wyrażenia regularnego. Na przykład dopasowuje {action:regex(^(list|get|create)$)} tylko wartość trasy do , lub actionlistgetcreate . Jeśli zostanie przekazany do słownika ograniczeń, ciąg ^(list|get|create)$ będzie równoważny. Ograniczenia przekazywane w słowniku ograniczeń, które nie pasują do jednego ze znanych ograniczeń, są również traktowane jako wyrażenia regularne. Ograniczenia przekazywane w ramach szablonu, które nie pasują do jednego ze znanych ograniczeń, nie są traktowane jako wyrażenia regularne.

Ograniczenia tras niestandardowych

Ograniczenia tras niestandardowych można utworzyć przez zaimplementowanie IRouteConstraint interfejsu. Interfejs IRouteConstraint zawiera wartość , która zwraca wartość , jeśli ograniczenie jest spełnione i w przeciwnym Matchtruefalse razie.

Niestandardowe ograniczenia trasy są rzadko potrzebne. Przed zaimplementowaniem niestandardowego ograniczenia trasy należy wziąć pod uwagę 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 obiektu , typ ograniczenia trasy musi być zarejestrowany w aplikacji w IRouteConstraintConstraintMap kontenerze usługi. A ConstraintMap to słownik, który mapuje klucze ograniczeń trasy na IRouteConstraint implementacje, które weryfikują te ograniczenia. Aplikację można ConstraintMap zaktualizować w ConstraintMap w ramach wywołania lub przez AddRouting skonfigurowanie RouteOptions bezpośrednio za pomocą . builder.Services.Configure<RouteOptions> Przykład:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Poprzednie 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 NoZeroesRouteConstraint funkcji uniemożliwia 0 stosowanie w parametrze 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 System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions przekierowywu limitu czasu.

Powyższy kod ma następujące działanie:

  • Zapobiega 0 w {id} segmencie trasy.
  • Pokazano, jak 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 zawierającego id0 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 poprzednich NoZeroesRouteConstraint podejściach:

  • Nie wymaga to ograniczenia niestandardowego.
  • Zwraca bardziej opisowy błąd, gdy parametr trasy zawiera wartość 0 .

Transformatory parametrów

Transformatory parametrów:

Na przykład transformator parametrów slugify niestandardowych we wzorcu trasy blog\{article:slugify} z generuje wartość Url.Action(new { article = "MyTestArticle" })blog\my-test-article .

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 w ConstraintMapConstraintMap:

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

W ASP.NET Core używane są transformatory parametrów do przekształcania wartości URI, gdzie punkt końcowy jest rozpoznawczy. Na przykład transformatory parametrów przekształcają wartości trasy używane do dopasowania area wartości , , i controlleractionpage :

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

W przypadku poprzedniego szablonu trasy akcja SubscriptionManagementController.GetAll jest do dopasowana z URI /subscription-management/get-all . Transformator parametrów nie zmienia wartości trasy używanych do generowania łącza. Na przykład dane Url.Action("GetAll", "SubscriptionManagement") wyjściowe to /subscription-management/get-all .

ASP.NET Core zawiera konwencje interfejsu API dotyczące używania transformatorów parametrów z wygenerowanych tras:

Informacje o generowaniu adresów URL

Ta sekcja zawiera odwołanie do algorytmu implementowany przez generowanie adresu URL. W praktyce najbardziej złożone przykłady generowania adresów URL używają kontrolerów lub Razor stron. Aby uzyskać dodatkowe informacje, zobacz routing w kontrolerach.

Proces generowania adresu URL rozpoczyna się od wywołania linku LinkGenerator.GetPathByAddress lub podobnej metody. Metoda jest dostarczana z adresem, zestawem wartości tras i opcjonalnie informacjami o bieżącym żądaniu z metody HttpContext .

Pierwszym krokiem jest użycie adresu w celu rozpoznania zestawu potencjalnych punktów końcowych przy użyciu obiektu , który odpowiada IEndpointAddressScheme<TAddress> typowi adresu.

Po odnalezioniu zestawu kandydatów przez schemat adresów punkty końcowe są uporządkowane i przetwarzane iteracyjnie do momentu, gdy operacja generowania adresu URL zakończy się pomyślnie. Generowanie adresu URL nie sprawdza niejednoznaczności, a pierwszym zwróconym wynikiem jest wynik końcowy.

Rozwiązywanie problemów z generowaniem adresów URL za pomocą rejestrowania

Pierwszym krokiem rozwiązywania problemów z generowaniem adresów URL jest ustawienie poziomu rejestrowania Microsoft.AspNetCore.Routing na TRACE . LinkGenerator Rejestruje wiele szczegółów dotyczących przetwarzania, co może być przydatne do rozwiązywania problemów.

Aby uzyskać szczegółowe informacje na temat generowania adresów URL, zobacz Informacje dotyczące 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 potencjalnych punktów końcowych.

Adresy to rozszerzalna koncepcja, która domyślnie ma dwie implementacje:

  • Przy użyciu nazwy punktu końcowego ( ) jako adresu:
    • Udostępnia funkcję podobną do nazwy trasy MVC.
    • Używa IEndpointNameMetadata typu metadanych.
    • Rozwiązuje 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 poza kontrolerami i Razor stronami.
  • Przy użyciu wartości trasy ( ) jako adresu:
    • Zapewnia funkcje podobne do kontrolerów i Razor generowania starszych adresów URL stron.
    • Bardzo złożone rozszerzanie i debugowanie.
    • Zawiera implementację używaną przez IUrlHelper elementy , pomocnicy tagów, pomocnicy HTML, wyniki akcji itp.

Schemat adresów ma na celu skojarzenie adresu z pasującymi punktami końcowymi według dowolnych kryteriów:

  • Schemat nazw punktów końcowych wykonuje podstawowe wyszukiwania w słowniku.
  • Schemat wartości trasy ma złożony najlepszy podzbiór algorytmu zestawu.

Wartości otoczenia i wartości jawne

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 zapewnienia przejrzystości dokumentacja odwołuje się do wartości tras przekazywanych do metod jako wartości jawnych.

W poniższym przykładzie pokazano wartości otoczenia i wartości jawne. Udostępnia on wartości otoczenia z bieżącego żądania i wartości jawne:

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 nie zawiera wartości otoczenia i wartości jawnych:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Poprzednia metoda zwraca /Home/Subscribe/17

Następujący kod w zwraca WidgetController : /Widget/Subscribe/17

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", null!, new { id = 17 })!;

Poniższy kod zawiera kontroler z wartości otoczenia w bieżącym żądaniu i wartości jawne:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Powyższy kod ma następujące działanie:

  • /Gadget/Edit/17 jest zwracany.
  • Url pobiera 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 wartości jawne:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Powyższy kod ustawia na url , gdy strona edycji zawiera /Edit/17Razor następującą dyrektywę strony:

@page "{id:int}"

Jeśli strona Edycja nie zawiera szablonu "{id:int}" trasy, to url/Edit?id=17 .

Zachowanie wzorca MVC dodaje warstwę złożoności oprócz IUrlHelper reguł opisanych tutaj:

  • IUrlHelper Zawsze dostarcza wartości trasy z bieżącego żądania jako wartości otoczenia.
  • IUrlHelper.Action zawsze kopiuje bieżące wartości i trasy jako wartości jawne, chyba że zostaną zastąpione przez controller dewelopera.
  • IUrlHelper.Page zawsze kopiuje bieżącą wartość trasy jako wartość jawną, chyba że zostanie zastąpiona.
  • IUrlHelper.Page Zawsze zastępuje bieżącą wartość handler trasy za pomocą wartości null jawnych, chyba że zostanie zastąpiony.

Użytkownicy są często zaskoczeni przez szczegóły zachowania wartości otoczenia, ponieważ MVC nie jest zgodne z własnymi regułami. Ze względów historycznych i zgodności niektóre wartości tras, takie jak , , , i mają własne actioncontroller zachowanie pagehandler specjalne.

Równoważna funkcjonalność zapewniana przez LinkGenerator.GetPathByAction funkcje i LinkGenerator.GetPathByPage duplikuje te anomalie programu IUrlHelper w celu zapewnienia zgodności.

Proces generowania adresu URL

Po znalezioniu zestawu potencjalnych punktów końcowych algorytm generowania adresów URL:

  • Przetwarza punkty końcowe iteracyjnie.
  • Zwraca pierwszy pomyślny wynik.

Pierwszy krok w tym procesie jest nazywany unieważnieniem wartości trasy. Unieważnianie wartości trasy to proces, za pomocą którego routing decyduje, które wartości tras z wartości otoczenia powinny być używane, a które należy zignorować. Każda wartość otoczenia jest rozważana i połączona z wartościami jawnymi lub ignorowana.

Najlepszym sposobem myślenia o roli wartości otoczenia jest próba zapisania wpisanych przez deweloperów aplikacji w niektórych typowych przypadkach. Tradycyjnie scenariusze, w których wartości otoczenia są przydatne, są powią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 taką samą metodą akcji nie trzeba określać wartości tras.
  • 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 , które zwracają są zwykle spowodowane przez nie zrozumienie IUrlHelpernull unieważnianie wartości trasy. Rozwiąż problemy z unieważnieniem wartości trasy, jawnie określając więcej wartości trasy, aby sprawdzić, czy to rozwiąże problem.

Unieważnianie wartości trasy działa przy założeniu, że schemat adresu URL aplikacji jest hierarchiczny, a hierarchia jest tworzymy od lewej do prawej. Rozważ podstawowy szablon trasy kontrolera, {controller}/{action}/{id?} aby uzyskać intuicyjne informacje o tym, jak to działa w praktyce. Zmiana wartości powoduje unieważnienie wszystkich wartości trasy wyświetlanych po prawej stronie. Odzwierciedla to założenie dotyczące hierarchii. Jeśli aplikacja ma wartość otoczenia id dla , a operacja określa inną wartość dla controller :

  • idnie zostanie użyty ponownie, ponieważ {controller} znajduje się po lewej stronie . {id?}

Niektóre przykłady demonstrujące tę zasadę:

  • Jeśli jawne wartości zawierają wartość dla , wartość otoczenia dla jest idid ignorowana. Wartości otoczenia dla controller i action mogą być używane.
  • Jeśli jawne wartości zawierają wartość , każda wartość otoczenia dla jest actionaction ignorowana. Można użyć wartości otoczenia controller dla . Jeśli jawna wartość dla jest inna niż wartość otoczenia dla , wartość nie actionaction będzie id używana. Jeśli jawna wartość dla action jest taka sama jak wartość otoczenia dla , można użyć wartości actionid .
  • Jeśli jawne wartości zawierają wartość , każda wartość otoczenia dla jest controllercontroller ignorowana. Jeśli jawna wartość dla jest inna niż wartość otoczenia dla , wartości i nie controllercontroller będą actionid używane. Jeśli jawna wartość dla jest taka sama jak wartość otoczenia dla , można użyć controllercontroller wartości i actionid .

Ten proces jest bardziej skomplikowany przez istnienie tras atrybutów i dedykowanych tras konwencjonalnych. Konwencjonalne trasy kontrolera, takie {controller}/{action}/{id?} jak określanie hierarchii przy użyciu parametrów trasy. W przypadku dedykowanych konwencjonalnych tras i atrybutów tras do kontrolerów i 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 strony Razor mają określone wymagane wartości, które umożliwiają unieważnienie wartości trasy.

Algorytm unieważnienia wartości trasy:

  • Wymagane nazwy wartości są łączone z parametrami trasy, a następnie przetwarzane od lewej do prawej.
  • Dla każdego parametru wartość otoczenia i wartość jawna są porównywane:
    • Jeśli wartość otoczenia i wartość jawna są takie same, proces jest kontynuowany.
    • Jeśli wartość otoczenia jest obecna, a wartość jawna nie jest, wartość otoczenia jest używana podczas generowania adresu URL.
    • Jeśli wartość otoczenia nie jest obecna, a wartość jawna to , odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
    • Jeśli wartość otoczenia i wartość jawna 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 łączony z wartościami domyślnymi parametru, które są dostarczane do ograniczeń. Jeśli wszystkie ograniczenia przejdą, operacja będzie kontynuowana.

Następnie akceptowane wartości mogą służyć do rozwijania szablonu trasy. Szablon trasy jest przetwarzany:

  • Od lewej do prawej.
  • Każdy parametr ma zastąpioną wartość akceptowaną.
  • Z następującymi specjalnymi przypadkami:
    • Jeśli w akceptowanych wartościach brakuje wartości, a parametr ma wartość domyślną, używana jest wartość domyślna.
    • Jeśli w akceptowanych wartościach brakuje wartości, a parametr jest opcjonalny, przetwarzanie jest kontynuowane.
    • Jeśli dowolny parametr trasy po prawej stronie brakującego opcjonalnego parametru ma wartość, operacja kończy się niepowodzeniem.
    • Ciągłe parametry o wartości domyślnej i parametry opcjonalne są zwijane 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 w przypadku korzystania z szablonu trasy {controller}/{action}/{id?} .

Wartości otoczenia Wartości jawne Wynik
controller = " Home " action = "About" /Home/About
controller = " Home " controller = "Order", action = "About" /Order/About
controller = " Home ", color = "Red" action = "About" /Home/About
controller = " Home " action = "About", color = "Red" /Home/About?color=Red

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 parametr culture trasy jest używany do lokalizacji. Chcemy, aby parametr culture był zawsze akceptowany jako wartość otoczenia. Jednak parametr culture nie jest akceptowany jako wartość otoczenia ze względu na sposób działania wymaganych wartości:

  • W szablonie trasy parametr trasy znajduje się po lewej stronie , więc zmiany w parametrze nie "default"culturecontrollercontroller unieważniają wartości culture .
  • W szablonie trasy parametr trasy jest traktowany jako po prawej stronie właściwości "blog" , która pojawia się w culturecontroller wymaganych wartościach.

Konfigurowanie metadanych punktu końcowego

Poniższe linki zawierają informacje na temat sposobu konfigurowania metadanych punktu końcowego:

Dopasowywanie hostów w trasach za pomocą requireHost

RequireHost stosuje ograniczenie do trasy, która wymaga określonego hosta. Parametr RequireHost lub RequireHost może być:

  • Host: www.domain.com , dopasowuje do dowolnego www.domain.com portu.
  • Host z symbolami wieloznacznymi: *.domain.com , dopasowuje , , lub na dowolnym www.domain.comsubdomain.domain.comwww.subdomain.domain.com porcie.
  • Port: *:5000 , dopasowuje port 5000 do dowolnego hosta.
  • Host i port: www.domain.com:5000 lub , dopasowuje host i *.domain.com:5000 port.

Można określić wiele parametrów przy użyciu RequireHost polecenia lub [Host] . Ograniczenie pasuje do hostów prawidłowych dla dowolnego z parametrów. Na przykład dopasowuje [Host("domain.com", "*.domain.com")] , domain.com i www.domain.comsubdomain.domain.com .

Poniższy kod używa RequireHost , 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 [Host] atrybutu na kontrolerze, aby wymagać dowolnego z określonych hostów:

[Host("contoso.com", "adventure-works.com")]
public class ProductsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Po zastosowaniu [Host] atrybutu 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ą, często podejrzewa się, że problemem jest routing. Podejrzewa się, że routing jest powodowany tym, że struktury, takie jak kontrolery i strony, zgłaszają ilość czasu spędzonego wewnątrz struktury Razor w komunikatach rejestrowania. Gdy istnieje znacząca różnica 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 przyczyną jest routing.

Routing jest testowany pod kątem wydajności przy użyciu tysięcy punktów końcowych. Jest mało prawdopodobne, że typowa aplikacja napotka problem z wydajnością, ponieważ jest zbyt duża. Najczęstszą główną przyczyną powolnego routingu jest zwykle źle zachowujące się niestandardowe oprogramowanie pośredniczące.

Poniższy przykład kodu przedstawia 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.");

Do routingu czasowego:

  • Przeplataj każde oprogramowanie pośredniczące kopią oprogramowania pośredniczącego chronometrażu pokazaną w poprzednim kodzie.
  • Dodaj unikatowy identyfikator, aby skorelować dane chronometrażu z kodem.

Jest to podstawowy sposób zawężenia opóźnienia, gdy jest ono znaczące, na przykład więcej niż 10ms . Time 2Odejmowanie Time 1 od raportów czasu spędzonego w oprogramowania UseRouting pośredniczącego.

Poniższy kod używa bardziej zwartego 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

Na poniższej liście przedstawiono pewne szczegółowe informacje o funkcjach routingu, które są stosunkowo kosztowne w porównaniu z podstawowymi szablonami tras:

  • Wyrażenia regularne: Można pisać wyrażenia regularne, 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 segmentu zwykłych ścieżek URL.
    • Spowoduje to przydzielenie o wiele większej liczby podciągów.
  • Synchroniczny dostęp do danych: wiele złożonych aplikacji ma dostęp do bazy danych w ramach routingu. Używaj punktów rozszerzalności, takich MatcherPolicy jak i , które są EndpointSelectorContext asynchroniczne.

Wskazówki dotyczące dużych tabel tras

Domyślnie program ASP.NET Core algorytm routingu, który handluje pamięcią na czas procesora CPU. Ma to dobry wpływ na to, że czas dopasowywania trasy zależy tylko od długości ścieżki do dopasowania, a nie od 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 liczba prefiksów zmiennych w trasach. Na przykład jeśli trasy mają parametry we wczesnych segmentach trasy, takie jak {parameter}/some/literal .

Jest mało prawdopodobne, że w aplikacji nie będzie miała miejsca sytuacja, w której jest to problem, chyba że:

  • Korzystając z tego wzorca, w aplikacji istnieje duża liczba tras.
  • W aplikacji istnieje duża liczba tras.

Jak ustalić, czy aplikacja ma problem z dużą tabelą tras

  • Istnieją dwa objawy, których należy szukać:
    • Uruchamianie aplikacji przy pierwszym żądaniu jest powolne.
      • Należy pamiętać, że jest to wymagane, ale nie wystarczające. Istnieje wiele innych problemów poza 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żym stopniu poprawią ten scenariusz:

  • Jeśli to możliwe, zastosuj ograniczenia trasy do parametrów, na przykład {parameter:int}{parameter:guid} , , {parameter:regex(\\d+)} itp.
    • Dzięki temu algorytm routingu może wewnętrznie zoptymalizować struktury używane do dopasowywania i znacząco zmniejszyć ilość używanej pamięci.
    • W większości przypadków wystarczy wrócić do akceptowalnego zachowania.
  • Zmień trasy, aby przenieść parametry do kolejnych segmentów w szablonie.
    • Zmniejsza to liczbę możliwych "ścieżek" do dopasowania do punktu końcowego w danej ścieżce.
  • Użyj trasy dynamicznej i dynamicznie przekieruj do kontrolera/strony.
    • Można to osiągnąć przy użyciu MapDynamicControllerRoute i MapDynamicPageRoute .

Wskazówki dla autorów bibliotek

Ta sekcja zawiera wskazówki dla autorów bibliotek, których tworzenie jest na podstawie routingu. Te szczegóły mają na celu zapewnienie, że deweloperzy aplikacji będą dobrze korzystać z bibliotek i platform, które rozszerzają routing.

Definiowanie punktów końcowych

Aby utworzyć platformę korzystającą z routingu do dopasowywania adresów URL, zacznij od zdefiniowania interfejsu użytkownika, które jest na podstawie UseEndpoints .

WYKONAJ kompilację na podstawie . Umożliwia to użytkownikom tworzenie struktury z innymi funkcjami ASP.NET Core bez nieporozumień. Każdy ASP.NET Core zawiera routing. Załóżmy, że routing jest obecny i znany użytkownikom.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

ZWRÓĆ zapieczętowany typ konkretny z wywołania , które implementuje IEndpointConventionBuilder . Większość metod Map... struktury jest zgodnie z tym wzorcem. IEndpointConventionBuilderInterfejs:

  • Umożliwia tworzenie metadanych.
  • Jest ona ukierunkowana na różne metody rozszerzeń.

Deklarowanie własnego typu umożliwia dodanie do konstruktora własnych funkcji specyficznych dla struktury. Można opakować konstruktora zadeklarowany w ramach struktury i przesyłać do niego wywołania.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

ROZWAŻ napisanie własnego . EndpointDataSource to podstawowy element 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 nie aktualizującego źródła danych.

NIE próbuj domyślnie rejestrować konta. Wymagaj od użytkowników zarejestrowania struktury w programie UseEndpoints . W przypadku routingu jest to, że domyślnie nic nie jest uwzględniane i UseEndpoints jest to miejsce do rejestrowania punktów końcowych.

Tworzenie oprogramowania pośredniczącego zintegrowanego z routingiem

ROZWAŻ zdefiniowanie typów metadanych jako interfejsu.

CZY umożliwia korzystanie z 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 wie, jak stosować atrybuty.

Deklarowanie typu metadanych jako interfejsu dodaje kolejną warstwę elastyczności:

  • Interfejsy są owalne.
  • Deweloperzy mogą deklarować 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 śledzenia tych wytycznych jest uniknięcie definiowania metadanych znaczników:

  • Nie należy po prostu szukać obecności typu metadanych.
  • Zdefiniuj właściwość w metadanych i sprawdź właściwość .

Kolekcja metadanych jest uporządkowana i obsługuje zastępowanie według priorytetu. W przypadku kontrolerów najbardziej szczegółowe są metadane metody akcji.

CZY oprogramowanie pośredniczące jest przydatne z routingiem i bez niego:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Jako przykład tych wytycznych rozważ oprogramowanie UseAuthorization pośredniczące. Oprogramowanie pośredniczące autoryzacji umożliwia podanie zasad rezerwowych. Zasady rezerwowe, jeśli zostały określone, dotyczą obu tych zasad:

  • Punkty końcowe bez określonych zasad.
  • Żądania, które nie pasują do punktu końcowego.

Dzięki temu oprogramowanie pośredniczące autoryzacji jest przydatne poza kontekstem routingu. Oprogramowanie pośredniczące autoryzacji może być używane do tradycyjnego programowania oprogramowania pośredniczącego.

Debugowanie diagnostyki

Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw Logging:LogLevel:Microsoft wartość Debug . W środowisku dewelopera ustaw poziom dziennika w ustawieniach 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 wykonywalnych punktów końcowych aplikacji. Punkty końcowe to jednostki wykonywalnego kodu obsługi żądań aplikacji. 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 może również generować adresy URL mapowe na punkty końcowe.

Aplikacje mogą konfigurować routing przy użyciu:

Ten dokument zawiera szczegółowe informacje o routingu ASP.NET Core poziomie. 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 systemie , wybierz wersję IRouter 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łączane przez określoną Startup klasę. Aby uruchomić określony przykład, zmodyfikuj program.cs, aby wywołać żądaną klasę.

Podstawy routingu

Wszystkie ASP.NET Core obejmują routing w wygenerowanym kodzie. Routing jest zarejestrowany w potoku oprogramowania pośredniczącego w programie .

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 usługi UseRouting i UseEndpoints :

  • UseRouting Dodaje dopasowywanie tras do potoku oprogramowania pośredniczącego. To oprogramowanie pośredniczące przejmuje 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.

Poprzedni przykład obejmuje pojedynczą trasę do punktu końcowego kodu przy użyciu metody MapGet:

  • Gdy żądanie HTTP GET jest wysyłane na główny adres / URL:
    • Wyświetlany delegat żądania jest wykonywany.
    • Hello World! jest zapisywany w odpowiedzi HTTP. Domyślnie główny adres URL / to https://localhost:5001/ .
  • Jeśli metoda żądania nie jest lub główny adres URL nie ma adresu , żadna trasa nie pasuje i zwracany jest GET/ błąd HTTP 404.

Punkt końcowy

Metoda MapGet służy do definiowania punktu MapGet. Punkt końcowy to coś, co może być:

  • 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 , i podobne MapPostMapGet delegatów żądań z systemem routingu. Dodatkowe metody mogą służyć do łączenia ASP.NET Core platformą z systemem routingu:

W poniższym przykładzie pokazano 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 jest /hello/{name:alpha}/hello/{name:alpha}. Służy do konfigurowania sposobu dopasowania punktu końcowego. W tym przypadku szablon jest taki jak:

  • Adres URL, taki jak /hello/Ryan
  • Dowolna ścieżka adresu URL, która rozpoczyna się od /hello/ ciągu , po której następuje sekwencja znaków alfabetycznych. :alpha stosuje ograniczenie trasy, które pasuje tylko do znaków alfabetycznych. Ograniczenia trasy zostały wyjaśnione w dalszej części tego dokumentu.

Drugi segment ścieżki adresu {name:alpha} URL:

System routingu punktów końcowych opisany w tym dokumencie jest nowy od 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 pokazano routing z kontrolami 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 do kodu przetłumaczone na języki inne niż angielski, daj nam znać w tym GitHub dyskusji.

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. Połączenie w RequireAuthorization łańcuch z tym wywołaniem dołącza zasady autoryzacji do punktu końcowego.

Wywoływanie UseAuthentication i UseAuthorization dodaje oprogramowanie pośredniczące uwierzytelniania i autoryzacji. Te oprogramowanie pośredniczące znajduje się między UseRouting i , dzięki czemu UseEndpoints mogą:

  • Zobacz, który punkt końcowy został wybrany przez program UseRouting .
  • Zastosuj zasady autoryzacji przed UseEndpoints wysłaniem ich do punktu końcowego.

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 kontroli /healthz kondycji, wykonywane jest 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 z routingiem.
  • Metadane mogą mieć dowolny typ .NET.

Pojęcia dotyczące routingu

System routingu opiera się na potoku oprogramowania pośredniczącego przez dodanie zaawansowanej koncepcji punktu końcowego. Punkty końcowe reprezentują jednostki funkcjonalności aplikacji, które różnią się od siebie pod względem routingu, autoryzacji i ASP.NET Core systemów aplikacji.

ASP.NET Core definicji punktu końcowego

Punkt ASP.NET Core to:

Poniższy kod pokazuje, jak pobrać i sprawdzić punkt końcowy zgodny z bieżącym żądaniem:

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 HttpContext . Jej właściwości można sprawdzić. Obiekty punktu końcowego są niezmienne i nie można ich modyfikować po utworzeniu. Najpopularniejszym typem punktu końcowego jest RouteEndpoint . RouteEndpoint Program zawiera informacje, które umożliwiają jego wybraną przez system routingu.

W poprzednim kodzie jest to aplikacja. Za pomocą polecenia konfiguruje się oprogramowanie pośredniczące .

Poniższy kod pokazuje, że w zależności od tego, gdzie jest wywoływana w app.Use potoku, może nie być punktu końcowego:

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

Ten poprzedni przykład dodaje Console.WriteLine instrukcje, które wyświetlają, czy punkt końcowy został wybrany. Dla przejrzystości przykład przypisuje nazwę wyświetlaną do podanego punktu / końcowego.

Uruchomienie tego kodu z adresem URL powoduje / wyświetlenie:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

Uruchomienie tego kodu z dowolnym innym adresem 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, zanim UseRouting zostanie wywołana.
  • Jeśli zostanie znalezione dopasowanie, punkt końcowy ma wartość niezerową między UseRouting i UseEndpoints .
  • Oprogramowanie UseEndpoints pośredniczące jest UseEndpoints gdy zostanie znalezione dopasowanie. Oprogramowanie pośredniczące terminalu jest zdefiniowane w dalszej części tego dokumentu.
  • Oprogramowanie pośredniczące po UseEndpoints wykonaniu jest wykonywane tylko wtedy, gdy nie zostanie znalezione dopasowanie.

Oprogramowanie UseRouting pośredniczące używa metody UseRouting aby dołączyć punkt końcowy do bieżącego kontekstu. Oprogramowanie pośredniczące można zastąpić niestandardową logiką i nadal korzystać UseRouting z zalet korzystania z punktów końcowych. Punkty końcowe są prymitywem niskiego poziomu, podobnie jak oprogramowanie pośredniczące, i nie są powiązane z implementacją routingu. Większość aplikacji nie musi być zastępowana UseRouting logiką niestandardową.

Oprogramowanie pośredniczące jest przeznaczone do współpracy z oprogramowaniem UseEndpointsUseRouting pośredniczącem. Podstawowa logika do wykonywania punktu końcowego nie jest skomplikowana. Użyj GetEndpoint metody , aby pobrać punkt końcowy, a następnie wywołaj 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 pokazano dwa ważne pojęcia:

  • Oprogramowanie pośredniczące może być uruchamiane przed UseRouting programem w celu zmodyfikowania danych, na których działa routing.
  • Oprogramowanie pośredniczące może działać między UseRouting i UseEndpoints w celu przetwarzania wyników routingu przed wykonaniem punktu końcowego.
    • Oprogramowanie pośredniczące, które działa między UseRouting i UseEndpoints :
      • Zwykle sprawdza metadane, aby zrozumieć punkty końcowe.
      • Często podejmuje decyzje dotyczące zabezpieczeń, tak jak to robią UseAuthorization i UseCors .
    • Kombinacja oprogramowania pośredniczącego i metadanych umożliwia konfigurowanie zasad dla każdego punktu końcowego.

Powyższy kod przedstawia przykład niestandardowego oprogramowania pośredniczącego, które obsługuje zasady dla niestandardowych punktów końcowych. Oprogramowanie pośredniczące zapisuje dziennik inspekcji dostępu do poufnych danych w konsoli programu . Oprogramowanie pośredniczące można skonfigurować do inspekcji punktu końcowego z metadanymi. W tym przykładzie pokazano wzorzec zgody, w którym inspekcje mają tylko punkty końcowe oznaczone jako poufne. Można zdefiniować tę logikę w odwrotnej kolejności, na przykład inspekcji wszystkich danych, które nie są oznaczone jako bezpieczne. System metadanych punktu końcowego jest elastyczny. Tę logikę można zaprojektować w dowolny sposób, który odpowiada przypadku użycia.

Powyższy przykładowy kod ma na celu zademonstrowanie podstawowych pojęć związanych z punktami końcowymi. Przykład nie jest przeznaczony do użytku produkcyjnego. Bardziej kompletna wersja oprogramowania pośredniczącego dziennika inspekcji będzie:

  • 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 zasad inspekcji są definiowane jako element ułatwiający korzystanie z platform opartych na AuditPolicyAttributeAttribute klasach, takich jak kontrolery i SignalR . W przypadku używania trasy do kodu:

  • Metadane są dołączane do interfejsu API konstruktora.
  • Struktury oparte na klasach zawierają wszystkie atrybuty odpowiedniej metody i klasy podczas tworzenia punktów końcowych.

Najlepsze rozwiązania dotyczące typów metadanych to 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 i routingu terminalu

Poniższy przykład kodu kontrastuje użycie 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!");
        });
    });
}

Wyświetlanym stylem oprogramowania pośredniczącego Approach 1: jest Approach 1:. Jest ono nazywane terminalowym oprogramowaniem pośredniczącem, ponieważ robi operację dopasowania:

  • Operacja dopasowywania w poprzednim przykładzie jest dla Path == "/" oprogramowania pośredniczącego i Path == "/Movie" routingu.
  • Gdy dopasowanie powiedzie się, wykonuje pewne funkcje i zwraca, zamiast wywołania next oprogramowania pośredniczącego.

Jest ono nazywane terminalowym oprogramowaniem pośredniczącem, ponieważ kończy wyszukiwanie, wykonuje pewne funkcje, a następnie zwraca.

Porównanie oprogramowania pośredniczącego i routingu terminalu:

  • Oba podejścia umożliwiają zakończenie potoku przetwarzania:
    • Oprogramowanie pośredniczące kończy potok, zwracając zamiast wywołania next .
    • Punkty końcowe są zawsze terminalne.
  • Oprogramowanie pośredniczące terminalu umożliwia ustawianie 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 do niej pasuje:
    • Kod dopasowania tras niestandardowych może być pełny i trudny do poprawnego napisania.
    • Routing zapewnia proste rozwiązania dla typowych aplikacji. Większość aplikacji nie wymaga kodu dopasowania tras niestandardowych.
  • Interfejs punktów końcowych z oprogramowaniem pośredniczącem, takim UseAuthorization jak i UseCors .
    • Korzystanie z terminalowego oprogramowania pośredniczącego z UseAuthorization lub UseCors wymaga ręcznej współpracy z systemem autoryzacji.

Punkt końcowy definiuje obie te definicje:

  • Delegat do przetwarzania żądań.
  • Kolekcja dowolnych metadanych. Metadane są używane do implementowanie poszczególnych kwestii 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.

Rozważ integrację z routingiem przed napisaniem oprogramowania pośredniczącego terminalu.

Istniejące oprogramowanie pośredniczące terminalu, które integruje się z usługą Map lub zwykle można je zamienić w punkt końcowy z uwzględnieniem routingu. MapHealthChecks demonstruje wzorzec oprogramowania router-ware:

  • Napisz metodę rozszerzenia w pliku IEndpointRouteBuilder .
  • Utwórz potok zagnieżdżonych oprogramowania pośredniczącego przy użyciu narzędzia CreateApplicationBuilder .
  • Dołącz oprogramowanie pośredniczące do nowego potoku. W tym przypadku jest to UseHealthChecks .
  • Build potok oprogramowania pośredniczącego RequestDelegate do .
  • Wywołaj Map i podaj nowy potok oprogramowania pośredniczącego.
  • Zwróć obiekt konstruktora dostarczony Map przez z metody rozszerzenia.

Poniższy kod pokazuje użycie funkcji 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. Zwrócenie obiektu konstruktora umożliwia deweloperowi aplikacji skonfigurowanie 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 rozszerzalności przy użyciu oprogramowania pośredniczącego terminalu. Wdrożenie własnej integracji z systemem autoryzacji dla każdego oprogramowania pośredniczącego jest problematyczne.

Dopasowywanie adresów URL

  • Czy proces, za pomocą którego routing pasuje do żądania przychodzącego do punktu końcowego,
  • Jest oparta na danych w ścieżce adresu URL i nagłówkach.
  • Można ją rozszerzyć w celu uwzględnienia dowolnych danych w żądaniu.

Gdy jest wykonywane oprogramowanie pośredniczące routingu, ustawia i przekieruje wartości do funkcji żądania w żądaniu Endpoint z EndpointHttpContext bieżącego żądania:

Oprogramowanie pośredniczące uruchomione po 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 interrogować kolekcję metadanych punktu końcowego w celu uzyskania 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 podjęta w systemie routingu.

Ostrzeżenie

Aby zapewnić zgodność z poprzednimi wersjami, podczas wykonywania delegata punktu końcowego kontrolera lub strony właściwości RazorRazor są ustawiane na odpowiednie wartości na podstawie wykonanego do tej pory przetwarzania żądania.

Typ RouteContext zostanie oznaczony jako przestarzały w przyszłej wersji:

  • RouteData.ValuesMigruj do HttpRequest.RouteValues .
  • RouteData.DataTokensPrzemigruj, RouteData.DataTokens z metadanych punktu końcowego.

Dopasowywanie adresów URL działa w konfigurowalnym zestawie faz. W każdej fazie dane wyjściowe są zestawem dopasowania. Zestaw dopasowania można jeszcze bardziej zawęzić do następnej fazy. 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 występują 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 nie powiodły się po zastosowaniu ograniczeń trasy.
  3. Przyjmuje poprzednią listę i usuwa dopasowania, które nie powiodą się w zestawie wystąpień matcherPolicy.
  4. Używa endpointselector do podjęcia ostatecznej decyzji z powyższej listy.

Priorytet listy punktów końcowych określa się według:

Wszystkie zgodne punkty końcowe są przetwarzane w każdej fazie, dopóki nie EndpointSelector zostanie osiągnięty. Jest EndpointSelector to faza końcowa. Wybiera punkt końcowy o najwyższym priorytecie spośród dopasowań jako najlepsze dopasowanie. Jeśli istnieją inne dopasowania o takim samym priorytecie jak 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 te adresy są zgodne ze ścieżką adresu URL /hello .
  • /hello jest bardziej szczegółowe i dlatego ma wyższy priorytet.

Ogólnie rzecz biorąc, pierwszeństwo trasy dobrze pasuje do rodzajów schematów adresów URL używanych w praktyce. Należy Order używać tylko wtedy, gdy jest to konieczne, aby uniknąć niejednoznaczności.

Ze względu na rodzaje rozszerzalności zapewniane przez routing nie jest możliwe, aby system routingu obliczał z wyprzedzeniem niejednoznaczne trasy. Rozważmy przykład, taki jak szablony tras /{message:alpha} i /{message:int} :

  • Ograniczenie alpha dopasowuje tylko znaki alfabetyczne.
  • Ograniczenie int dopasowuje tylko liczby.
  • Te szablony mają takie samo pierwszeństwo trasy, ale nie ma jednego adresu URL, z który są zgodne.
  • Jeśli system routingu zgłosił błąd niejednoznaczności podczas uruchamiania, zablokowałby ten 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 automatycznie przypisują wartość zamówienia do punktów końcowych na podstawie MapAreaRoute kolejności ich wywoływania. Symuluje to długotrwałe zachowanie kontrolerów bez systemu routingu zapewniającego takie same gwarancje jak starsze implementacje routingu.

W starszej implementacji routingu można zaimplementować rozszerzalność routingu, która jest zależna od kolejności przetwarzania tras. Routing punktów końcowych w ASP.NET Core 3.0 i nowszych:

  • Nie ma koncepcji 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 tego, jaka jest jego specyfika. Pierwszeństwo szablonu trasy:

  • Pozwala uniknąć konieczności dostosowania kolejności punktów końcowych w typowych przypadkach.
  • Próbuje dopasować typowe oczekiwania dotyczące zachowania routingu.

Rozważmy na przykład szablony /Products/List i /Products/{id} . Rozsądnie byłoby założyć, że jest /Products/List to lepsze dopasowanie niż dla ścieżki adresu URL /Products/{id}/Products/List . Działa to, ponieważ segment literału jest uznawany za lepszy /List priorytet niż segment parametrów /{id} .

Szczegóły dotyczące sposobu działania pierwszeństwa są powiązane ze zdefiniowanymi szablonami tras:

  • Szablony z większą liczby segmentów są uznawane za bardziej szczegółowe.
  • Segment z tekstem literału jest uznawany za bardziej konkretny niż segment parametrów.
  • Segment parametrów z ograniczeniem jest uznawany za bardziej konkretny niż jeden bez.
  • Segment złożony jest traktowany jako specyficzny jako segment parametrów z ograniczeniem.
  • Parametry catch-all są najmniej specyficzne. Aby uzyskać ważne informacje na temat tras catch-all, zobacz catch-all w szablonie trasy.

Zobacz kod źródłowy na GitHub, aby uzyskać odwołanie do dokładnych wartości.

Pojęcia dotyczące generowania adresów URL

Generowanie adresu URL:

  • Jest procesem, w którym 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 mają do nich dostęp.

Routing punktów końcowych obejmuje interfejs LinkGenerator API. LinkGenerator jest pojedynczą usługą dostępną z LinkGenerator. Interfejsu LinkGenerator API można używać poza kontekstem wykonywanego żądania. Mvc.IUrlHelper i scenariusze, które polegają na , takich jak Pomocnicy tagów, Pomocnicy HTML i Wyniki akcji , używają interfejsu API wewnętrznie, aby zapewnić możliwości generowania linków.

Generator linków jest pozyskany przez koncepcję schematów adresów i adresów. Schemat adresów to sposób określania punktów końcowych, które należy wziąć pod uwagę podczas generowania linków. Na przykład scenariusze nazwy trasy i wartości trasy są znane wielu użytkownikom z kontrolerów, a strony są Razor implementowane jako schemat adresów.

Generator linków może łączyć się z kontrolerami i Razor stronami za pośrednictwem następujących metod rozszerzeń:

Przeciążenia tych metod akceptują argumenty, które obejmują HttpContext . Te metody są funkcjonalnie równoważne metodom Url.Actioni Url.Page, ale oferują dodatkową elastyczność i opcje.

Metody są najbardziej podobne do metod i , ponieważ generują one wartość GetPath*Url.ActionUrl.Page URI zawierającą ścieżkę bezwzględną. Metody GetUri* zawsze generują bezwzględny URI zawierający schemat i hosta. Metody akceptujące generowanie HttpContext URI w kontekście wykonującego żądania. Wartości trasy otoczenia, ścieżka podstawowa adresu URL, schemat i host z żądania wykonującego są używane, chyba że zostaną zastąpione.

LinkGenerator Element jest wywoływany z adresem . Generowanie URI odbywa się w dwóch krokach:

  1. Adres jest powiązany z listą punktów końcowych, które pasują do adresu.
  2. Każdy punkt końcowy jest oceniany do momentu, aż zostanie znaleziony wzorzec trasy, który odpowiada RoutePattern dostarczonym wartościom. Wynikowe dane wyjściowe są łączone z innymi częściami URI dostarczonymi do generatora linków i zwracane.

Metody udostępniane przez LinkGenerator usługę obsługują standardowe możliwości generowania linków dla dowolnego typu adresu. Najbardziej wygodnym sposobem korzystania z generatora linków są metody rozszerzenia, które wykonują operacje dla określonego typu adresu:

Extension, metoda Opis
GetPathByAddress Generuje URI ze ścieżką bezwzględną na podstawie podanych wartości.
GetUriByAddress Generuje bezwzględny URI na podstawie podanych wartości.

Ostrzeżenie

Zwróć uwagę na następujące implikacje metod LinkGenerator wywoływania:

  • Metody rozszerzeń należy stosować ostrożnie w konfiguracji aplikacji, która nie weryfikuje GetUri*Host nagłówka żądań przychodzących. Jeśli nagłówek żądań przychodzących nie zostanie zweryfikowany, niezaufane dane wejściowe żądania mogą być wysyłane z powrotem do klienta w interfejsach URIs w Host widoku lub na stronie. Zalecamy, aby wszystkie aplikacje produkcyjne skonfigurowały swój serwer tak, aby Host sprawdzał poprawność nagłówka pod względem znanych prawidłowych wartości.

  • Należy LinkGenerator zachować ostrożność w oprogramowanie pośredniczące w połączeniu z lub MapMapWhen . Map* Zmienia ścieżkę podstawową wykonującego żądania, co wpływa na dane wyjściowe generowania łącza. Wszystkie LinkGenerator interfejsy API umożliwiają określenie ścieżki podstawowej. Określ pustą ścieżkę podstawową, aby Map* cofnąć wpływ na generowanie linków.

Przykład oprogramowania pośredniczącego

W poniższym przykładzie oprogramowanie pośredniczące używa interfejsu API do utworzenia linku do metody akcji, która LinkGenerator wyświetla listę produktów ze sklepu. Użycie generatora linków przez wstrzyknięcie go do klasy i wywołanie jest GenerateLink 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.");
    }
}

Odwołanie do szablonu trasy

Tokeny w {} ramach definiują parametry trasy, które są powiązane, jeśli trasa jest do siebie dopasowana. W segmencie trasy można zdefiniować więcej niż jeden parametr trasy, ale parametry trasy muszą być oddzielone 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 ) i separator ścieżki muszą być zgodne {id}/ z tekstem w adresie URL. W dopasowywaniu tekstu nie jest roz rozwrażliwa na litera i opiera się na zdekodowanych reprezentacjach ścieżki adresu URL. Aby dopasować ogranicznik parametru trasy literału { lub } , ogranicznik ucieczki przez powtórzenie znaku. Na przykład {{ lub }} .

Gwiazdka * lub podwójna gwiazdka: **

  • Może służyć jako prefiks do parametru trasy w celu powiązania z pozostałą część identyfikatora URI.
  • Są nazywane parametrami catch-all. Na blog/{**slug} przykład:
    • Pasuje do dowolnego URI, który rozpoczyna się od /blog i ma dowolną wartość po nim.
    • Następująca wartość /blog jest przypisywana do /blog

Ostrzeżenie

Parametr catch-all może niepoprawnie dopasować trasy z powodu usterki w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:

  • Trasa catch-all, na przykład {**slug}"
  • Trasa catch-all nie pasuje do żądań, które powinny być zgodne.
  • Usunięcie innych tras sprawia, że trasa catch-all zaczyna działać.

Zobacz GitHub błędów 18677 i 16579, aby uzyskać przykładowe przypadki, w których ta usterka dotyczy.

Poprawka dla tej usterki jest zawarta w zestawie SDK platformy .NET Core 3.1.301 i nowszych. 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 również mogą być zgodne z pustym ciągiem.

Parametr catch-all unika odpowiednie znaki, gdy trasa jest używana do generowania adresu URL, w tym znaków separatora / ścieżki. Na przykład trasa z foo/{*path} wartościami trasy { path = "my/path" } generuje wartość foo/my%2Fpath . Zwróć uwagę na ukośnik ucieczki. Aby znaki separatora ścieżki rundy, użyj ** prefiksu parametru trasy. Trasa z foo/{**path} generuje { path = "my/path" }foo/my/path .

Wzorce adresów URL, które próbują przechwycić nazwę pliku z opcjonalnym rozszerzeniem pliku, mają dodatkowe zagadnienia. Rozważmy na przykład szablon files/{filename}.{ext?} . Gdy wartości dla wartości filename i ext istnieją, obie wartości są wypełniane. Jeśli w adresie URL istnieje tylko wartość filename , trasa jest taka, ponieważ końcowe wartości . są opcjonalne. Następujące adresy URL są zgodne z tą trasą:

  • /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 jako wartość Home domyślną dla 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 wartościami opcjonalnymi i domyślnymi parametrami trasy jest:

  • 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 : nazwy ograniczenia i po nazwie parametru trasy określa wbudowane ograniczenie parametru trasy. Jeśli ograniczenie wymaga argumentów, są one ujęte w nawiasy (...) po nazwie ograniczenia. Wiele ograniczeń w tekście można określić, dołączając inną nazwę ograniczenia i .

Nazwa ograniczenia i argumenty są przekazywane do usługi w celu utworzenia wystąpienia klasy do IInlineConstraintResolverIRouteConstraint użycia w przetwarzaniu adresu URL. Na przykład szablon trasy określa blog/{article:minlength(10)} ograniczenie minlength za pomocą argumentu 10 . Aby uzyskać więcej informacji na temat ograniczeń trasy i listy ograniczeń dostarczanych przez platformę, zobacz sekcję Odwołania do ograniczeń trasy.

Parametry trasy mogą również mieć transformatory parametrów. Transformatory parametrów przekształcają wartość parametru podczas generowania linków i dopasowywania akcji i stron do adresów URL. Podobnie jak ograniczenia, transformatory parametrów mogą być dodawane w tekście do parametru trasy przez dodanie nazwy transformatora i : po nazwie parametru trasy. Na przykład szablon trasy określa blog/{article:slugify}slugify transformatora. Aby uzyskać więcej informacji na temat transformatorów parametrów, zobacz sekcję Informacje dotyczące transformatora parametrów.

W poniższej tabeli przedstawiono przykładowe szablony tras i ich zachowanie:

Szablon trasy Przykładowy pasujący URI URI żądania...
hello /hello Pasuje tylko do pojedynczej ścieżki /hello .
{Page=Home} / Dopasowuje i ustawia Page na Home .
{Page=Home} /Contact Dopasowuje i ustawia Page na Contact .
{controller}/{action}/{id?} /Products/List Mapy do kontrolera Products i List akcji.
{controller}/{action}/{id?} /Products/Details/123 Mapy do kontrolera Products i akcji z Detailsid ustawioną na 123.
{controller=Home}/{action=Index}/{id?} / Mapy do kontrolera Home i Index metody. Parametr id jest ignorowany.
{controller=Home}/{action=Index}/{id?} /Products Mapy do kontrolera Products i Index metody. Parametr id jest ignorowany.

Użycie szablonu jest zazwyczaj najprostszym podejściem do routingu. Ograniczenia i wartości domyślne można również określić poza szablonem trasy.

Segmenty złożone

Złożone segmenty są przetwarzane przez dopasowanie ograniczników literału od prawej do lewej w sposób niezachłanny. Na przykład [Route("/a{b}c{d}")] jest to segment złożony. Złożone segmenty działają w określony sposób, który musi być zrozumiały, aby można było z nich korzystać pomyślnie. W przykładzie w tej sekcji pokazano, dlaczego złożone segmenty działają dobrze tylko wtedy, gdy tekst ogranicznika nie pojawia się wewnątrz wartości parametrów. W przypadku bardziej złożonych przypadków wymagane jest użycie wyrażenia regularnego, a następnie ręczne wyodrębnienie wartości.

Ostrzeżenie

W przypadku System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions przekierowywu limitu czasu.

To jest podsumowanie kroków, które routing wykonuje za pomocą szablonu /a{b}c{d} i ścieżki adresu URL /abcd . Funkcja | służy do wizualizacji sposobu działania algorytmu:

  • Pierwszy literał, od prawej do lewej, to c . Tak /abcd więc jest wyszukiwany od prawej strony i znajduje /ab|c|d .
  • Wszystko po prawej stronie ( d ) jest teraz dopasowane do parametru trasy {d} .
  • Następny literał, od prawej do lewej, to a . Tak /ab|c|d więc wyszukiwanie rozpoczyna się od miejsca, w którym zostało to pozostawione, a następnie jest a wyszukiwane /|a|b|c|d .
  • Wartość po prawej stronie ( b ) jest teraz do dopasowana do parametru trasy {b} .
  • Nie ma pozostałego tekstu i nie ma pozostałego szablonu trasy, więc jest to dopasowanie.

Oto przykład przypadku negatywnego przy użyciu tego samego szablonu /a{b}c{d} i ścieżki adresu URL /aabcd . Funkcja | służy do wizualizacji sposobu działania algorytmu. Ten przypadek nie jest dopasowaniem, co jest wyjaśnione za pomocą tego samego algorytmu:

  • Pierwszy literał, od prawej do lewej, to c . Tak /aabcd więc jest wyszukiwany z prawej strony i znajduje /aab|c|d .
  • Wszystko po prawej stronie ( d ) jest teraz dopasowane do parametru trasy {d} .
  • Następny literał, od prawej do lewej, to a . Wyszukiwanie /aab|c|d rozpoczyna się od miejsca, w którym ją wyłączyliśmy, a następnie jest a wyszukiwane /a|a|b|c|d .
  • Wartość po prawej stronie ( b ) jest teraz do dopasowana do parametru trasy {b} .
  • W tym momencie jest pozostały tekst , ale w algorytmie zabrakło szablonu trasy do analizy, więc nie a jest to dopasowanie.

Ponieważ algorytm dopasowywania nie jest zachłanny:

  • Dopasowuje najmniejszą ilość tekstu możliwą w każdym kroku.
  • Każdy przypadek, w którym wartość ogranicznika pojawia się wewnątrz wartości parametrów, powoduje niepasującą wartość.

Wyrażenia regularne zapewniają znacznie większą kontrolę nad ich zachowaniem dopasowania.

Zachłanne dopasowywanie, wiadomo również, że dopasowanie z opóźnieniempasuje do największego możliwego ciągu. Niezachłanny dopasowuje najmniejszą możliwą wartość ciągu.

Odwołanie do ograniczenia trasy

Ograniczenia trasy są wykonywane, gdy wystąpiło dopasowanie do przychodzącego adresu URL, a ścieżka adresu URL jest tokenizowana na wartości trasy. Ograniczenia trasy zazwyczaj sprawdzają wartość trasy skojarzoną za pośrednictwem szablonu trasy i decyzje dotyczące tego, czy wartość jest dopuszczalna, czy jest to prawda, czy fałsz. Niektóre ograniczenia trasy używają danych spoza wartości trasy, aby rozważyć, czy żądanie może być kierowane. Na przykład może HttpMethodRouteConstraint akceptować lub odrzucać żądanie na podstawie jego czasownika HTTP. Ograniczenia są używane podczas generowania żądań routingu i linków.

Ostrzeżenie

Nie używaj ograniczeń do weryfikacji danych wejściowych. Jeśli ograniczenia są używane do weryfikacji danych wejściowych, nieprawidłowe dane wejściowe skutkują odpowiedzią 404 Nie znaleziono. Nieprawidłowe dane wejściowe powinny dawać 400 nieprawidłowe żądanie z odpowiednim komunikatem o błędzie. Ograniczenia trasy są używane do ujednoznania podobnych tras, a nie do weryfikowania 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 Dopasowuje dowolną liczbę całkowitą
bool {active:bool} true, FALSE Dopasowuje true lub false . Bez uwzględniania wielkości liter
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Dopasowuje DateTime prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
decimal {price:decimal} 49.99, -1,000.01 Dopasowuje decimal prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
double {weight:double} 1.234, -1,001.01e8 Dopasowuje double prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
float {weight:float} 1.234, -1,001.01e8 Dopasowuje float prawidłową wartość w niezmiennej kulturze. Zobacz poprzednie ostrzeżenie.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Dopasowuje prawidłową Guid wartość
long {ticks:long} 123456789, -123456789 Dopasowuje prawidłową long wartość
minlength(value) {username:minlength(4)} Rick Ciąg musi zawierać 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ć 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 może zawierać 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 może być większa niż 120
alpha {name:alpha} Rick Ciąg musi składać się z co najmniej jednego znaków alfabetycznych i bez uwzględniania a-z liter.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 Ciąg musi być zgodne z wyrażeniem regularnym. Zobacz porady dotyczące definiowania wyrażenia regularnego.
required {name:required} Rick Służy do wymuszania, że wartość bez parametru jest obecna podczas generowania adresu URL

Ostrzeżenie

W przypadku System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions limitu czasu.

Ograniczenia rozdzielane wieloma dwukropkami można zastosować do pojedynczego 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 CLR int lub DateTime . Te ograniczenia zakładają, że adres URL nie jest do lokalizacji. Ograniczenia tras udostępniane przez platformę nie modyfikują wartości przechowywanych w wartościach tras. Wszystkie wartości trasy analizowane z adresu URL są przechowywane jako ciągi. Na przykład ograniczenie próbuje przekonwertować wartość trasy na wartość zmiennoprzecinkową, ale przekonwertowana wartość jest używana tylko do sprawdzenia, czy można ją przekonwertować na wartość float zmiennoprzecinkową.

Wyrażenia regularne w ograniczeniach

Ostrzeżenie

W przypadku System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions 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ągu są interpretowane jako wyrażenia regularne.

W poniższym kodzie użyto wbudowanego ograniczenia wyrażenia regularnego:

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

Ta ASP.NET Core dodaje RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant do konstruktora wyrażenia regularnego. Zobacz RegexOptions , aby uzyskać opis tych elementów członkowskich.

Wyrażenia regularne używają ograniczników i tokenów podobnych do używanych przez routing i język C#. Tokeny wyrażeń regularnych muszą być znakami ucieczki. Aby użyć wyrażenia ^\d{3}-\d{2}-\d{4}$ regularnego w ograniczeniu w tekście, użyj jednej z następujących czynności:

  • 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łownego.

Aby uniknąć znaków ogranicznika parametrów routingu , , , , , podwaja znaki w wyrażeniu, na przykład {} , , , []{{}}[[]] . W poniższej tabeli przedstawiono wyrażenie regularne i jego wersję z ucieczką:

Wyrażenie regularne Wyrażenie regularne ze ucieczką
^\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ątku ciągu. Wyrażenia często kończą się $ znakiem i pasują do końca ciągu. Znaki ^ i zapewniają zgodność wyrażenia $ regularnego z wartością parametru całej trasy. Bez znaków i wyrażenie regularne dopasowuje dowolny podciąg 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 Ciąg Dopasowanie Komentarz
[a-z]{2} hello Tak Dopasowania podciągów
[a-z]{2} 123abc456 Tak Dopasowania podciągów
[a-z]{2} Mz Tak Dopasowuje wyrażenie
[a-z]{2} MZ Tak Wielkość liter nie jest zróżnicowa
^[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 wyrażenia regularne.

Aby ograniczyć parametr do znanego zestawu możliwych wartości, użyj wyrażenia regularnego. Na przykład dopasowuje {action:regex(^(list|get|create)$)} tylko wartość trasy do , lub actionlistgetcreate . Jeśli zostanie przekazany do słownika ograniczeń, ciąg ^(list|get|create)$ będzie równoważny. Ograniczenia przekazywane w słowniku ograniczeń, które nie pasują do jednego ze znanych ograniczeń, są również traktowane jako wyrażenia regularne. Ograniczenia przekazywane w ramach szablonu, które nie pasują do jednego ze znanych ograniczeń, nie są traktowane jako wyrażenia regularne.

Ograniczenia tras niestandardowych

Ograniczenia tras niestandardowych można utworzyć przez zaimplementowanie IRouteConstraint interfejsu. Interfejs IRouteConstraint zawiera wartość , która zwraca wartość , jeśli ograniczenie jest spełnione i w przeciwnym Matchtruefalse razie.

Niestandardowe ograniczenia trasy są rzadko potrzebne. Przed zaimplementowaniem niestandardowego ograniczenia trasy należy wziąć pod uwagę 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 obiektu , typ ograniczenia trasy musi być zarejestrowany w aplikacji w IRouteConstraintConstraintMap kontenerze usługi. A ConstraintMap to słownik, który mapuje klucze ograniczeń trasy na IRouteConstraint implementacje, które weryfikują te ograniczenia. Aplikacje można ConstraintMap aktualizować w Startup.ConfigureServices ramach ConstraintMap lub przez skonfigurowanie RouteOptions bezpośrednio za pomocą . services.Configure<RouteOptions> Przykład:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddRouting(options =>
    {
        options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
    });
}

Poprzednie 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 Rick.Docs.Samples.RouteInfo NuGet wyświetla informacje o trasie.

Implementacja MyCustomConstraint funkcji zapobiega 0 zastosowaniu do parametru 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 System.Text.RegularExpressions używania metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może wprowadzić dane wejściowe RegularExpressions w celu spowodowania ataku typu RegularExpressions ASP.NET Core interfejsów API struktury, które używają RegularExpressions przekierowywu limitu czasu.

Powyższy kod ma następujące działanie:

  • Zapobiega 0 w {id} segmencie trasy.
  • Pokazano, jak 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 zawierającego id0 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 poprzednich MyCustomConstraint podejściach:

  • Nie wymaga to ograniczenia niestandardowego.
  • Zwraca bardziej opisowy błąd, gdy parametr trasy zawiera wartość 0 .

Odwołanie do transformatora parametrów

Transformatory parametrów:

Na przykład transformator parametrów slugify niestandardowych we wzorcu trasy blog\{article:slugify} z generuje wartość Url.Action(new { article = "MyTestArticle" })blog\my-test-article .

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 funkcji w ConstraintMap parametrze Startup.ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddRouting(options =>
    {
        options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
    });
}

W ASP.NET Core używane są transformatory parametrów do przekształcania wartości URI, gdzie punkt końcowy jest rozpoznawczy. Na przykład transformatory parametrów przekształcają wartości trasy używane do dopasowania area wartości , , i controlleractionpage .

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

W przypadku poprzedniego szablonu trasy akcja SubscriptionManagementController.GetAll jest do dopasowana z URI /subscription-management/get-all . Transformator parametrów nie zmienia wartości trasy używanych do generowania łącza. Na przykład dane Url.Action("GetAll", "SubscriptionManagement") wyjściowe to /subscription-management/get-all .

ASP.NET Core zawiera konwencje interfejsu API dotyczące używania transformatorów parametrów z wygenerowanych tras:

Informacje o generowaniu adresów URL

Ta sekcja zawiera odwołanie do algorytmu implementowany przez generowanie adresu URL. W praktyce najbardziej złożone przykłady generowania adresów URL używają kontrolerów lub Razor stron. Aby uzyskać dodatkowe informacje, zobacz routing w kontrolerach.

Proces generowania adresu URL rozpoczyna się od wywołania linku LinkGenerator.GetPathByAddress lub podobnej metody. Metoda jest dostarczana z adresem, zestawem wartości tras i opcjonalnie informacjami o bieżącym żądaniu z metody HttpContext .

Pierwszym krokiem jest użycie adresu w celu rozpoznania zestawu potencjalnych punktów końcowych przy użyciu obiektu , który odpowiada IEndpointAddressScheme<TAddress> typowi adresu.

Po odnalezioniu zestawu kandydatów przez schemat adresów punkty końcowe są uporządkowane i przetwarzane iteracyjnie do momentu, gdy operacja generowania adresu URL zakończy się pomyślnie. Generowanie adresu URL nie sprawdza niejednoznaczności, a pierwszym zwróconym wynikiem jest wynik końcowy.

Rozwiązywanie problemów z generowaniem adresów URL za pomocą rejestrowania

Pierwszym krokiem rozwiązywania problemów z generowaniem adresów URL jest ustawienie poziomu rejestrowania Microsoft.AspNetCore.Routing na TRACE . LinkGenerator Rejestruje wiele szczegółów dotyczących przetwarzania, co może być przydatne do rozwiązywania problemów.

Aby uzyskać szczegółowe informacje na temat generowania adresów URL, zobacz Informacje dotyczące 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 potencjalnych punktów końcowych.

Adresy to rozszerzalna koncepcja, która domyślnie ma dwie implementacje:

  • Przy użyciu nazwy punktu końcowego ( ) jako adresu:
    • Udostępnia funkcję podobną do nazwy trasy MVC.
    • Używa IEndpointNameMetadata typu metadanych.
    • Rozwiązuje 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 poza kontrolerami i Razor stronami.
  • Przy użyciu wartości trasy ( ) jako adresu:
    • Zapewnia funkcje podobne do kontrolerów i Razor generowania starszych adresów URL stron.
    • Bardzo złożone rozszerzanie i debugowanie.
    • Zawiera implementację używaną przez IUrlHelper elementy , pomocnicy tagów, pomocnicy HTML, wyniki akcji itp.

Schemat adresów ma na celu skojarzenie adresu z pasującymi punktami końcowymi według dowolnych kryteriów:

  • Schemat nazw punktów końcowych wykonuje podstawowe wyszukiwania w słowniku.
  • Schemat wartości trasy ma złożony najlepszy podzbiór algorytmu zestawu.

Wartości otoczenia i wartości jawne

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 zapewnienia przejrzystości dokumentacja odwołuje się do wartości tras przekazywanych do metod jako wartości jawnych.

W poniższym przykładzie pokazano wartości otoczenia i wartości jawne. Udostępnia wartości otoczenia z bieżącego żądania i wartości jawne: { 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 wartości jawnych: { 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

Następujący kod w zwraca WidgetController : /Widget/Subscribe/17

var url = _linkGenerator.GetPathByAction("Subscribe", null,
                                         new { id = 17, });

Poniższy kod zawiera kontroler z wartości otoczenia w bieżącym żądaniu i wartości jawne: { 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 ma następujące działanie:

  • /Gadget/Edit/17 jest zwracany.
  • Url pobiera 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 wartości jawne: { 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 na url , gdy strona edycji zawiera /Edit/17Razor następującą dyrektywę strony:

@page "{id:int}"

Jeśli strona Edycja nie zawiera szablonu "{id:int}" trasy, to url/Edit?id=17 .

Zachowanie wzorca MVC dodaje warstwę złożoności oprócz IUrlHelper reguł opisanych tutaj:

  • IUrlHelper Zawsze dostarcza wartości trasy z bieżącego żądania jako wartości otoczenia.
  • IUrlHelper.Action zawsze kopiuje bieżące wartości i trasy jako wartości jawne, chyba że zostaną zastąpione przez controller dewelopera.
  • IUrlHelper.Page zawsze kopiuje bieżącą wartość trasy jako wartość jawną, chyba że zostanie zastąpiona.
  • IUrlHelper.Page Zawsze zastępuje bieżącą wartość handler trasy za pomocą wartości null jawnych, chyba że zostanie zastąpiony.

Użytkownicy są często zaskoczeni przez szczegóły zachowania wartości otoczenia, ponieważ MVC nie jest zgodne z własnymi regułami. Ze względów historycznych i zgodności niektóre wartości tras, takie jak , , , i mają własne actioncontroller zachowanie pagehandler specjalne.

Równoważna funkcjonalność zapewniana przez LinkGenerator.GetPathByAction funkcje i LinkGenerator.GetPathByPage duplikuje te anomalie programu IUrlHelper w celu zapewnienia zgodności.

Proces generowania adresu URL

Po znalezioniu zestawu potencjalnych punktów końcowych algorytm generowania adresów URL:

  • Przetwarza punkty końcowe iteracyjnie.
  • Zwraca pierwszy pomyślny wynik.

Pierwszy krok w tym procesie jest nazywany unieważnieniem wartości trasy. Unieważnianie wartości trasy to proces, za pomocą którego routing decyduje, które wartości tras z wartości otoczenia powinny być używane, a które należy zignorować. Każda wartość otoczenia jest rozważana i połączona z wartościami jawnymi lub ignorowana.

Najlepszym sposobem myślenia o roli wartości otoczenia jest próba zapisania wpisanych przez deweloperów aplikacji w niektórych typowych przypadkach. Tradycyjnie scenariusze, w których wartości otoczenia są przydatne, są powią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 taką samą metodą akcji nie trzeba określać wartości tras.
  • 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 , które zwracają są zwykle spowodowane przez nie zrozumienie IUrlHelpernull unieważnianie wartości trasy. Rozwiąż problemy z unieważnieniem wartości trasy, jawnie określając więcej wartości trasy, aby sprawdzić, czy to rozwiąże problem.

Unieważnianie wartości trasy działa przy założeniu, że schemat adresu URL aplikacji jest hierarchiczny, a hierarchia jest tworzymy od lewej do prawej. Rozważ podstawowy szablon trasy kontrolera, {controller}/{action}/{id?} aby uzyskać intuicyjne informacje o tym, jak to działa w praktyce. Zmiana wartości powoduje unieważnienie wszystkich wartości trasy wyświetlanych po prawej stronie. Odzwierciedla to założenie dotyczące hierarchii. Jeśli aplikacja ma wartość otoczenia id dla , a operacja określa inną wartość dla controller :

  • idnie zostanie użyty ponownie, ponieważ {controller} znajduje się po lewej stronie . {id?}

Niektóre przykłady demonstrujące tę zasadę:

  • Jeśli jawne wartości zawierają wartość dla , wartość otoczenia dla jest idid ignorowana. Wartości otoczenia dla controller i action mogą być używane.
  • Jeśli jawne wartości zawierają wartość , każda wartość otoczenia dla jest actionaction ignorowana. Można użyć wartości otoczenia controller dla . Jeśli jawna wartość dla jest inna niż wartość otoczenia dla , wartość nie actionaction będzie id używana. Jeśli jawna wartość dla action jest taka sama jak wartość otoczenia dla , można użyć wartości actionid .
  • Jeśli jawne wartości zawierają wartość , każda wartość otoczenia dla jest controllercontroller ignorowana. Jeśli jawna wartość dla jest inna niż wartość otoczenia dla , wartości i nie controllercontroller będą actionid używane. Jeśli jawna wartość dla jest taka sama jak wartość otoczenia dla , można użyć controllercontroller wartości i actionid .

Ten proces jest bardziej skomplikowany przez istnienie tras atrybutów i dedykowanych tras konwencjonalnych. Konwencjonalne trasy kontrolera, takie {controller}/{action}/{id?} jak określanie hierarchii przy użyciu parametrów trasy. W przypadku dedykowanych konwencjonalnych tras i atrybutów tras do kontrolerów i 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 strony Razor mają określone wymagane wartości, które umożliwiają unieważnienie wartości trasy.

Algorytm unieważnienia wartości trasy:

  • Wymagane nazwy wartości są łączone z parametrami trasy, a następnie przetwarzane od lewej do prawej.
  • Dla każdego parametru wartość otoczenia i wartość jawna są porównywane:
    • Jeśli wartość otoczenia i wartość jawna są takie same, proces jest kontynuowany.
    • Jeśli wartość otoczenia jest obecna, a wartość jawna nie jest, wartość otoczenia jest używana podczas generowania adresu URL.
    • Jeśli wartość otoczenia nie jest obecna, a wartość jawna to , odrzuć wartość otoczenia i wszystkie kolejne wartości otoczenia.
    • Jeśli wartość otoczenia i wartość jawna 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 łączony z wartościami domyślnymi parametru, które są dostarczane do ograniczeń. Jeśli wszystkie ograniczenia przejdą, operacja będzie kontynuowana.

Następnie akceptowane wartości mogą służyć do rozwijania szablonu trasy. Szablon trasy jest przetwarzany:

  • Od lewej do prawej.
  • Każdy parametr ma zastąpioną wartość akceptowaną.
  • Z następującymi specjalnymi przypadkami:
    • Jeśli w akceptowanych wartościach brakuje wartości, a parametr ma wartość domyślną, używana jest wartość domyślna.
    • Jeśli w akceptowanych wartościach brakuje wartości, a parametr jest opcjonalny, przetwarzanie jest kontynuowane.
    • Jeśli dowolny parametr trasy po prawej stronie brakującego opcjonalnego parametru ma wartość, operacja kończy się niepowodzeniem.
    • Ciągłe parametry o wartości domyślnej i parametry opcjonalne są zwijane 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 w przypadku korzystania z szablonu trasy {controller}/{action}/{id?} .

Wartości otoczenia Wartości jawne Wynik
controller = " Home " action = "About" /Home/About
controller = " Home " controller = "Order", action = "About" /Order/About
controller = " Home ", color = "Red" action = "About" /Home/About
controller = " Home " action = "About", color = "Red" /Home/About?color=Red

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 w przypadku generowania adresów URL. Zespół ASP.NET Core planuje dodanie funkcji w celu rozwiązania 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 parametr culture trasy jest używany do lokalizacji. Chce, aby parametr culture był zawsze akceptowany jako wartość otoczenia. Jednak parametr culture nie jest akceptowany jako wartość otoczenia ze względu na sposób działania wymaganych wartości:

  • W "default" szablonie trasy parametr trasy znajduje się po lewej stronie , więc zmiany w parametrze culturecontroller nie controller unieważniają wartości culture .
  • W szablonie trasy parametr trasy jest traktowany jako po prawej stronie właściwości "blog" , która pojawia się w culturecontroller wymaganych wartościach.

Konfigurowanie metadanych punktu końcowego

Poniższe linki zawierają informacje dotyczące konfigurowania metadanych punktu końcowego:

Dopasowywanie hostów w trasach za pomocą requireHost

RequireHost stosuje ograniczenie do trasy, która wymaga określonego hosta. Parametr RequireHost lub RequireHost może być:

  • Host: www.domain.com , dopasowuje do dowolnego www.domain.com portu.
  • Host z symbolami wieloznacznymi: *.domain.com , dopasowuje , , lub na dowolnym www.domain.comsubdomain.domain.comwww.subdomain.domain.com porcie.
  • Port: *:5000 , dopasowuje port 5000 do dowolnego hosta.
  • Host i port: www.domain.com:5000 lub , dopasowuje host i *.domain.com:5000 port.

Można określić wiele parametrów przy użyciu RequireHost polecenia lub [Host] . Ograniczenie pasuje do hostów prawidłowych dla dowolnego z parametrów. Na przykład dopasowuje [Host("domain.com", "*.domain.com")] , domain.com i www.domain.comsubdomain.domain.com .

Poniższy kod używa RequireHost , 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 [Host] atrybutu na kontrolerze, aby wymagać dowolnego 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 [Host] atrybutu 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 wersji ASP.NET Core 3.0 w celu zwiększenia wydajności.

Gdy aplikacja ma problemy z wydajnością, często podejrzewa się, że problemem jest routing. Podejrzewa się, że routing jest powodowany tym, że struktury, takie jak kontrolery i strony, zgłaszają ilość czasu spędzonego wewnątrz struktury Razor w komunikatach rejestrowania. Gdy istnieje znacząca różnica 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 przyczyną jest routing.

Routing jest testowany pod kątem wydajności przy użyciu tysięcy punktów końcowych. Jest mało prawdopodobne, że typowa aplikacja napotka problem z wydajnością, ponieważ jest zbyt duża. Najczęstszą główną przyczyną powolnego routingu jest zwykle źle zachowujące się niestandardowe oprogramowanie pośredniczące.

Poniższy przykład kodu przedstawia 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.");
        });
    });
}

Do routingu czasowego:

  • Przeplataj każde oprogramowanie pośredniczące kopią oprogramowania pośredniczącego chronometrażu pokazaną w poprzednim kodzie.
  • Dodaj unikatowy identyfikator, aby skorelować dane chronometrażu z kodem.

Jest to podstawowy sposób zawężenia opóźnienia, gdy jest ono znaczące, na przykład więcej niż 10ms . Time 2Odejmowanie Time 1 od raportów czasu spędzonego w oprogramowania UseRouting pośredniczącego.

Poniższy kod używa bardziej zwartego 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

Na poniższej liście przedstawiono pewne szczegółowe informacje o funkcjach routingu, które są stosunkowo kosztowne w porównaniu z podstawowymi szablonami tras:

  • Wyrażenia regularne: Można pisać wyrażenia regularne, 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 segmentu zwykłych ścieżek URL.
    • Spowoduje to przydzielenie o wiele większej liczby podciągów.
    • Logika segmentu złożonego nie została zaktualizowana w ASP.NET Core wydajności routingu w wersji 3.0.
  • Synchroniczny dostęp do danych: wiele złożonych aplikacji ma dostęp do bazy danych w ramach routingu. ASP.NET Core 2.2 i starsze mogą nie zapewniać odpowiednich punktów rozszerzalności do obsługi routingu dostępu do bazy danych. Na przykład IRouteConstraint , i są IActionConstraint synchroniczne. Punkty rozszerzalności, takie jak MatcherPolicy i EndpointSelectorContext , są asynchroniczne.

Wskazówki dla autorów bibliotek

Ta sekcja zawiera wskazówki dla autorów bibliotek, których tworzenie jest na podstawie routingu. Te szczegóły mają na celu zapewnienie, że deweloperzy aplikacji będą dobrze korzystać z bibliotek i platform, które rozszerzają routing.

Definiowanie punktów końcowych

Aby utworzyć platformę korzystającą z routingu do dopasowywania adresów URL, zacznij od zdefiniowania interfejsu użytkownika, które jest na podstawie UseEndpoints .

WYKONAJ kompilację na podstawie . Umożliwia to użytkownikom tworzenie struktury z innymi funkcjami ASP.NET Core bez nieporozumień. Każdy 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");
});

ZWRÓĆ zapieczętowany typ konkretny z wywołania , które implementuje IEndpointConventionBuilder . Większość metod Map... struktury jest zgodnie z tym wzorcem. IEndpointConventionBuilderInterfejs:

  • Umożliwia tworzenie metadanych.
  • Jest ona ukierunkowana na różne metody rozszerzeń.

Deklarowanie własnego typu umożliwia dodanie do konstruktora własnych funkcji specyficznych dla struktury. Można opakować konstruktora zadeklarowany w ramach struktury i przesyłać do niego wywołania.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization()
                                 .WithMyFrameworkFeature(awesome: true);

    endpoints.MapHealthChecks("/healthz");
});

ROZWAŻ napisanie własnego . EndpointDataSource to podstawowy element 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 nie aktualizującego źródła danych.

NIE próbuj domyślnie rejestrować konta. Wymagaj od użytkowników zarejestrowania struktury w programie UseEndpoints . W przypadku routingu jest to, że domyślnie nic nie jest uwzględniane i UseEndpoints jest to miejsce do rejestrowania punktów końcowych.

Tworzenie oprogramowania pośredniczącego zintegrowanego z routingiem

ROZWAŻ zdefiniowanie typów metadanych jako interfejsu.

CZY umożliwia korzystanie z 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 wie, jak stosować atrybuty.

Deklarowanie typu metadanych jako interfejsu dodaje kolejną warstwę elastyczności:

  • Interfejsy są owalne.
  • Deweloperzy mogą deklarować 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 śledzenia tych wytycznych jest uniknięcie definiowania metadanych znaczników:

  • Nie należy po prostu szukać obecności typu metadanych.
  • Zdefiniuj właściwość w metadanych i sprawdź właściwość .

Kolekcja metadanych jest uporządkowana i obsługuje zastępowanie według priorytetu. W przypadku kontrolerów najbardziej szczegółowe są metadane metody akcji.

UPEWNIJ się, że oprogramowanie pośredniczące jest przydatne z routingiem i bez niego.

app.UseRouting();

app.UseAuthorization(new AuthorizationPolicy() { ... });

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization();
});

Jako przykład tych wytycznych rozważ oprogramowanie UseAuthorization pośredniczące. Oprogramowanie pośredniczące autoryzacji umożliwia podanie zasad rezerwowych. Zasady rezerwowe, jeśli zostały określone, dotyczą obu tych zasad:

  • Punkty końcowe bez określonych zasad.
  • Żądania, które nie pasują do punktu końcowego.

Dzięki temu oprogramowanie pośredniczące autoryzacji jest przydatne poza kontekstem routingu. Oprogramowanie pośredniczące autoryzacji może być używane do tradycyjnego programowania oprogramowania pośredniczącego.

Debugowanie diagnostyki

Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw Logging:LogLevel:Microsoft wartość Debug . W środowisku dewelopera ustaw poziom dziennika w ustawieniach appsettings. Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}