Udostępnij za pośrednictwem


Routing do akcji kontrolera na platformie ASP.NET Core

Ryan Nowak, Kirk Larkin i Rick Anderson

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

kontrolery ASP.NET Core używają oprogramowania pośredniczącego routingu, aby dopasować adresy URL żądań przychodzących i mapować je na akcje. Szablony tras:

  • Są definiowane podczas uruchamiania w Program.cs lub w atrybutach.
  • Opisz, jak ścieżki adresów URL są dopasowywane do akcji.
  • Są używane do generowania adresów URL łączy. Wygenerowane linki są zwykle zwracane w odpowiedziach.

Akcje są tradycyjnie kierowane lub kierowane przez atrybuty. Umieszczenie trasy na kontrolerze lub akcji powoduje, że jest ona trasowana za pomocą atrybutu. Aby uzyskać więcej informacji, zobacz Routing mieszany .

Ten dokument:

  • Objaśnia interakcje między MVC i Routingiem:
    • Jak typowe aplikacje MVC korzystają z funkcji routingu.
    • Obejmuje oba:
    • Zobacz Routing , aby uzyskać szczegółowe informacje na temat routingu zaawansowanego.
  • Odwołuje się do domyślnego systemu routingu o nazwie routing punktów końcowych. Do celów zgodności można używać kontrolerów z poprzednią wersją routingu. Aby uzyskać instrukcje, zobacz przewodnik migracji 2.2-3.0.

Konfigurowanie konwencjonalnej trasy

Szablon ASP.NET Core MVC generuje konwencjonalny kod routingu podobny do następującego:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

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

app.Run();

MapControllerRoute służy do tworzenia pojedynczej trasy. Pojedyncza trasa nosi nazwę default route. Większość aplikacji z kontrolerami i widokami używa szablonu trasy podobnego do default. REST Interfejsy API powinny używać routingu atrybutów.

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

Szablon "{controller=Home}/{action=Index}/{id?}"trasy:

  • Pasuje do ścieżki adresu URL, takiej jak /Products/Details/5

  • Wyodrębnia wartości { controller = Products, action = Details, id = 5 } tras, tokenizując ścieżkę. Wyodrębnianie wartości tras prowadzi do zgodności, jeśli aplikacja ma kontroler nazwany ProductsController oraz akcję Details.

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfo jest dostarczany przez pakiet NuGet Rick.Docs.Samples.RouteInfo i wyświetla informacje o trasie.

  • /Products/Details/5 model wiąże wartość parametru id = 5 z wartością id , aby ustawić parametr na 5. Aby uzyskać więcej informacji, zobacz Powiązanie modelu.

  • {controller=Home} definiuje Home jako wartość domyślną controller.

  • {action=Index} definiuje Index jako wartość domyślną action.

  • Znak ? w pliku {id?} definiuje id się jako opcjonalny.

    • Domyślne i opcjonalne parametry trasy nie muszą być obecne w ścieżce adresu URL dla dopasowania. Zobacz Dokumentacja szablonu trasy, aby uzyskać szczegółowy opis składni szablonu trasy.
  • Pasuje do ścieżki /adresu URL .

  • Tworzy wartości { controller = Home, action = Index }tras .

Wartości i controlleraction używają wartości domyślnych. id nie generuje wartości, ponieważ w ścieżce adresu URL nie ma odpowiedniego segmentu. / odpowiada tylko wtedy, gdy istnieje akcja HomeController i Index :

public class HomeController : Controller
{
    public IActionResult Index() { ... }
}

Korzystając z powyższej definicji kontrolera i szablonu trasy, HomeController.Index akcja jest uruchamiana dla następujących ścieżek URL:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

Ścieżka adresu URL / używa domyślnych Home kontrolerów i Index akcji szablonu trasy. Ścieżka adresu URL /Home używa domyślnej Index akcji szablonu trasy.

Metoda MapDefaultControllerRoutewygody:

app.MapDefaultControllerRoute();

Zastępuje:

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

Ważne

Trasowanie jest konfigurowane przy użyciu middleware UseRouting i UseEndpoints. Aby użyć kontrolerów:

Aplikacje zazwyczaj nie muszą wywoływać ani UseRouting, ani UseEndpoints. WebApplicationBuilder Konfiguruje potok oprogramowania pośredniczącego, który opakowuje oprogramowanie pośredniczące dodane w Program.cs za pomocą UseRouting i UseEndpoints. Aby uzyskać więcej informacji, zobacz Routing na platformie ASP.NET Core.

Routing konwencjonalny

Routing konwencjonalny jest używany z kontrolerami i widokami. Trasa default :

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

Powyższy jest przykładem konwencjonalnej trasy. Jest to nazywane routingiem konwencjonalnym, ponieważ ustanawia konwencjędla ścieżek adresów URL:

  • Pierwszy segment ścieżki, {controller=Home}, odpowiada nazwie kontrolera.
  • Drugi segment, {action=Index}, odnosi się do nazwy akcji.
  • Trzeci segment {id?} jest używany dla opcjonalnego id . Element ? in {id?} sprawia, że jest opcjonalny. id służy do mapowania na jednostkę modelu.

Korzystając z tej default trasy, ścieżka adresu URL:

  • /Products/List przypisuje się do ProductsController.List działania.
  • /Blog/Article/17 mapuje na BlogController.Article i zazwyczaj model wiąże id parametr z wartością 17.

To mapowanie:

  • Jest oparty tylko na kontrolerach i nazwach akcji.
  • Nie jest oparta na przestrzeniach nazw, lokalizacjach plików źródłowych ani parametrach metody.

Użycie konwencjonalnego routingu z trasą domyślną umożliwia utworzenie aplikacji bez konieczności tworzenia nowego wzorca adresu URL dla każdej akcji. W przypadku aplikacji z akcjami stylu CRUD spójność adresów URL między kontrolerami:

  • Ułatwia uproszczenie kodu.
  • Sprawia, że interfejs użytkownika jest bardziej przewidywalny.

Ostrzeżenie

Element id w poprzednim kodzie jest definiowany jako opcjonalny przez szablon trasy. Akcje mogą być wykonywane bez opcjonalnego identyfikatora podanego jako część adresu URL. Ogólnie rzecz biorąc, w przypadku pominięcia id w adresie URL:

  • id jest ustawiony na 0 przez bindowanie modelu.
  • W bazie danych pasującej id == 0nie można odnaleźć jednostki .

Routing atrybutów zapewnia szczegółową kontrolę, aby identyfikator był wymagany dla niektórych akcji, a nie dla innych. Zgodnie z konwencją dokumentacja zawiera opcjonalne parametry, takie jak id wtedy, gdy prawdopodobnie pojawią się w poprawnym użyciu.

Większość aplikacji powinna wybrać podstawowy i opisowy schemat routingu, aby adresy URL mogły być czytelne i zrozumiałe. Domyślna trasa konwencjonalna {controller=Home}/{action=Index}/{id?}:

  • Obsługuje podstawowy i opisowy schemat routingu.
  • Jest przydatnym punktem wyjścia dla aplikacji opartych na interfejsie użytkownika.
  • Jest jedynym szablonem ścieżki wymaganym dla wielu aplikacji interfejsu użytkownika w internecie. W przypadku większych aplikacji interfejsu użytkownika sieci Web kolejna trasa korzystająca z obszarów jest często wymagana.

MapControllerRoute i MapAreaRoute :

  • Automatycznie przypisz wartość kolejności do punktów końcowych na podstawie kolejności, w której są wywoływane.

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

  • Nie ma pojęcia tras.
  • Nie zapewnia gwarancji kolejności wykonywania rozszerzalności, wszystkie punkty końcowe są przetwarzane jednocześnie.

Włącz rejestrowanie , aby zobaczyć, jak wbudowane implementacje routingu, takie jak Route, pasują do żądań.

Routing atrybutów wyjaśniono w dalszej części tego dokumentu.

Wiele konwencjonalnych tras

Wiele konwencjonalnych tras można skonfigurować, dodając więcej wywołań do MapControllerRoute i MapAreaControllerRoute. Umożliwia to zdefiniowanie wielu konwencji lub dodanie konwencjonalnych tras przeznaczonych do określonej akcji, takich jak:

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Trasa blog w poprzednim kodzie jest dedykowaną trasą konwencjonalną. Nazywa się to dedykowaną tradycyjną trasą, ponieważ:

Ponieważ controller i action nie są wyświetlane w szablonie "blog/{*article}" trasy jako parametry:

  • Mogą mieć tylko wartości { controller = "Blog", action = "Article" }domyślne .
  • Ta trasa zawsze mapuje na akcję BlogController.Article.

/Blog, /Blog/Articlei /Blog/{any-string} są jedynymi ścieżkami adresów URL pasujących do trasy blogu.

Powyższy przykład:

  • blog trasa ma wyższy priorytet dla dopasowań niż default, ponieważ jest dodana jako pierwsza.
  • To przykład routingu stylu Slug , w którym zazwyczaj jest używana nazwa artykułu jako część adresu URL.

Ostrzeżenie

W ASP.NET Core mechanizmy routingu nie wykonują następujących czynności:

  • Definiowanie koncepcji nazywanej trasą. UseRouting Dodaje trasę zgodną z potokiem oprogramowania pośredniczącego. Oprogramowanie UseRouting pośredniczące analizuje zestaw punktów końcowych zdefiniowanych w aplikacji i wybiera najlepsze dopasowanie punktu końcowego na podstawie żądania.
  • Gwarantowanie kolejności realizacji rozszerzeń, takich jak IRouteConstraint lub IActionConstraint.

Zobacz Routing , aby uzyskać informacje referencyjne dotyczące routingu.

Tradycyjna kolejność routingu

Konwencjonalne routowanie dopasowuje się tylko do kombinacji działania i kontrolera, które są zdefiniowane przez aplikację. Ma to na celu uproszczenie przypadków, w których nakładają się konwencjonalne trasy. Dodawanie tras przy użyciu MapControllerRoute, MapDefaultControllerRoute i MapAreaControllerRoute automatycznie przypisuje wartość zamówienia do ich punktów końcowych na podstawie kolejności, w jakiej są wywoływane. Dopasowania z wyświetlonej wcześniej trasy mają wyższy priorytet. Routing konwencjonalny jest zależny od kolejności. Ogólnie rzecz biorąc, trasy z obszarami powinny być umieszczane wcześniej, ponieważ są bardziej szczegółowe niż trasy bez obszaru. Dedykowane trasy konwencjonalne z parametrami catch-all, takimi jak {*article}, mogą sprawić, że trasa będzie zbyt zachłanna, co oznacza, że będzie dopasowywać adresy URL, które powinny być obsługiwane przez inne trasy. Umieść chciwe trasy później w tabeli tras, aby zapobiec chciwym dopasowaniom.

Ostrzeżenie

Parametr catch-all może być niepoprawnie zgodny z trasami z powodu błędu w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:

  • Trasa typu catch-all, na przykład {**slug}"
  • Uniwersalna trasa nie odpowiada na żądania, które powinna obsługiwać.
  • Usunięcie innych tras powoduje, że uniwersalna trasa zaczyna działać.

Zobacz błędy GitHub 18677 i 16579 jako przykłady przypadków, w których wystąpiła ta usterka.

Poprawka wymagająca aktywacji dla tej usterki zawiera się w SDK platformy .NET Core 3.1.301 lub nowszym. 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.

Rozwiązywanie niejednoznacznych akcji

Gdy dwa punkty końcowe są zestawione za pomocą routingu, routing musi wykonać jedną z następujących czynności:

  • Wybierz najlepszego kandydata.
  • Zgłaszanie wyjątku.

Na przykład:

public class Products33Controller : Controller
{
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpPost]
    public IActionResult Edit(int id, Product product)
    {
        return ControllerContext.MyDisplayRouteInfo(id, product.name);
    }
}

Poprzedni kontroler definiuje dwie akcje zgodne z następującymi akcjami:

  • Ścieżka adresu URL /Products33/Edit/17
  • Kierowanie danych { controller = Products33, action = Edit, id = 17 }.

Jest to typowy wzorzec dla kontrolerów MVC:

  • Edit(int) wyświetla formularz do edycji produktu.
  • Edit(int, Product) przetwarza opublikowany formularz.

Aby ustalić prawidłową trasę:

  • Edit(int, Product) jest wybierany, gdy żądanie jest http POST.
  • Edit(int) jest wybierany, gdy czasownik HTTP jest czymkolwiek innym. Edit(int)jest zwykle wywoływana za pośrednictwem .GET

HttpPostAttribute [HttpPost] jest dostarczany do routingu, aby umożliwić wybór na podstawie metody HTTP żądania. Funkcja HttpPostAttribute sprawia, że Edit(int, Product) lepsze dopasowanie niż Edit(int).

Ważne jest, aby zrozumieć rolę atrybutów, takich jak HttpPostAttribute. Podobne atrybuty są definiowane dla innych czasowników HTTP. W przypadku routingu konwencjonalnego akcje używają tej samej nazwy akcji, gdy są częścią formularza pokazu, przesyłaj przepływ pracy formularza. Na przykład zobacz Zbadaj dwie metody akcji Edytuj.

Jeśli routing nie może wybrać najlepszego kandydata, zgłoszony zostanie wyjątek AmbiguousMatchException, wymieniający wiele dopasowanych punktów końcowych.

Konwencjonalne nazwy tras

Ciągi "blog" i "default" w następujących przykładach to konwencjonalne nazwy tras.

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Nazwy tras dają trasie nazwę logiczną. Nazwana trasa może służyć do generowania adresów URL. Użycie nazwanej trasy upraszcza tworzenie adresów URL, gdy kolejność tras może spowodować, że generowanie adresów URL jest skomplikowane. Nazwy tras muszą być unikatowe dla całej aplikacji.

Nazwy tras:

  • Nie ma wpływu na dopasowywanie adresów URL ani obsługę żądań.
  • Są używane tylko do generowania adresów URL.

Koncepcja nazwy trasy jest reprezentowana w routingu jako IEndpointNameMetadata. Terminy nazwa trasy i nazwa punktu końcowego:

  • Są zamienne.
  • Który z nich jest używany w dokumentacji i kodzie, zależy od opisanego interfejsu API.

Routing atrybutów dla REST interfejsów API

REST Interfejsy API powinny używać routingu atrybutów do modelowania funkcjonalności aplikacji jako zestawu zasobów, w których operacje są reprezentowane przez czasowniki HTTP.

Trasowanie atrybutowe używa zestawu atrybutów do mapowania działań bezpośrednio na szablony tras. Poniższy kod jest typowy dla interfejsu REST API i jest używany w następnym przykładzie:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

W poprzednim kodzie MapControllers jest wywoływana w celu mapowania kontrolerów kierowanych atrybutami.

W poniższym przykładzie:

  • HomeController dopasowuje się do zestawu adresów URL podobnych do tych, które obsługuje domyślna konwencjonalna trasa {controller=Home}/{action=Index}/{id?} .
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Akcja HomeController.Index jest uruchamiana dla dowolnej ścieżki /adresu URL , /Home, /Home/Indexlub /Home/Index/3.

W tym przykładzie wyróżniono kluczową różnicę programową między routingiem atrybutów a routingiem konwencjonalnym. Routing atrybutów wymaga większej ilości danych wejściowych w celu określenia trasy. Tradycyjna trasa domyślna obsługuje trasy bardziej zwięźle. Jednak trasowanie z użyciem atrybutów wymaga i pozwala na precyzyjną kontrolę nad tym, które szablony tras są stosowane w każdej akcji.

W przypadku routingu atrybutów nazwy kontrolera i akcji nie odgrywają żadnej roli w dopasowywaniu akcji, chyba że używana jest zamiana tokenu. Poniższy przykład jest zgodny z tymi samymi adresami URL co w poprzednim przykładzie:

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Poniższy kod używa zastępowania tokenów dla znaczników action i controller.

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Następujący kod ma zastosowanie [Route("[controller]/[action]")] do kontrolera:

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

W poprzednim kodzie szablony metod Index muszą mieć dodane na początku / lub ~/, tak aby poprzedzały szablony tras. Szablony tras stosowane do akcji, które zaczynają się od / lub ~/, nie są łączone z szablonami tras zastosowanymi do kontrolera.

Zobacz Pierwszeństwo szablonu trasy, aby uzyskać informacje na temat wyboru szablonu trasy.

Nazwy routingu zarezerwowanego

Następujące słowa kluczowe to zastrzeżone nazwy parametrów tras podczas korzystania z kontrolerów lub Razor stron:

  • action
  • area
  • controller
  • handler
  • page

Używanie page jako parametru trasy w trasowaniu przez atrybuty jest typowym błędem. Powoduje to niespójne i mylące zachowanie podczas generowania adresów URL.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

Nazwy parametrów specjalnych są używane przez generowanie adresów URL w celu określenia, czy operacja generowania adresu URL odnosi się do Razor strony lub kontrolera.

Następujące słowa kluczowe są zastrzeżone w kontekście Razor widoku lub Razor strony:

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Te słowa kluczowe nie powinny być używane w przypadku generowania linków, parametrów powiązanych z modelem ani właściwości najwyższego poziomu.

Szablony czasowników HTTP

ASP.NET Core ma następujące szablony czasowników HTTP:

Szablony tras

ASP.NET Core ma następujące szablony tras:

Routing atrybutów za pomocą atrybutów czasownika HTTP

Rozważmy następujący kontroler:

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W poprzednim kodzie:

  • Każda akcja zawiera [HttpGet] atrybut, który ogranicza dopasowanie tylko do żądań HTTP GET.
  • Akcja GetProduct zawiera "{id}" szablon, dlatego id jest dołączana do "api/[controller]" szablonu na kontrolerze. Szablon metody to "api/[controller]/{id}". W związku z tym ta akcja odpowiada tylko żądaniom GET dla formularza /api/test2/xyz,/api/test2/123/api/test2/{any string} itp.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • Akcja GetIntProduct zawiera "int/{id:int}" szablon. Część :int szablonu ogranicza id wartości trasy do ciągów, które można przekonwertować na liczbę całkowitą. Żądanie GET do /api/test2/int/abc:
    • Nie pasuje do tej akcji.
    • Zwraca błąd 404 Nie znaleziono.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • Akcja GetInt2Product zawiera {id} element w szablonie, ale nie ogranicza id się do wartości, które można przekonwertować na liczbę całkowitą. Żądanie GET do /api/test2/int2/abc:
    • Pasuje do tej trasy.
    • Model binding nie udaje się przekonwertować abc na liczbę całkowitą. Parametr id metody to liczba całkowita.
    • Zwraca 400 Bad Request, ponieważ powiązanie modelu nie zdołało przekonwertować abc na liczbę całkowitą.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Routing atrybutów może używać HttpMethodAttribute atrybutów, takich jak HttpPostAttribute, HttpPutAttributei HttpDeleteAttribute. Wszystkie atrybuty czasownika HTTP akceptują szablon trasy. W poniższym przykładzie przedstawiono dwie akcje pasujące do tego samego szablonu trasy:

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

Za pomocą ścieżki adresu URL /products3:

  • Akcja MyProductsController.ListProducts jest uruchamiana, gdy metoda HTTP jest GET.
  • Akcja MyProductsController.CreateProduct jest uruchamiana, gdy metoda HTTP jest POST.

Podczas tworzenia interfejsu REST API rzadko zdarza się, że trzeba użyć [Route(...)] w metodzie akcji, ponieważ akcja akceptuje wszystkie metody HTTP. Lepiej użyć bardziej szczegółowego atrybutu czasownika HTTP, aby dokładnie określić, co obsługuje interfejs API. Oczekuje się, że klienci interfejsów REST API będą wiedzieć, jakie ścieżki i czasowniki HTTP mapują na określone operacje logiczne.

REST Interfejsy API powinny używać routingu atrybutów do modelowania funkcjonalności aplikacji jako zestawu zasobów, w których operacje są reprezentowane przez czasowniki HTTP. Oznacza to, że wiele operacji, na przykład GET i POST w tym samym zasobie logicznym, używa tego samego adresu URL. Routing za pomocą atrybutów zapewnia poziom kontroli, który jest potrzebny do dokładnego zaprojektowania publicznego układu punktów końcowych API.

Ponieważ trasa atrybutu ma zastosowanie do określonej akcji, łatwo jest ustawić parametry wymagane w ramach definicji szablonu trasy. W poniższym przykładzie id jest wymagany jako część ścieżki adresu URL:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Akcja Products2ApiController.GetProduct(int) :

  • Jest uruchamiany ze ścieżką adresu URL, na przykład /products2/3
  • Nie jest uruchamiany za pomocą ścieżki adresu URL /products2.

Atrybut [Consumes] umożliwia akcji ograniczenie obsługiwanych typów zawartości żądania. Aby uzyskać więcej informacji, zobacz Definiowanie obsługiwanych typów zawartości żądań za pomocą atrybutu Consumes.

Zobacz Routing , aby uzyskać pełny opis szablonów tras i powiązanych opcji.

Aby uzyskać więcej informacji na temat [ApiController], zobacz atrybut ApiController.

Nazwa trasy

Poniższy kod definiuje nazwę trasy :Products_List

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Nazwy tras mogą służyć do generowania adresu URL na podstawie określonej trasy. Nazwy tras:

  • Nie ma wpływu na zachowanie dopasowywania adresów URL podczas trasowania.
  • Są używane tylko do generowania adresów URL.

Nazwy tras muszą być unikatowe dla całej aplikacji.

Porównaj powyższy kod z tradycyjną trasą domyślną, która definiuje id parametr jako opcjonalny ({id?}). Możliwość precyzyjnego określenia interfejsów API ma zalety, takie jak przyporządkowanie /products i /products/5 do różnych działań.

Łączenie tras atrybutów

Aby trasowanie według atrybutów było mniej powtarzalne, atrybuty ruty na kontrolerze są łączone z atrybutami ruty w poszczególnych akcjach. Wszystkie szablony tras zdefiniowane na kontrolerze są dołączane do szablonów tras w akcjach. Umieszczenie atrybutu trasy na kontrolerze sprawia, że wszystkie akcje w kontrolerze używają routingu atrybutów.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W powyższym przykładzie:

  • Ścieżka adresu URL /products może pasować do ProductsApi.ListProducts
  • Ścieżka /products/5 adresu URL może być zgodna z ProductsApi.GetProduct(int).

Obie te akcje są zgodne tylko z protokołem HTTP GET , ponieważ są one oznaczone atrybutem [HttpGet] .

Szablony tras stosowane do akcji, które zaczynają się od / lub ~/, nie są łączone z szablonami tras zastosowanymi do kontrolera. Poniższy przykład pasuje do zestawu ścieżek URL podobnych do trasy domyślnej.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Tabela poniżej wyjaśnia atrybuty [Route] w poprzednim kodzie.

Atrybut Łączy się z [Route("Home")] Definiuje wzorzec trasy
[Route("")] Tak "Home"
[Route("Index")] Tak "Home/Index"
[Route("/")] Nie ""
[Route("About")] Tak "Home/About"

Kolejność tras atrybutów

Routing tworzy drzewo i pasuje do wszystkich punktów końcowych jednocześnie:

  • Wpisy trasy zachowują się tak, jakby zostały umieszczone w idealnej kolejności.
  • Najbardziej konkretne trasy mają szansę wykonać przed bardziej ogólnymi trasami.

Na przykład trasa atrybutu taka jak blog/search/{topic} jest bardziej specyficzna niż trasa atrybutu taka jak blog/{*article}. Trasa blog/search/{topic} ma domyślnie wyższy priorytet, ponieważ jest bardziej szczegółowa. Korzystając z routingu konwencjonalnego, deweloper jest odpowiedzialny za umieszczanie tras w żądanej kolejności.

Trasy atrybutów mogą konfigurować kolejność przy użyciu Order właściwości . Wszystkie dostarczone atrybuty trasy obejmują Order. Trasy są przetwarzane zgodnie z sortowaniem rosnącym według właściwości Order. Domyślna kolejność to 0. Ustawianie trasy za pomocą Order = -1 odbywa się przed trasami, które nie mają ustalonej kolejności. Ustawianie trasy przy użyciu Order = 1 odbywa się po domyślnym porządkowaniu tras.

Unikaj polegania na Order. Jeśli przestrzeń adresów URL aplikacji wymaga jawnie określonych wartości kolejności do poprawnego kierowania, prawdopodobnie jest to również mylące dla klientów. Ogólnie rzecz biorąc, routing atrybutów wybiera poprawną trasę z dopasowaniem adresu URL. Jeśli domyślna kolejność używana na potrzeby generowania adresów URL nie działa, użycie nazwy trasy jako zastąpienia jest zwykle prostsze niż zastosowanie Order właściwości.

Rozważmy dwa następujące kontrolery, które definiują dopasowanie trasy /home:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

/home Żądanie z powyższym kodem zgłasza wyjątek podobny do następującego:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

Dodanie Order do jednego z atrybutów trasy rozwiązuje niejednoznaczność:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

W poprzednim kodzie /home uruchamia HomeController.Index punkt końcowy. Aby dostać się do MyDemoController.MyIndex, złóż żądanie /home/MyIndex. Uwaga:

  • Powyższy kod jest przykładem lub słabym projektem routingu. Użyto tego do zilustrowania Order właściwości.
  • Właściwość Order rozwiązuje tylko niejednoznaczność, że tego szablonu nie można dopasować. Lepiej byłoby usunąć [Route("Home")] szablon.

Zobacz Razor Konwencje tras stron i aplikacji: Kolejność tras, aby uzyskać informacje na temat kolejności tras za pomocą Razor stron.

W niektórych przypadkach zwracany jest błąd HTTP 500 z niejednoznacznymi trasami. Użyj logowania, aby zobaczyć, które punkty końcowe spowodowały AmbiguousMatchException.

Zamiana tokenów w szablonach tras [kontroler], [akcja], [obszar]

Dla wygody trasy z przypisanymi atrybutami obsługują zamianę tokenów przez umieszczenie tokenu w nawiasach kwadratowych ([, ]). Tokeny [action], [area]i [controller] są zastępowane wartościami nazwy akcji, nazwy obszaru i nazwy kontrolera z akcji, w której zdefiniowano trasę:

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W poprzednim kodzie:

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • Dopasowania /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Dopasowania /Products0/Edit/{id}

Zastąpienie tokenu odbywa się w ostatnim kroku tworzenia tras atrybutów. Powyższy przykład zachowuje się tak samo jak w poniższym kodzie:

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Jeśli czytasz to w języku innym niż angielski, daj nam znać w tej kwestii dyskusji GitHub, jeśli chcesz, aby komentarze kodu były w Twoim języku ojczystym.

Trasy atrybutów można również łączyć z dziedziczeniem. Jest to potężne połączenie z zastępowaniem tokenu. Zamiana tokenu ma również zastosowanie do nazw ścieżek zdefiniowanych przez ścieżki atrybutów. [Route("[controller]/[action]", Name="[controller]_[action]")]generuje unikatową nazwę trasy dla każdej akcji:

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

pl-PL: Aby dopasować ogranicznik zastępczy tokenu literału [ lub ], należy go zduplikować, powtarzając znak ([[ lub ]]).

Dostosowywanie zamiany tokenu za pomocą funkcji przekształcania parametrów

Zamianę tokenu można dostosować przy użyciu transformatora parametrów. Transformator parametrów implementuje IOutboundParameterTransformer i przekształca wartość parametrów. Na przykład niestandardowy transformator parametrów SlugifyParameterTransformer zmienia SubscriptionManagement wartość trasy na subscription-management.

using System.Text.RegularExpressions;

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

To RouteTokenTransformerConvention jest konwencją modelu aplikacji, która:

  • Stosuje transformator parametru do wszystkich tras atrybutów w aplikacji.
  • Dostosowuje wartości tokenów atrybutów trasy podczas ich zastępowania.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Poprzednia ListAll metoda pasuje do /subscription-management/list-all.

Element RouteTokenTransformerConvention jest zarejestrowany jako opcja:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(new RouteTokenTransformerConvention(
                                 new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

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

app.Run();

Aby uzyskać definicję terminu „Slug”, odnieś się do dokumentacji internetowej MDN dotyczącej Slug.

Ostrzeżenie

W przypadku używania System.Text.RegularExpressions do przetwarzania niezaufanych danych wejściowych należy ustawić limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressionsatak typu "odmowa usługi". API platformy ASP.NET Core, które używają RegularExpressions, przekazują limit czasu.

Wiele tras opartych na atrybutach

Routing atrybutów obsługuje definiowanie wielu tras, które docierają do tej samej akcji. Najczęstszym zastosowaniem tej metody jest naśladowanie zachowania domyślnej konwencjonalnej trasy, jak pokazano w poniższym przykładzie:

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Umieszczenie wielu atrybutów trasy na kontrolerze oznacza, że każdy z nich łączy się z każdym z atrybutów trasy w metodach akcji:

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Wszystkie ograniczenia trasy czasownika HTTP implementują IActionConstraint.

Gdy na akcji umieszczone są wiele atrybutów trasy implementujących IActionConstraint:

  • Każde ograniczenie akcji łączy się z szablonem trasy zastosowanym do kontrolera.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Używanie wielu tras w akcjach może wydawać się użyteczne i potężne, jednak lepiej zachować przestrzeń adresów URL aplikacji jako podstawową i dobrze zdefiniowaną. Korzystaj z wielu tras w akcjach jedynie tam, gdzie jest to konieczne, na przykład, aby wspierać istniejących klientów.

Określanie opcjonalnych parametrów trasy atrybutu, wartości domyślnych i ograniczeń

Trasy atrybutów obsługują tę samą składnię śródliniową co konwencjonalne trasy w celu określenia opcjonalnych parametrów, wartości domyślnych i ograniczeń.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W poprzednim kodzie [HttpPost("product14/{id:int}")] stosuje ograniczenie trasy. Akcja Products14Controller.ShowProduct jest dopasowywana tylko przez ścieżki adresu URL, takie jak /product14/3. Część szablonu {id:int} trasy ogranicza ten segment tylko do liczb całkowitych.

Zobacz Dokumentacja szablonu trasy, aby uzyskać szczegółowy opis składni szablonu trasy.

Niestandardowe atrybuty trasy przy użyciu elementu IRouteTemplateProvider

Wszystkie atrybuty trasy implementują IRouteTemplateProviderelement . Środowisko uruchomieniowe ASP.NET Core:

  • Szuka atrybutów w klasach kontrolerów i metodach akcji podczas uruchamiania aplikacji.
  • Używa atrybutów implementujących IRouteTemplateProvider do tworzenia początkowego zestawu tras.

Zaimplementuj IRouteTemplateProvider , aby zdefiniować niestandardowe atrybuty trasy. Każda IRouteTemplateProvider z nich umożliwia zdefiniowanie pojedynczej trasy przy użyciu niestandardowego szablonu trasy, kolejności i nazwy:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; } = string.Empty;
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Poprzednia Get metoda zwraca wartość Order = 2, Template = api/MyTestApi.

Dostosowywanie tras atrybutów za pomocą modelu aplikacji

Model aplikacji:

  • Jest modelem obiektów utworzonym podczas uruchamiania w programie Program.cs.
  • Zawiera wszystkie metadane używane przez ASP.NET Core do kierowania i wykonywania akcji w aplikacji.

Model aplikacji zawiera wszystkie dane zebrane z atrybutów trasy. Dane z atrybutów trasy są dostarczane przez implementację IRouteTemplateProvider . Konwencje:

  • Można zapisać zmiany w celu zmodyfikowania modelu aplikacji, aby dostosować sposób działania routingu.
  • Są odczytywane podczas uruchamiania aplikacji.

W tej sekcji przedstawiono podstawowy przykład dostosowywania routingu przy użyciu modelu aplikacji. Poniższy kod sprawia, że trasy są w przybliżeniu zgodne ze strukturą folderów projektu.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

Poniższy kod uniemożliwia zastosowanie konwencji do kontrolerów, które są kierowane za pomocą atrybutów.

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Na przykład następujący kontroler nie używa polecenia NamespaceRoutingConvention:

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Metoda NamespaceRoutingConvention.Apply:

  • Nic nie robi, jeśli kontroler jest kierowany atrybut.
  • Ustawia szablon kontrolerów na namespacepodstawie elementu z usuniętą bazą namespace .

Element NamespaceRoutingConvention można zastosować w pliku Program.cs:

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(
     new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

Rozważmy na przykład następujący kontroler:

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

W poprzednim kodzie:

  • Podstawą namespace jest My.Application.
  • Pełna nazwa poprzedniego kontrolera to My.Application.Admin.Controllers.UsersController.
  • Parametr NamespaceRoutingConvention ustawia szablon kontrolerów na Admin/Controllers/Users/[action]/{id?wartość .

Może być również zastosowany jako atrybut na kontrolerze: NamespaceRoutingConvention

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Routing mieszany: routing atrybutów a routing konwencjonalny

aplikacje ASP.NET Core mogą łączyć użycie konwencjonalnego routingu i routingu atrybutów. Typowe jest używanie konwencjonalnych tras dla kontrolerów obsługujących strony HTML dla przeglądarek i routing atrybutów dla kontrolerów obsługujących REST interfejsy API.

Akcje są tradycyjnie kierowane lub kierowane przez atrybuty. Umieszczenie trasy w kontrolerze lub akcji sprawia, że trasa staje się przypisana jako atrybut. Akcje definiujące trasy atrybutów nie mogą być osiągane za pośrednictwem konwencjonalnych tras i odwrotnie. Każdy atrybut trasy na kontrolerze sprawia, że wszystkie akcje w atrybucie kontrolera są kierowane.

Routing atrybutów i routing konwencjonalny używają tego samego mechanizmu routingu.

Routing z znakami specjalnymi

Routing ze znakami specjalnymi może prowadzić na nieoczekiwane wyniki. Rozważmy na przykład kontroler z następującą metodą akcji:

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Jeśli string id zawiera następujące zakodowane wartości, mogą wystąpić nieoczekiwane wyniki:

ASCII Zakodowane
/ %2F
+

Parametry trasy nie zawsze są dekodowane za pomocą adresu URL. Ten problem może zostać rozwiązany w przyszłości. Aby uzyskać więcej informacji, zobacz ten problem z usługą GitHub;

Generowanie adresów URL i wartości otoczenia

Aplikacje mogą korzystać z funkcji generowania URL w routingu, aby tworzyć linki URL do akcji. Generowanie adresów URL eliminuje ręczne kodowanie adresów URL, dzięki czemu kod jest bardziej niezawodny i łatwy w utrzymaniu. Ta sekcja koncentruje się na funkcjach generowania adresów URL udostępnianych przez mvC i omówiono tylko podstawowe informacje na temat sposobu działania generowania adresów URL. Zobacz Routing , aby uzyskać szczegółowy opis generowania adresu URL.

Interfejs IUrlHelper jest podstawowym elementem infrastruktury między MVC i routingiem na potrzeby generowania adresów URL. W kontrolerach, widokach i składnikach widoku instancja IUrlHelper jest dostępna za pośrednictwem właściwości Url.

W poniższym przykładzie interfejs IUrlHelper jest używany poprzez właściwość Controller.Url do wygenerowania adresu URL prowadzącego do innej akcji.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Jeśli aplikacja używa domyślnej konwencjonalnej trasy, wartość url zmiennej to ciąg /UrlGeneration/Destinationścieżki adresu URL . Ścieżka tego adresu URL jest tworzona poprzez routowanie, które polega na łączeniu.

  • Wartości trasy z bieżącego żądania, które są nazywane wartościami otoczenia.
  • Wartości przekazane do Url.Action i podstawiając te wartości do szablonu trasy:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Każdy parametr trasy w szablonie trasy ma swoją wartość zamienioną przez dopasowanie nazw z wartościami i wartościami domyślnymi. Parametr trasy, który nie ma wartości, może:

  • Użyj wartości domyślnej, jeśli ma ją.
  • Można pominąć, jeśli jest to opcjonalne. Na przykład id z szablonu trasy {controller}/{action}/{id?}.

Generowanie adresu URL kończy się niepowodzeniem, jeśli jakikolwiek wymagany parametr trasy nie ma odpowiedniej wartości. Jeśli generowanie adresu URL zakończy się niepowodzeniem dla trasy, kolejna trasa zostanie podjęta do momentu wypróbowanej wszystkich tras lub znalezienia dopasowania.

W poprzednim przykładzie przyjęto Url.Actionrouting konwencjonalny. Generowanie adresów URL działa podobnie z routingiem atrybutów, chociaż koncepcje są różne. Z konwencjonalnym trasowaniem:

  • Wartości tras służą do rozwijania szablonu.
  • Wartości tras dla controller i action zwykle są wyświetlane w tym szablonie. Działa to, ponieważ adresy URL dopasowane przez routing są zgodne z konwencją.

W poniższym przykładzie użyto routingu atrybutów:

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

Akcja Source w poprzednim kodzie generuje custom/url/to/destinationelement .

LinkGenerator dodano w ASP.NET Core 3.0 jako alternatywę dla IUrlHelper. LinkGenerator oferuje podobne, ale bardziej elastyczne funkcje. Każda metoda w systemie IUrlHelper ma również odpowiednią rodzinę metod LinkGenerator .

Generowanie adresów URL według nazwy akcji

Url.Action, LinkGenerator.GetPathByAction oraz wszystkie powiązane przeciążenia mają na celu generowanie docelowego punktu końcowego poprzez określenie nazwy kontrolera i nazwy akcji.

W przypadku używania parametru Url.Actionbieżące wartości tras i controlleraction są udostępniane przez środowisko uruchomieniowe:

  • Wartość controller i action są częścią wartości otoczenia i wartości. Metoda Url.Action zawsze używa bieżących action wartości i controller generuje ścieżkę adresu URL, która kieruje do bieżącej akcji.

Routing próbuje użyć wartości w wartościach otoczenia, aby wypełnić informacje, które nie zostały podane podczas generowania adresu URL. Rozważ trasę taką jak {a}/{b}/{c}/{d} z wartościami otoczenia { a = Alice, b = Bob, c = Carol, d = David }.

  • Routing ma wystarczającą ilość informacji, aby wygenerować adres URL bez żadnych dodatkowych wartości.
  • Routing ma wystarczającą ilość informacji, ponieważ wszystkie parametry trasy mają wartość.

Jeśli wartość { d = Donovan } zostanie dodana:

  • Wartość { d = David } jest ignorowana.
  • Wygenerowana ścieżka adresu URL to Alice/Bob/Carol/Donovan.

Ostrzeżenie: ścieżki adresów URL są hierarchiczne. W poprzednim przykładzie, jeśli wartość { c = Cheryl } jest dodawana:

  • Obie wartości { c = Carol, d = David } są ignorowane.
  • Generowanie adresu URL nie jest już wartością d , a generowanie adresu URL kończy się niepowodzeniem.
  • Wymagane wartości c i d należy określić, aby wygenerować adres URL.

Możesz spodziewać się wystąpienia tego problemu z domyślną trasą {controller}/{action}/{id?}. Ten problem jest rzadki w praktyce, ponieważ Url.Action zawsze jawnie określa wartość controller i action .

Kilka przeciążeń Url.Action przyjmuje obiekt wartości trasy, aby podać wartości dla parametrów trasy innych niż controller oraz action. Obiekt wartości trasy jest często używany z elementem id. Na przykład Url.Action("Buy", "Products", new { id = 17 }). Obiekt wartości trasy:

  • Zgodnie z konwencją jest zwykle obiektem typu anonimowego.
  • Może to być IDictionary<> lub POCO.

Wszelkie dodatkowe wartości tras, które nie pasują do parametrów trasy, są umieszczane w ciągu zapytania.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url!);
}

Powyższy kod generuje /Products/Buy/17?color=red.

Poniższy kod generuje bezwzględny adres URL:

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url!);
}

Aby utworzyć bezwzględny adres URL, użyj jednego z następujących elementów:

  • Przeciążenie, które akceptuje element protocol. Na przykład powyższy kod.
  • LinkGenerator.GetUriByAction, który domyślnie generuje bezwzględne identyfikatory URI.

Generowanie adresów URL według trasy

Powyższy kod demonstrował generowanie adresu URL przez przekazanie kontrolera i nazwy akcji. IUrlHelper Udostępnia również rodzinę metod Url.RouteUrl . Te metody są podobne do Url.Action, ale nie kopiują bieżących wartości z action i controller do wartości trasy. Najbardziej typowe użycie elementu Url.RouteUrl:

  • Określa nazwę trasy do wygenerowania adresu URL.
  • Ogólnie rzecz biorąc, nie określa kontrolera ani nazwy akcji.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Razor Poniższy plik generuje link HTML do pliku Destination_Route:

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Generowanie adresów URL w kodzie HTML i Razor

IHtmlHelperudostępnia metody HtmlHelper i Html.ActionLink do generowania elementów i <form> .<a> Te metody używają metody Url.Action do wygenerowania adresu URL i akceptują podobne argumenty. Są Url.RouteUrl one obsługiwane HtmlHelperHtml.BeginRouteForm i Html.RouteLink które mają podobne funkcje.

TagHelpers generują adresy URL za pośrednictwem form klasy TagHelper i <a> TagHelper. Oba te elementy używają IUrlHelper do swojej implementacji. Aby uzyskać więcej informacji, zobacz Pomocnicy tagów w formularzach .

Wewnątrz widoków IUrlHelper właściwość jest dostępna za pośrednictwem Url właściwości dla dowolnego generowania adresów URL ad hoc, które nie są objęte powyższymi elementami.

Generowanie adresu URL w wynikach akcji

W poprzednich przykładach pokazano użycie IUrlHelper w kontrolerze. Najczęstszym użyciem kontrolera jest wygenerowanie adresu URL w ramach wyniku akcji.

Podstawowe klasy ControllerBase i Controller oferują wygodne metody dla wyników działań odwołujących się do innych działań. Jednym z typowych użycia jest przekierowanie po zaakceptowaniu danych wejściowych użytkownika:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

Metody fabryki wyników akcji, takie jak RedirectToAction i CreatedAtAction, podążają za wzorcem podobnym do metod na IUrlHelper.

Szczególny przypadek dla dedykowanych tras konwencjonalnych

Tradycyjne trasowanie może używać specjalnego rodzaju definicji trasy nazywanej dedykowaną konwencjonalną trasą. W poniższym przykładzie trasa o nazwie blog jest dedykowaną trasą konwencjonalną:

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Korzystając z powyższych definicji tras, Url.Action("Index", "Home") generuje ścieżkę URL / używając trasy default, ale dlaczego? Może się okazać, że wartości { controller = Home, action = Index } trasy będą wystarczające, aby wygenerować adres URL przy użyciu metody blog, a wynikiem będzie /blog?action=Index&controller=Home.

Dedykowane konwencjonalne trasy wykorzystują specjalne zachowanie wartości domyślnych, które nie mają odpowiadającego parametru trasy, aby zapobiec nadmiernemu generowaniu adresów URL przez trasy. W takim przypadku wartości domyślne to { controller = Blog, action = Article }, a ani controller, ani action nie pojawiają się jako parametry trasy. Gdy routing wykonuje generowanie adresów URL, podane wartości muszą być zgodne z wartościami domyślnymi. Generowanie adresu URL za pomocą blog kończy się niepowodzeniem, ponieważ wartości { controller = Home, action = Index } nie pasują do { controller = Blog, action = Article }. Następnie routing wraca do próby default, co się udaje.

Obszary

Obszary są funkcją MVC używaną do organizowania powiązanych funkcji w grupie jako oddzielnej:

  • Trasowanie przestrzeni nazw dla akcji kontrolera.
  • Struktura folderów dla widoków.

Używanie obszarów umożliwia aplikacji posiadanie wielu kontrolerów o tej samej nazwie, o ile mają różne obszary. Użycie obszarów tworzy hierarchię na potrzeby routingu przez dodanie innego parametru area trasy do controller i action. W tej sekcji omówiono sposób interakcji routingu z obszarami. Zobacz Obszary , aby uzyskać szczegółowe informacje na temat sposobu użycia obszarów z widokami.

Poniższy przykład konfiguruje wzorzec MVC do używania domyślnej trasy konwencjonalnej oraz trasy area dla trasy nazwanej areaBlog.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{    
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

app.Run();

W poprzednim kodzie MapAreaControllerRoute jest wywoływane, aby utworzyć "blog_route". Drugi parametr , "Blog"to nazwa obszaru.

Podczas dopasowywania ścieżki adresu URL, takiej jak /Manage/Users/AddUser, trasa "blog_route" generuje wartości trasy { area = Blog, controller = Users, action = AddUser }. Wartość trasy area jest generowana przez domyślną wartość elementu area. Trasa utworzona przez MapAreaControllerRoute jest równoważna następującym:

app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

MapAreaControllerRoute tworzy trasę, korzystając z zarówno wartości domyślnej, jak i ograniczenia dla area, wykorzystując podaną nazwę obszaru, w tym przypadku Blog. Wartość domyślna gwarantuje, że trasa zawsze generuje { area = Blog, ... }, a ograniczenie wymaga wartości { area = Blog, ... } do generowania adresu URL.

Routing konwencjonalny jest zależny od kolejności. Ogólnie rzecz biorąc, trasy z obszarami powinny być umieszczane wcześniej, ponieważ są bardziej szczegółowe niż trasy bez obszaru.

Korzystając z powyższego przykładu, wartości trasy { area = Blog, controller = Users, action = AddUser } odpowiadają następującej akcji.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

Atrybut [Area] określa kontroler w ramach obszaru. Ten kontroler znajduje się w Blog obszarze. Kontrolery bez atrybutu [Area] nie są elementami członkowskimi żadnego obszaru i nie są zgodne, gdy area wartość trasy jest dostarczana przez routing. W poniższym przykładzie tylko pierwszy kontroler wymieniony może odpowiadać wartościom { area = Blog, controller = Users, action = AddUser }trasy.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

Przestrzeń nazw każdego kontrolera jest wyświetlana tutaj w celu uzyskania kompletności. Jeśli poprzednie kontrolery używały tej samej przestrzeni nazw, zostanie wygenerowany błąd kompilatora. Przestrzenie nazw klas nie mają wpływu na routing MVC.

Dwa pierwsze kontrolery są członkami obszarów i są zgodne tylko wtedy, gdy ich nazwa obszaru jest podana area przez wartość trasy. Trzeci kontroler nie jest członkiem żadnego obszaru i może działać poprawnie tylko wtedy, gdy routing nie prześle żadnej wartości dla area.

Jeśli chodzi o dopasowanie żadnej wartości, brak area wartości jest taki sam, jak w przypadku wartości area null lub pustego ciągu.

Podczas wykonywania akcji wewnątrz obszaru wartość trasy jest dostępna jako wartość area otoczenia dla routingu do użycia na potrzeby generowania adresów URL. Oznacza to, że domyślnie obszary działają lepkie dla generowania adresów URL, jak pokazano w poniższym przykładzie.

app.MapAreaControllerRoute(name: "duck_route",
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
                             pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

Poniższy kod generuje adres URL do /Zebra/Users/AddUser:

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

Definicja akcji

Metody publiczne na kontrolerze, z wyjątkiem tych z atrybutem NonAction , są akcjami.

Przykładowy kod

Diagnostyka debugowania

Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw wartość Logging:LogLevel:MicrosoftDebug. W środowisku programistycznym ustaw poziom logowania w appsettings.Development.json:

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

kontrolery ASP.NET Core używają oprogramowania pośredniczącego routingu, aby dopasować adresy URL żądań przychodzących i mapować je na akcje. Szablony tras:

  • Są definiowane w kodzie startowym lub atrybutach.
  • Opisz, jak ścieżki adresów URL są dopasowywane do akcji.
  • Są używane do generowania adresów URL łączy. Wygenerowane linki są zwykle zwracane w odpowiedziach.

Akcje są tradycyjnie kierowane lub kierowane przez atrybuty. Umieszczenie trasy na kontrolerze lub akcji powoduje, że jest ona trasowana za pomocą atrybutu. Aby uzyskać więcej informacji, zobacz Routing mieszany .

Ten dokument:

  • Objaśnia interakcje między MVC i Routingiem:
    • Jak typowe aplikacje MVC korzystają z funkcji routingu.
    • Obejmuje oba:
    • Zobacz Routing , aby uzyskać szczegółowe informacje na temat routingu zaawansowanego.
  • Odwołuje się do domyślnego systemu routingu dodanego w ASP.NET Core 3.0, nazywanego routingiem punktów końcowych. Do celów zgodności można używać kontrolerów z poprzednią wersją routingu. Aby uzyskać instrukcje, zobacz przewodnik migracji 2.2-3.0. Zapoznaj się z wersją 2.2 tego dokumentu , aby uzyskać informacje referencyjne dotyczące starszego systemu routingu.

Konfigurowanie konwencjonalnej trasy

Startup.Configure Zazwyczaj kod jest podobny do następującego w przypadku korzystania z routingu konwencjonalnego:

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

Wewnątrz wywołania UseEndpoints, MapControllerRoute jest używane do tworzenia pojedynczej trasy. Pojedyncza trasa nosi nazwę default route. Większość aplikacji z kontrolerami i widokami używa szablonu trasy podobnego do default. REST Interfejsy API powinny używać routingu atrybutów.

Szablon "{controller=Home}/{action=Index}/{id?}"trasy:

  • Pasuje do ścieżki adresu URL, takiej jak /Products/Details/5

  • Wyodrębnia wartości { controller = Products, action = Details, id = 5 } tras, tokenizując ścieżkę. Wyodrębnianie wartości tras prowadzi do zgodności, jeśli aplikacja ma kontroler nazwany ProductsController oraz akcję Details.

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfo jest dostarczany przez pakiet NuGet Rick.Docs.Samples.RouteInfo i wyświetla informacje o trasie.

  • /Products/Details/5 model wiąże wartość parametru id = 5 z wartością id , aby ustawić parametr na 5. Aby uzyskać więcej informacji, zobacz Powiązanie modelu.

  • {controller=Home} definiuje Home jako wartość domyślną controller.

  • {action=Index} definiuje Index jako wartość domyślną action.

  • Znak ? w pliku {id?} definiuje id się jako opcjonalny.

  • Domyślne i opcjonalne parametry trasy nie muszą być obecne w ścieżce adresu URL dla dopasowania. Zobacz Dokumentacja szablonu trasy, aby uzyskać szczegółowy opis składni szablonu trasy.

  • Pasuje do ścieżki /adresu URL .

  • Tworzy wartości { controller = Home, action = Index }tras .

Wartości i controlleraction używają wartości domyślnych. id nie generuje wartości, ponieważ w ścieżce adresu URL nie ma odpowiedniego segmentu. / odpowiada tylko wtedy, gdy istnieje akcja HomeController i Index :

public class HomeController : Controller
{
  public IActionResult Index() { ... }
}

Korzystając z powyższej definicji kontrolera i szablonu trasy, HomeController.Index akcja jest uruchamiana dla następujących ścieżek URL:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

Ścieżka adresu URL / używa domyślnych Home kontrolerów i Index akcji szablonu trasy. Ścieżka adresu URL /Home używa domyślnej Index akcji szablonu trasy.

Metoda MapDefaultControllerRoutewygody:

endpoints.MapDefaultControllerRoute();

Zastępuje:

endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

Ważne

Routing jest skonfigurowany przy pomocy middleware UseRouting, MapControllerRoute i MapAreaControllerRoute. Aby użyć kontrolerów:

Routing konwencjonalny

Routing konwencjonalny jest używany z kontrolerami i widokami. Trasa default :

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

Powyższy jest przykładem konwencjonalnej trasy. Jest to nazywane routingiem konwencjonalnym, ponieważ ustanawia konwencjędla ścieżek adresów URL:

  • Pierwszy segment ścieżki, {controller=Home}, odpowiada nazwie kontrolera.
  • Drugi segment, {action=Index}, odnosi się do nazwy akcji.
  • Trzeci segment {id?} jest używany dla opcjonalnego id . Element ? in {id?} sprawia, że jest opcjonalny. id służy do mapowania na jednostkę modelu.

Korzystając z tej default trasy, ścieżka adresu URL:

  • /Products/List przypisuje się do ProductsController.List działania.
  • /Blog/Article/17 mapuje na BlogController.Article i zazwyczaj model wiąże id parametr z wartością 17.

To mapowanie:

  • Jest oparty tylko na kontrolerach i nazwach akcji.
  • Nie jest oparta na przestrzeniach nazw, lokalizacjach plików źródłowych ani parametrach metody.

Użycie konwencjonalnego routingu z trasą domyślną umożliwia utworzenie aplikacji bez konieczności tworzenia nowego wzorca adresu URL dla każdej akcji. W przypadku aplikacji z akcjami stylu CRUD spójność adresów URL między kontrolerami:

  • Ułatwia uproszczenie kodu.
  • Sprawia, że interfejs użytkownika jest bardziej przewidywalny.

Ostrzeżenie

Element id w poprzednim kodzie jest definiowany jako opcjonalny przez szablon trasy. Akcje mogą być wykonywane bez opcjonalnego identyfikatora podanego jako część adresu URL. Ogólnie rzecz biorąc, w przypadku pominięcia id w adresie URL:

  • id jest ustawiony na 0 przez bindowanie modelu.
  • W bazie danych pasującej id == 0nie można odnaleźć jednostki .

Routing atrybutów zapewnia szczegółową kontrolę, aby identyfikator był wymagany dla niektórych akcji, a nie dla innych. Zgodnie z konwencją dokumentacja zawiera opcjonalne parametry, takie jak id wtedy, gdy prawdopodobnie pojawią się w poprawnym użyciu.

Większość aplikacji powinna wybrać podstawowy i opisowy schemat routingu, aby adresy URL mogły być czytelne i zrozumiałe. Domyślna trasa konwencjonalna {controller=Home}/{action=Index}/{id?}:

  • Obsługuje podstawowy i opisowy schemat routingu.
  • Jest przydatnym punktem wyjścia dla aplikacji opartych na interfejsie użytkownika.
  • Jest jedynym szablonem ścieżki wymaganym dla wielu aplikacji interfejsu użytkownika w internecie. W przypadku większych aplikacji interfejsu użytkownika sieci Web kolejna trasa korzystająca z obszarów jest często wymagana.

MapControllerRoute i MapAreaRoute :

  • Automatycznie przypisz wartość kolejności do punktów końcowych na podstawie kolejności, w której są wywoływane.

Routing punktów końcowych w programie ASP.NET Core 3.0 lub nowszym:

  • Nie ma pojęcia tras.
  • Nie zapewnia gwarancji kolejności wykonywania rozszerzalności, wszystkie punkty końcowe są przetwarzane jednocześnie.

Włącz rejestrowanie , aby zobaczyć, jak wbudowane implementacje routingu, takie jak Route, pasują do żądań.

Routing atrybutów wyjaśniono w dalszej części tego dokumentu.

Wiele konwencjonalnych tras

W środku można dodać wiele UseEndpoints, dodając więcej wywołań do MapControllerRoute i MapAreaControllerRoute. Umożliwia to zdefiniowanie wielu konwencji lub dodanie konwencjonalnych tras przeznaczonych do określonej akcji, takich jak:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

Trasa blog w poprzednim kodzie jest dedykowaną trasą konwencjonalną. Nazywa się to dedykowaną tradycyjną trasą, ponieważ:

Ponieważ controller i action nie są wyświetlane w szablonie "blog/{*article}" trasy jako parametry:

  • Mogą mieć tylko wartości { controller = "Blog", action = "Article" }domyślne .
  • Ta trasa zawsze mapuje na akcję BlogController.Article.

/Blog, /Blog/Articlei /Blog/{any-string} są jedynymi ścieżkami adresów URL pasujących do trasy blogu.

Powyższy przykład:

  • blog trasa ma wyższy priorytet dla dopasowań niż default, ponieważ jest dodana jako pierwsza.
  • To przykład routingu stylu Slug , w którym zazwyczaj jest używana nazwa artykułu jako część adresu URL.

Ostrzeżenie

W systemie ASP.NET Core 3.0 lub nowszym routing nie działa w następujący sposób:

  • Definiowanie koncepcji nazywanej trasą. UseRouting Dodaje trasę zgodną z potokiem oprogramowania pośredniczącego. Oprogramowanie UseRouting pośredniczące analizuje zestaw punktów końcowych zdefiniowanych w aplikacji i wybiera najlepsze dopasowanie punktu końcowego na podstawie żądania.
  • Gwarantowanie kolejności realizacji rozszerzeń, takich jak IRouteConstraint lub IActionConstraint.

Zobacz Routing , aby uzyskać informacje referencyjne dotyczące routingu.

Tradycyjna kolejność routingu

Konwencjonalne routowanie dopasowuje się tylko do kombinacji działania i kontrolera, które są zdefiniowane przez aplikację. Ma to na celu uproszczenie przypadków, w których nakładają się konwencjonalne trasy. Dodawanie tras przy użyciu MapControllerRoute, MapDefaultControllerRoute i MapAreaControllerRoute automatycznie przypisuje wartość zamówienia do ich punktów końcowych na podstawie kolejności, w jakiej są wywoływane. Dopasowania z wyświetlonej wcześniej trasy mają wyższy priorytet. Routing konwencjonalny jest zależny od kolejności. Ogólnie rzecz biorąc, trasy z obszarami powinny być umieszczane wcześniej, ponieważ są bardziej szczegółowe niż trasy bez obszaru. Dedykowane trasy konwencjonalne z parametrami catch-all, takimi jak {*article}, mogą sprawić, że trasa będzie zbyt zachłanna, co oznacza, że będzie dopasowywać adresy URL, które powinny być obsługiwane przez inne trasy. Umieść chciwe trasy później w tabeli tras, aby zapobiec chciwym dopasowaniom.

Ostrzeżenie

Parametr catch-all może być niepoprawnie zgodny z trasami z powodu błędu w routingu. Aplikacje, których dotyczy ta usterka, mają następujące cechy:

  • Trasa typu catch-all, na przykład {**slug}"
  • Uniwersalna trasa nie odpowiada na żądania, które powinna obsługiwać.
  • Usunięcie innych tras powoduje, że uniwersalna trasa zaczyna działać.

Zobacz błędy GitHub 18677 i 16579 jako przykłady przypadków, w których wystąpiła ta usterka.

Poprawka wymagająca aktywacji dla tej usterki zawiera się w SDK platformy .NET Core 3.1.301 lub nowszym. 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.

Rozwiązywanie niejednoznacznych akcji

Gdy dwa punkty końcowe są zestawione za pomocą routingu, routing musi wykonać jedną z następujących czynności:

  • Wybierz najlepszego kandydata.
  • Zgłaszanie wyjątku.

Na przykład:

    public class Products33Controller : Controller
    {
        public IActionResult Edit(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpPost]
        public IActionResult Edit(int id, Product product)
        {
            return ControllerContext.MyDisplayRouteInfo(id, product.name);
        }
    }
}

Poprzedni kontroler definiuje dwie akcje zgodne z następującymi akcjami:

  • Ścieżka adresu URL /Products33/Edit/17
  • Kierowanie danych { controller = Products33, action = Edit, id = 17 }.

Jest to typowy wzorzec dla kontrolerów MVC:

  • Edit(int) wyświetla formularz do edycji produktu.
  • Edit(int, Product) przetwarza opublikowany formularz.

Aby ustalić prawidłową trasę:

  • Edit(int, Product) jest wybierany, gdy żądanie jest http POST.
  • Edit(int) jest wybierany, gdy czasownik HTTP jest czymkolwiek innym. Edit(int)jest zwykle wywoływana za pośrednictwem .GET

HttpPostAttribute [HttpPost] jest dostarczany do routingu, aby umożliwić wybór na podstawie metody HTTP żądania. Funkcja HttpPostAttribute sprawia, że Edit(int, Product) lepsze dopasowanie niż Edit(int).

Ważne jest, aby zrozumieć rolę atrybutów, takich jak HttpPostAttribute. Podobne atrybuty są definiowane dla innych czasowników HTTP. W przypadku routingu konwencjonalnego akcje używają tej samej nazwy akcji, gdy są częścią formularza pokazu, przesyłaj przepływ pracy formularza. Na przykład zobacz Zbadaj dwie metody akcji Edytuj.

Jeśli routing nie może wybrać najlepszego kandydata, zgłoszony zostanie wyjątek AmbiguousMatchException, wymieniający wiele dopasowanych punktów końcowych.

Konwencjonalne nazwy tras

Ciągi "blog" i "default" w następujących przykładach to konwencjonalne nazwy tras.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

Nazwy tras dają trasie nazwę logiczną. Nazwana trasa może służyć do generowania adresów URL. Użycie nazwanej trasy upraszcza tworzenie adresów URL, gdy kolejność tras może spowodować, że generowanie adresów URL jest skomplikowane. Nazwy tras muszą być unikatowe dla całej aplikacji.

Nazwy tras:

  • Nie ma wpływu na dopasowywanie adresów URL ani obsługę żądań.
  • Są używane tylko do generowania adresów URL.

Koncepcja nazwy trasy jest reprezentowana w routingu jako IEndpointNameMetadata. Terminy nazwa trasy i nazwa punktu końcowego:

  • Są zamienne.
  • Który z nich jest używany w dokumentacji i kodzie, zależy od opisanego interfejsu API.

Routing atrybutów dla REST interfejsów API

REST Interfejsy API powinny używać routingu atrybutów do modelowania funkcjonalności aplikacji jako zestawu zasobów, w których operacje są reprezentowane przez czasowniki HTTP.

Trasowanie atrybutowe używa zestawu atrybutów do mapowania działań bezpośrednio na szablony tras. Poniższy StartUp.Configure kod jest typowy dla interfejsu REST API i jest używany w następnym przykładzie:

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

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

W poprzednim kodzie MapControllers jest wywoływany wewnątrz UseEndpoints w celu mapowania kontrolerów kierowanych atrybutami.

W poniższym przykładzie:

  • HomeController dopasowuje się do zestawu adresów URL podobnych do tych, które obsługuje domyślna konwencjonalna trasa {controller=Home}/{action=Index}/{id?} .
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Akcja HomeController.Index jest uruchamiana dla dowolnej ścieżki /adresu URL , /Home, /Home/Indexlub /Home/Index/3.

W tym przykładzie wyróżniono kluczową różnicę programową między routingiem atrybutów a routingiem konwencjonalnym. Routing atrybutów wymaga większej ilości danych wejściowych w celu określenia trasy. Tradycyjna trasa domyślna obsługuje trasy bardziej zwięźle. Jednak trasowanie z użyciem atrybutów wymaga i pozwala na precyzyjną kontrolę nad tym, które szablony tras są stosowane w każdej akcji.

W przypadku routingu atrybutów nazwy kontrolera i akcji nie odgrywają żadnej roli w dopasowywaniu akcji, chyba że używana jest zamiana tokenu. Poniższy przykład jest zgodny z tymi samymi adresami URL co w poprzednim przykładzie:

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Poniższy kod używa zastępowania tokenów dla znaczników action i controller.

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Następujący kod ma zastosowanie [Route("[controller]/[action]")] do kontrolera:

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

W poprzednim kodzie szablony metod Index muszą mieć dodane na początku / lub ~/, tak aby poprzedzały szablony tras. Szablony tras stosowane do akcji, które zaczynają się od / lub ~/, nie są łączone z szablonami tras zastosowanymi do kontrolera.

Zobacz Pierwszeństwo szablonu trasy, aby uzyskać informacje na temat wyboru szablonu trasy.

Nazwy routingu zarezerwowanego

Następujące słowa kluczowe to zastrzeżone nazwy parametrów tras podczas korzystania z kontrolerów lub Razor stron:

  • action
  • area
  • controller
  • handler
  • page

Używanie page jako parametru trasy w trasowaniu przez atrybuty jest typowym błędem. Powoduje to niespójne i mylące zachowanie podczas generowania adresów URL.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

Nazwy parametrów specjalnych są używane przez generowanie adresów URL w celu określenia, czy operacja generowania adresu URL odnosi się do Razor strony lub kontrolera.

Następujące słowa kluczowe są zastrzeżone w kontekście Razor widoku lub Razor strony:

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Te słowa kluczowe nie powinny być używane w przypadku generowania linków, parametrów powiązanych z modelem ani właściwości najwyższego poziomu.

Szablony czasowników HTTP

ASP.NET Core ma następujące szablony czasowników HTTP:

Szablony tras

ASP.NET Core ma następujące szablony tras:

Routing atrybutów za pomocą atrybutów czasownika HTTP

Rozważmy następujący kontroler:

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W poprzednim kodzie:

  • Każda akcja zawiera [HttpGet] atrybut, który ogranicza dopasowanie tylko do żądań HTTP GET.
  • Akcja GetProduct zawiera "{id}" szablon, dlatego id jest dołączana do "api/[controller]" szablonu na kontrolerze. Szablon metody to "api/[controller]/{id}". W związku z tym ta akcja odpowiada tylko żądaniom GET dla formularza /api/test2/xyz,/api/test2/123/api/test2/{any string} itp.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • Akcja GetIntProduct zawiera "int/{id:int}" szablon. Część :int szablonu ogranicza id wartości trasy do ciągów, które można przekonwertować na liczbę całkowitą. Żądanie GET do /api/test2/int/abc:
    • Nie pasuje do tej akcji.
    • Zwraca błąd 404 Nie znaleziono.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • Akcja GetInt2Product zawiera {id} element w szablonie, ale nie ogranicza id się do wartości, które można przekonwertować na liczbę całkowitą. Żądanie GET do /api/test2/int2/abc:
    • Pasuje do tej trasy.
    • Model binding nie udaje się przekonwertować abc na liczbę całkowitą. Parametr id metody to liczba całkowita.
    • Zwraca 400 Bad Request, ponieważ powiązanie modelu nie zdołało przekonwertować abc na liczbę całkowitą.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Routing atrybutów może używać HttpMethodAttribute atrybutów, takich jak HttpPostAttribute, HttpPutAttributei HttpDeleteAttribute. Wszystkie atrybuty czasownika HTTP akceptują szablon trasy. W poniższym przykładzie przedstawiono dwie akcje pasujące do tego samego szablonu trasy:

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

Za pomocą ścieżki adresu URL /products3:

  • Akcja MyProductsController.ListProducts jest uruchamiana, gdy metoda HTTP jest GET.
  • Akcja MyProductsController.CreateProduct jest uruchamiana, gdy metoda HTTP jest POST.

Podczas tworzenia interfejsu REST API rzadko zdarza się, że trzeba użyć [Route(...)] w metodzie akcji, ponieważ akcja akceptuje wszystkie metody HTTP. Lepiej użyć bardziej szczegółowego atrybutu czasownika HTTP, aby dokładnie określić, co obsługuje interfejs API. Oczekuje się, że klienci interfejsów REST API będą wiedzieć, jakie ścieżki i czasowniki HTTP mapują na określone operacje logiczne.

REST Interfejsy API powinny używać routingu atrybutów do modelowania funkcjonalności aplikacji jako zestawu zasobów, w których operacje są reprezentowane przez czasowniki HTTP. Oznacza to, że wiele operacji, na przykład GET i POST w tym samym zasobie logicznym, używa tego samego adresu URL. Routing za pomocą atrybutów zapewnia poziom kontroli, który jest potrzebny do dokładnego zaprojektowania publicznego układu punktów końcowych API.

Ponieważ trasa atrybutu ma zastosowanie do określonej akcji, łatwo jest ustawić parametry wymagane w ramach definicji szablonu trasy. W poniższym przykładzie id jest wymagany jako część ścieżki adresu URL:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Akcja Products2ApiController.GetProduct(int) :

  • Jest uruchamiany ze ścieżką adresu URL, na przykład /products2/3
  • Nie jest uruchamiany za pomocą ścieżki adresu URL /products2.

Atrybut [Consumes] umożliwia akcji ograniczenie obsługiwanych typów zawartości żądania. Aby uzyskać więcej informacji, zobacz Definiowanie obsługiwanych typów zawartości żądań za pomocą atrybutu Consumes.

Zobacz Routing , aby uzyskać pełny opis szablonów tras i powiązanych opcji.

Aby uzyskać więcej informacji na temat [ApiController], zobacz atrybut ApiController.

Nazwa trasy

Poniższy kod definiuje nazwę trasy :Products_List

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Nazwy tras mogą służyć do generowania adresu URL na podstawie określonej trasy. Nazwy tras:

  • Nie ma wpływu na zachowanie dopasowywania adresów URL podczas trasowania.
  • Są używane tylko do generowania adresów URL.

Nazwy tras muszą być unikatowe dla całej aplikacji.

Porównaj powyższy kod z tradycyjną trasą domyślną, która definiuje id parametr jako opcjonalny ({id?}). Możliwość precyzyjnego określenia interfejsów API ma zalety, takie jak przyporządkowanie /products i /products/5 do różnych działań.

Łączenie tras atrybutów

Aby trasowanie według atrybutów było mniej powtarzalne, atrybuty ruty na kontrolerze są łączone z atrybutami ruty w poszczególnych akcjach. Wszystkie szablony tras zdefiniowane na kontrolerze są dołączane do szablonów tras w akcjach. Umieszczenie atrybutu trasy na kontrolerze sprawia, że wszystkie akcje w kontrolerze używają routingu atrybutów.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W powyższym przykładzie:

  • Ścieżka adresu URL /products może pasować do ProductsApi.ListProducts
  • Ścieżka /products/5 adresu URL może być zgodna z ProductsApi.GetProduct(int).

Obie te akcje są zgodne tylko z protokołem HTTP GET , ponieważ są one oznaczone atrybutem [HttpGet] .

Szablony tras stosowane do akcji, które zaczynają się od / lub ~/, nie są łączone z szablonami tras zastosowanymi do kontrolera. Poniższy przykład pasuje do zestawu ścieżek URL podobnych do trasy domyślnej.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Tabela poniżej wyjaśnia atrybuty [Route] w poprzednim kodzie.

Atrybut Łączy się z [Route("Home")] Definiuje wzorzec trasy
[Route("")] Tak "Home"
[Route("Index")] Tak "Home/Index"
[Route("/")] Nie ""
[Route("About")] Tak "Home/About"

Kolejność tras atrybutów

Routing tworzy drzewo i pasuje do wszystkich punktów końcowych jednocześnie:

  • Wpisy trasy zachowują się tak, jakby zostały umieszczone w idealnej kolejności.
  • Najbardziej konkretne trasy mają szansę wykonać przed bardziej ogólnymi trasami.

Na przykład trasa atrybutu taka jak blog/search/{topic} jest bardziej specyficzna niż trasa atrybutu taka jak blog/{*article}. Trasa blog/search/{topic} ma domyślnie wyższy priorytet, ponieważ jest bardziej szczegółowa. Korzystając z routingu konwencjonalnego, deweloper jest odpowiedzialny za umieszczanie tras w żądanej kolejności.

Trasy atrybutów mogą konfigurować kolejność przy użyciu Order właściwości . Wszystkie dostarczone atrybuty trasy obejmują Order. Trasy są przetwarzane zgodnie z sortowaniem rosnącym według właściwości Order. Domyślna kolejność to 0. Ustawianie trasy za pomocą Order = -1 odbywa się przed trasami, które nie mają ustalonej kolejności. Ustawianie trasy przy użyciu Order = 1 odbywa się po domyślnym porządkowaniu tras.

Unikaj polegania na Order. Jeśli przestrzeń adresów URL aplikacji wymaga jawnie określonych wartości kolejności do poprawnego kierowania, prawdopodobnie jest to również mylące dla klientów. Ogólnie rzecz biorąc, routing atrybutów wybiera poprawną trasę z dopasowaniem adresu URL. Jeśli domyślna kolejność używana na potrzeby generowania adresów URL nie działa, użycie nazwy trasy jako zastąpienia jest zwykle prostsze niż zastosowanie Order właściwości.

Rozważmy dwa następujące kontrolery, które definiują dopasowanie trasy /home:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

/home Żądanie z powyższym kodem zgłasza wyjątek podobny do następującego:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

Dodanie Order do jednego z atrybutów trasy rozwiązuje niejednoznaczność:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

W poprzednim kodzie /home uruchamia HomeController.Index punkt końcowy. Aby dostać się do MyDemoController.MyIndex, złóż żądanie /home/MyIndex. Uwaga:

  • Powyższy kod jest przykładem lub słabym projektem routingu. Użyto tego do zilustrowania Order właściwości.
  • Właściwość Order rozwiązuje tylko niejednoznaczność, że tego szablonu nie można dopasować. Lepiej byłoby usunąć [Route("Home")] szablon.

Zobacz Razor Konwencje tras stron i aplikacji: Kolejność tras, aby uzyskać informacje na temat kolejności tras za pomocą Razor stron.

W niektórych przypadkach zwracany jest błąd HTTP 500 z niejednoznacznymi trasami. Użyj logowania, aby zobaczyć, które punkty końcowe spowodowały AmbiguousMatchException.

Zamiana tokenów w szablonach tras [kontroler], [akcja], [obszar]

Dla wygody trasy z przypisanymi atrybutami obsługują zamianę tokenów przez umieszczenie tokenu w nawiasach kwadratowych ([, ]). Tokeny [action], [area]i [controller] są zastępowane wartościami nazwy akcji, nazwy obszaru i nazwy kontrolera z akcji, w której zdefiniowano trasę:

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W poprzednim kodzie:

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • Dopasowania /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Dopasowania /Products0/Edit/{id}

Zastąpienie tokenu odbywa się w ostatnim kroku tworzenia tras atrybutów. Powyższy przykład zachowuje się tak samo jak w poniższym kodzie:

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Jeśli czytasz to w języku innym niż angielski, daj nam znać w tej kwestii dyskusji GitHub, jeśli chcesz, aby komentarze kodu były w Twoim języku ojczystym.

Trasy atrybutów można również łączyć z dziedziczeniem. Jest to potężne połączenie z zastępowaniem tokenu. Zamiana tokenu ma również zastosowanie do nazw ścieżek zdefiniowanych przez ścieżki atrybutów. [Route("[controller]/[action]", Name="[controller]_[action]")]generuje unikatową nazwę trasy dla każdej akcji:

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

pl-PL: Aby dopasować ogranicznik zastępczy tokenu literału [ lub ], należy go zduplikować, powtarzając znak ([[ lub ]]).

Dostosowywanie zamiany tokenu za pomocą funkcji przekształcania parametrów

Zamianę tokenu można dostosować przy użyciu transformatora parametrów. Transformator parametrów implementuje IOutboundParameterTransformer i przekształca wartość parametrów. Na przykład niestandardowy transformator parametrów SlugifyParameterTransformer zmienia SubscriptionManagement wartość trasy na subscription-management.

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

To RouteTokenTransformerConvention jest konwencją modelu aplikacji, która:

  • Stosuje transformator parametru do wszystkich tras atrybutów w aplikacji.
  • Dostosowuje wartości tokenów atrybutów trasy podczas ich zastępowania.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Poprzednia ListAll metoda pasuje do /subscription-management/list-all.

Element RouteTokenTransformerConvention jest zarejestrowany jako opcja w ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

Aby uzyskać definicję terminu „Slug”, odnieś się do dokumentacji internetowej MDN dotyczącej Slug.

Ostrzeżenie

W przypadku używania System.Text.RegularExpressions do przetwarzania niezaufanych danych wejściowych należy ustawić limit czasu. Złośliwy użytkownik może podać dane wejściowe, aby spowodować RegularExpressionsatak typu "odmowa usługi". API platformy ASP.NET Core, które używają RegularExpressions, przekazują limit czasu.

Wiele tras opartych na atrybutach

Routing atrybutów obsługuje definiowanie wielu tras, które docierają do tej samej akcji. Najczęstszym zastosowaniem tej metody jest naśladowanie zachowania domyślnej konwencjonalnej trasy, jak pokazano w poniższym przykładzie:

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Umieszczenie wielu atrybutów trasy na kontrolerze oznacza, że każdy z nich łączy się z każdym z atrybutów trasy w metodach akcji:

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Wszystkie ograniczenia trasy czasownika HTTP implementują IActionConstraint.

Gdy na akcji umieszczone są wiele atrybutów trasy implementujących IActionConstraint:

  • Każde ograniczenie akcji łączy się z szablonem trasy zastosowanym do kontrolera.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Używanie wielu tras w akcjach może wydawać się użyteczne i potężne, jednak lepiej zachować przestrzeń adresów URL aplikacji jako podstawową i dobrze zdefiniowaną. Korzystaj z wielu tras w akcjach jedynie tam, gdzie jest to konieczne, na przykład, aby wspierać istniejących klientów.

Określanie opcjonalnych parametrów trasy atrybutu, wartości domyślnych i ograniczeń

Trasy atrybutów obsługują tę samą składnię śródliniową co konwencjonalne trasy w celu określenia opcjonalnych parametrów, wartości domyślnych i ograniczeń.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

W poprzednim kodzie [HttpPost("product14/{id:int}")] stosuje ograniczenie trasy. Akcja Products14Controller.ShowProduct jest dopasowywana tylko przez ścieżki adresu URL, takie jak /product14/3. Część szablonu {id:int} trasy ogranicza ten segment tylko do liczb całkowitych.

Zobacz Dokumentacja szablonu trasy, aby uzyskać szczegółowy opis składni szablonu trasy.

Niestandardowe atrybuty trasy przy użyciu elementu IRouteTemplateProvider

Wszystkie atrybuty trasy implementują IRouteTemplateProviderelement . Środowisko uruchomieniowe ASP.NET Core:

  • Szuka atrybutów w klasach kontrolerów i metodach akcji podczas uruchamiania aplikacji.
  • Używa atrybutów implementujących IRouteTemplateProvider do tworzenia początkowego zestawu tras.

Zaimplementuj IRouteTemplateProvider , aby zdefiniować niestandardowe atrybuty trasy. Każda IRouteTemplateProvider z nich umożliwia zdefiniowanie pojedynczej trasy przy użyciu niestandardowego szablonu trasy, kolejności i nazwy:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; }
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Poprzednia Get metoda zwraca wartość Order = 2, Template = api/MyTestApi.

Dostosowywanie tras atrybutów za pomocą modelu aplikacji

Model aplikacji:

  • Jest modelem obiektów utworzonym podczas uruchamiania.
  • Zawiera wszystkie metadane używane przez ASP.NET Core do kierowania i wykonywania akcji w aplikacji.

Model aplikacji zawiera wszystkie dane zebrane z atrybutów trasy. Dane z atrybutów trasy są dostarczane przez implementację IRouteTemplateProvider . Konwencje:

  • Można zapisać zmiany w celu zmodyfikowania modelu aplikacji, aby dostosować sposób działania routingu.
  • Są odczytywane podczas uruchamiania aplikacji.

W tej sekcji przedstawiono podstawowy przykład dostosowywania routingu przy użyciu modelu aplikacji. Poniższy kod sprawia, że trasy są w przybliżeniu zgodne ze strukturą folderów projektu.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

Poniższy kod uniemożliwia zastosowanie konwencji do kontrolerów, które są kierowane za pomocą atrybutów.

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Na przykład następujący kontroler nie używa polecenia NamespaceRoutingConvention:

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Metoda NamespaceRoutingConvention.Apply:

  • Nic nie robi, jeśli kontroler jest kierowany atrybut.
  • Ustawia szablon kontrolerów na namespacepodstawie elementu z usuniętą bazą namespace .

Element NamespaceRoutingConvention można zastosować w pliku Startup.ConfigureServices:

namespace My.Application
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Conventions.Add(
                    new NamespaceRoutingConvention(typeof(Startup).Namespace));
            });
        }
        // Remaining code ommitted for brevity.

Rozważmy na przykład następujący kontroler:

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

W poprzednim kodzie:

  • Podstawą namespace jest My.Application.
  • Pełna nazwa poprzedniego kontrolera to My.Application.Admin.Controllers.UsersController.
  • Parametr NamespaceRoutingConvention ustawia szablon kontrolerów na Admin/Controllers/Users/[action]/{id?wartość .

Może być również zastosowany jako atrybut na kontrolerze: NamespaceRoutingConvention

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Routing mieszany: routing atrybutów a routing konwencjonalny

aplikacje ASP.NET Core mogą łączyć użycie konwencjonalnego routingu i routingu atrybutów. Typowe jest używanie konwencjonalnych tras dla kontrolerów obsługujących strony HTML dla przeglądarek i routing atrybutów dla kontrolerów obsługujących REST interfejsy API.

Akcje są tradycyjnie kierowane lub kierowane przez atrybuty. Umieszczenie trasy w kontrolerze lub akcji sprawia, że trasa staje się przypisana jako atrybut. Akcje definiujące trasy atrybutów nie mogą być osiągane za pośrednictwem konwencjonalnych tras i odwrotnie. Każdy atrybut trasy na kontrolerze sprawia, że wszystkie akcje w atrybucie kontrolera są kierowane.

Routing atrybutów i routing konwencjonalny używają tego samego mechanizmu routingu.

Generowanie adresów URL i wartości otoczenia

Aplikacje mogą korzystać z funkcji generowania URL w routingu, aby tworzyć linki URL do akcji. Generowanie adresów URL eliminuje stałe kodowanie adresów URL, dzięki czemu kod jest bardziej niezawodny i łatwiejszy do utrzymania. Ta sekcja koncentruje się na funkcjach generowania adresów URL udostępnianych przez mvC i omówiono tylko podstawowe informacje na temat sposobu działania generowania adresów URL. Zobacz Routing , aby uzyskać szczegółowy opis generowania adresu URL.

Interfejs IUrlHelper jest podstawowym elementem infrastruktury między MVC i routingiem na potrzeby generowania adresów URL. W kontrolerach, widokach i składnikach widoku instancja IUrlHelper jest dostępna za pośrednictwem właściwości Url.

W poniższym przykładzie interfejs IUrlHelper jest używany poprzez właściwość Controller.Url do wygenerowania adresu URL prowadzącego do innej akcji.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Jeśli aplikacja używa domyślnej konwencjonalnej trasy, wartość url zmiennej to ciąg /UrlGeneration/Destinationścieżki adresu URL . Ścieżka tego adresu URL jest tworzona poprzez routowanie, które polega na łączeniu.

  • Wartości trasy z bieżącego żądania, które są nazywane wartościami otoczenia.
  • Wartości przekazane do Url.Action i podstawiając te wartości do szablonu trasy:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Każdy parametr trasy w szablonie trasy ma swoją wartość zamienioną przez dopasowanie nazw z wartościami i wartościami domyślnymi. Parametr trasy, który nie ma wartości, może:

  • Użyj wartości domyślnej, jeśli ma ją.
  • Można pominąć, jeśli jest to opcjonalne. Na przykład id z szablonu trasy {controller}/{action}/{id?}.

Generowanie adresu URL kończy się niepowodzeniem, jeśli jakikolwiek wymagany parametr trasy nie ma odpowiedniej wartości. Jeśli generowanie adresu URL zakończy się niepowodzeniem dla trasy, kolejna trasa zostanie podjęta do momentu wypróbowanej wszystkich tras lub znalezienia dopasowania.

W poprzednim przykładzie przyjęto Url.Actionrouting konwencjonalny. Generowanie adresów URL działa podobnie z routingiem atrybutów, chociaż koncepcje są różne. Z konwencjonalnym trasowaniem:

  • Wartości tras służą do rozwijania szablonu.
  • Wartości tras dla controller i action zwykle są wyświetlane w tym szablonie. Działa to, ponieważ adresy URL dopasowane przez routing są zgodne z konwencją.

W poniższym przykładzie użyto routingu atrybutów:

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

Akcja Source w poprzednim kodzie generuje custom/url/to/destinationelement .

LinkGenerator dodano w ASP.NET Core 3.0 jako alternatywę dla IUrlHelper. LinkGenerator oferuje podobne, ale bardziej elastyczne funkcje. Każda metoda w systemie IUrlHelper ma również odpowiednią rodzinę metod LinkGenerator .

Generowanie adresów URL według nazwy akcji

Url.Action, LinkGenerator.GetPathByAction oraz wszystkie powiązane przeciążenia mają na celu generowanie docelowego punktu końcowego poprzez określenie nazwy kontrolera i nazwy akcji.

W przypadku używania parametru Url.Actionbieżące wartości tras i controlleraction są udostępniane przez środowisko uruchomieniowe:

  • Wartość controller i action są częścią wartości otoczenia i wartości. Metoda Url.Action zawsze używa bieżących action wartości i controller generuje ścieżkę adresu URL, która kieruje do bieżącej akcji.

Routing próbuje użyć wartości w wartościach otoczenia, aby wypełnić informacje, które nie zostały podane podczas generowania adresu URL. Rozważ trasę taką jak {a}/{b}/{c}/{d} z wartościami otoczenia { a = Alice, b = Bob, c = Carol, d = David }.

  • Routing ma wystarczającą ilość informacji, aby wygenerować adres URL bez żadnych dodatkowych wartości.
  • Routing ma wystarczającą ilość informacji, ponieważ wszystkie parametry trasy mają wartość.

Jeśli wartość { d = Donovan } zostanie dodana:

  • Wartość { d = David } jest ignorowana.
  • Wygenerowana ścieżka adresu URL to Alice/Bob/Carol/Donovan.

Ostrzeżenie: ścieżki adresów URL są hierarchiczne. W poprzednim przykładzie, jeśli wartość { c = Cheryl } jest dodawana:

  • Obie wartości { c = Carol, d = David } są ignorowane.
  • Generowanie adresu URL nie jest już wartością d , a generowanie adresu URL kończy się niepowodzeniem.
  • Wymagane wartości c i d należy określić, aby wygenerować adres URL.

Możesz spodziewać się wystąpienia tego problemu z domyślną trasą {controller}/{action}/{id?}. Ten problem jest rzadki w praktyce, ponieważ Url.Action zawsze jawnie określa wartość controller i action .

Kilka przeciążeń Url.Action przyjmuje obiekt wartości trasy, aby podać wartości dla parametrów trasy innych niż controller oraz action. Obiekt wartości trasy jest często używany z elementem id. Na przykład Url.Action("Buy", "Products", new { id = 17 }). Obiekt wartości trasy:

  • Zgodnie z konwencją jest zwykle obiektem typu anonimowego.
  • Może to być IDictionary<> lub POCO.

Wszelkie dodatkowe wartości tras, które nie pasują do parametrów trasy, są umieszczane w ciągu zapytania.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url);
}

Powyższy kod generuje /Products/Buy/17?color=red.

Poniższy kod generuje bezwzględny adres URL:

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url);
}

Aby utworzyć bezwzględny adres URL, użyj jednego z następujących elementów:

  • Przeciążenie, które akceptuje element protocol. Na przykład powyższy kod.
  • LinkGenerator.GetUriByAction, który domyślnie generuje bezwzględne identyfikatory URI.

Generowanie adresów URL według trasy

Powyższy kod demonstrował generowanie adresu URL przez przekazanie kontrolera i nazwy akcji. IUrlHelper Udostępnia również rodzinę metod Url.RouteUrl . Te metody są podobne do Url.Action, ale nie kopiują bieżących wartości z action i controller do wartości trasy. Najbardziej typowe użycie elementu Url.RouteUrl:

  • Określa nazwę trasy do wygenerowania adresu URL.
  • Ogólnie rzecz biorąc, nie określa kontrolera ani nazwy akcji.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Razor Poniższy plik generuje link HTML do pliku Destination_Route:

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Generowanie adresów URL w kodzie HTML i Razor

IHtmlHelperudostępnia metody HtmlHelper i Html.ActionLink do generowania elementów i <form> .<a> Te metody używają metody Url.Action do wygenerowania adresu URL i akceptują podobne argumenty. Są Url.RouteUrl one obsługiwane HtmlHelperHtml.BeginRouteForm i Html.RouteLink które mają podobne funkcje.

TagHelpers generują adresy URL za pośrednictwem form klasy TagHelper i <a> TagHelper. Oba te elementy używają IUrlHelper do swojej implementacji. Aby uzyskać więcej informacji, zobacz Pomocnicy tagów w formularzach .

Wewnątrz widoków IUrlHelper właściwość jest dostępna za pośrednictwem Url właściwości dla dowolnego generowania adresów URL ad hoc, które nie są objęte powyższymi elementami.

Generowanie adresu URL w wynikach akcji

W poprzednich przykładach pokazano użycie IUrlHelper w kontrolerze. Najczęstszym użyciem kontrolera jest wygenerowanie adresu URL w ramach wyniku akcji.

Podstawowe klasy ControllerBase i Controller oferują wygodne metody dla wyników działań odwołujących się do innych działań. Jednym z typowych użycia jest przekierowanie po zaakceptowaniu danych wejściowych użytkownika:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

Metody fabryki wyników akcji, takie jak RedirectToAction i CreatedAtAction, podążają za wzorcem podobnym do metod na IUrlHelper.

Szczególny przypadek dla dedykowanych tras konwencjonalnych

Tradycyjne trasowanie może używać specjalnego rodzaju definicji trasy nazywanej dedykowaną konwencjonalną trasą. W poniższym przykładzie trasa o nazwie blog jest dedykowaną trasą konwencjonalną:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

Korzystając z powyższych definicji tras, Url.Action("Index", "Home") generuje ścieżkę URL / używając trasy default, ale dlaczego? Może się okazać, że wartości { controller = Home, action = Index } trasy będą wystarczające, aby wygenerować adres URL przy użyciu metody blog, a wynikiem będzie /blog?action=Index&controller=Home.

Dedykowane konwencjonalne trasy wykorzystują specjalne zachowanie wartości domyślnych, które nie mają odpowiadającego parametru trasy, aby zapobiec nadmiernemu generowaniu adresów URL przez trasy. W takim przypadku wartości domyślne to { controller = Blog, action = Article }, a ani controller, ani action nie pojawiają się jako parametry trasy. Gdy routing wykonuje generowanie adresów URL, podane wartości muszą być zgodne z wartościami domyślnymi. Generowanie adresu URL za pomocą blog kończy się niepowodzeniem, ponieważ wartości { controller = Home, action = Index } nie pasują do { controller = Blog, action = Article }. Następnie routing wraca do próby default, co się udaje.

Obszary

Obszary są funkcją MVC używaną do organizowania powiązanych funkcji w grupie jako oddzielnej:

  • Trasowanie przestrzeni nazw dla akcji kontrolera.
  • Struktura folderów dla widoków.

Używanie obszarów umożliwia aplikacji posiadanie wielu kontrolerów o tej samej nazwie, o ile mają różne obszary. Użycie obszarów tworzy hierarchię na potrzeby routingu przez dodanie innego parametru area trasy do controller i action. W tej sekcji omówiono sposób interakcji routingu z obszarami. Zobacz Obszary , aby uzyskać szczegółowe informacje na temat sposobu użycia obszarów z widokami.

Poniższy przykład konfiguruje wzorzec MVC do używania domyślnej trasy konwencjonalnej oraz trasy area dla trasy nazwanej areaBlog.

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

W poprzednim kodzie MapAreaControllerRoute jest wywoływane, aby utworzyć "blog_route". Drugi parametr , "Blog"to nazwa obszaru.

Podczas dopasowywania ścieżki adresu URL, takiej jak /Manage/Users/AddUser, trasa "blog_route" generuje wartości trasy { area = Blog, controller = Users, action = AddUser }. Wartość trasy area jest generowana przez domyślną wartość elementu area. Trasa utworzona przez MapAreaControllerRoute jest równoważna następującym:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

MapAreaControllerRoute tworzy trasę, korzystając z zarówno wartości domyślnej, jak i ograniczenia dla area, wykorzystując podaną nazwę obszaru, w tym przypadku Blog. Wartość domyślna gwarantuje, że trasa zawsze generuje { area = Blog, ... }, a ograniczenie wymaga wartości { area = Blog, ... } do generowania adresu URL.

Routing konwencjonalny jest zależny od kolejności. Ogólnie rzecz biorąc, trasy z obszarami powinny być umieszczane wcześniej, ponieważ są bardziej szczegółowe niż trasy bez obszaru.

Korzystając z powyższego przykładu, wartości trasy { area = Blog, controller = Users, action = AddUser } odpowiadają następującej akcji.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

Atrybut [Area] określa kontroler w ramach obszaru. Ten kontroler znajduje się w Blog obszarze. Kontrolery bez atrybutu [Area] nie są elementami członkowskimi żadnego obszaru i nie są zgodne, gdy area wartość trasy jest dostarczana przez routing. W poniższym przykładzie tylko pierwszy kontroler wymieniony może odpowiadać wartościom { area = Blog, controller = Users, action = AddUser }trasy.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

Przestrzeń nazw każdego kontrolera jest wyświetlana tutaj w celu uzyskania kompletności. Jeśli poprzednie kontrolery używały tej samej przestrzeni nazw, zostanie wygenerowany błąd kompilatora. Przestrzenie nazw klas nie mają wpływu na routing MVC.

Dwa pierwsze kontrolery są członkami obszarów i są zgodne tylko wtedy, gdy ich nazwa obszaru jest podana area przez wartość trasy. Trzeci kontroler nie jest członkiem żadnego obszaru i może działać poprawnie tylko wtedy, gdy routing nie prześle żadnej wartości dla area.

Jeśli chodzi o dopasowanie żadnej wartości, brak area wartości jest taki sam, jak w przypadku wartości area null lub pustego ciągu.

Podczas wykonywania akcji wewnątrz obszaru wartość trasy jest dostępna jako wartość area otoczenia dla routingu do użycia na potrzeby generowania adresów URL. Oznacza to, że domyślnie obszary działają lepkie dla generowania adresów URL, jak pokazano w poniższym przykładzie.

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute(name: "duck_route", 
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute(name: "default",
                                 pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

Poniższy kod generuje adres URL do /Zebra/Users/AddUser:

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

Definicja akcji

Metody publiczne na kontrolerze, z wyjątkiem tych z atrybutem NonAction , są akcjami.

Przykładowy kod

Diagnostyka debugowania

Aby uzyskać szczegółowe dane wyjściowe diagnostyki routingu, ustaw wartość Logging:LogLevel:MicrosoftDebug. W środowisku programistycznym ustaw poziom logowania w appsettings.Development.json:

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